Skip to content

Commit

Permalink
Implement PackageIcon automatic packing
Browse files Browse the repository at this point in the history
Just like our PackagePath implies Pack=true, we should likewise automatically pack a None/Content item if it was used as the PackageIcon property.

We support this for relative paths within the project as well as linked files (in case users use the same icon across multiple projects).

This means that in the most common case (a .jpg/.png) alognside the project file, setting the PackageIcon property to that file will Just Work.
  • Loading branch information
kzu committed Feb 27, 2023
1 parent bd6cc9c commit 4cb987e
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 8 deletions.
50 changes: 49 additions & 1 deletion src/NuGetizer.Tasks/NuGetizer.Inference.targets
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Copyright (c) .NET Foundation. All rights reserved.
_SetPackTargetFramework;
InferPackageContents
</GetPackageContentsDependsOn>

</PropertyGroup>

<Target Name="_SetDefaultPackageReferencePack" Condition="'$(PackFolder)' == 'build' or '$(PackFolder)' == 'buildTransitive'"
Expand All @@ -198,6 +199,31 @@ Copyright (c) .NET Foundation. All rights reserved.
</ItemGroup>
</Target>

<!--
Updates None/Content to have the proper packaging metadata for the icon metadata.
The PackageIcon can point to a relative project directory path, or a root relative path.
In either case, the PackageIcon becomes the package path.
The item can be a linked file too, in which case the PackageIcon should match the link
within the project structure.
If the item was Content, we change its packing properties so it doesn't become a
contentFiles item in the package, which would end up in the users' solution.
-->
<Target Name="_InferPackageIcon" Condition="'$(PackageIcon)' != ''">
<ItemGroup>
<None Update="@(None)"
Pack="true"
PackagePath="%(None.Link)"
Condition="%(None.Identity) == '$(PackageIcon)' or %(None.Link) == '$(PackageIcon)'" />

<Content Update="@(Content)"
Pack="true"
BuildAction="None"
PackFolder="None"
PackagePath="%(Content.Link)"
Condition="%(Content.Identity) == '$(PackageIcon)' or %(Content.Link) == '$(PackageIcon)'" />
</ItemGroup>
</Target>

<Target Name="_CollectInferenceCandidates" Inputs="@(PackInference)" Outputs="|%(PackInference.Identity)|">
<PropertyGroup>
<PackExclude>%(PackInference.PackExclude)</PackExclude>
Expand Down Expand Up @@ -247,7 +273,7 @@ Copyright (c) .NET Foundation. All rights reserved.

</Target>

<Target Name="InferPackageContents" DependsOnTargets="$(InferPackageContentsDependsOn);_CollectInferenceCandidates" Returns="@(PackageFile)">
<Target Name="InferPackageContents" DependsOnTargets="$(InferPackageContentsDependsOn);_InferPackageIcon;_CollectInferenceCandidates" Returns="@(PackageFile)">
<Error Code="NG1004" Condition="'$(PackAsTool)' == 'true' and '$(PackAsPublish)' == 'true'" Text="PackAsTool and PackAsPublish are mutually exclusive." />

<!-- Even if all these conditions are false, the user can still explicitly pack the file, of course -->
Expand All @@ -261,6 +287,28 @@ Copyright (c) .NET Foundation. All rights reserved.
Condition="'%(Extension)' == '$(PackageReadmeExtension)'" />
</ItemGroup>

<ItemGroup Label="Icon" Condition="'$(PackageIcon)' != ''">
<_PackageIcon Include="$(PackageIcon)" />
</ItemGroup>
<PropertyGroup Label="Icon" Condition="'$(PackageIcon)' != '' and @(_PackageIcon -> Count()) == '1'">
<PackageIconExists Condition="Exists(@(_PackageIcon -> '%(FullPath)'))"></PackageIconExists>
<PackageIconFilename Condition="'$(PackageIconExists)' == 'true'">@(_PackageIcon -> '%(Filename)')</PackageIconFilename>
<PackageIconExtension Condition="'$(PackageIconExists)' == 'true'">@(_PackageIcon -> '%(Extension)')</PackageIconExtension>
</PropertyGroup>

<!-- Even if all these conditions are false, the user can still explicitly pack the icon file, of course -->
<ItemGroup Label="Icon" Condition="'$(PackageIcon)' != ''">
<_ExistingIcon Include="@(None -> WithMetadataValue('Filename', '$(PackageReadmeFilename)'))" />

<_ExistingReadme Include="@(None -> WithMetadataValue('Filename', '$(PackageReadmeFilename)'))" />
<_ExistingReadme Include="@(Content -> WithMetadataValue('Filename', '$(PackageReadmeFilename)'))" Condition="'@(_ExistingReadme)' == ''" />
<_ExistingReadme Include="$(MSBuildProjectDirectory)\$(PackageReadmeFilename)$(PackageReadmeExtension)" Condition="'@(_ExistingReadme)' == '' and Exists('$(MSBuildProjectDirectory)\$(PackageReadmeFilename)$(PackageReadmeExtension)')" />

<InferenceCandidate Include="@(_ExistingReadme)"
PackagePath="%(Filename)%(Extension)"
Condition="'%(Extension)' == '$(PackageReadmeExtension)'" />
</ItemGroup>

<ItemGroup>
<InferenceCandidate>
<ShouldPack Condition="('%(Pack)' == 'true' or '%(PackagePath)' != '' or '%(PackFolder)' != '' or '%(PackageReference)' != '') and '%(Pack)' != 'false'">true</ShouldPack>
Expand Down
4 changes: 3 additions & 1 deletion src/NuGetizer.Tests/Builder.NuGetizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ public static TargetResult BuildProjects(
}
catch (System.Xml.XmlException)
{
File.WriteAllText(Path.Combine(scenarioDir, file.name), file.contents);
var path = Path.Combine(scenarioDir, file.name);
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllText(path, file.contents);
}
}

Expand Down
137 changes: 137 additions & 0 deletions src/NuGetizer.Tests/InlineProjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -817,5 +817,142 @@ public void when_validating_package_then_succeeds()

result.AssertSuccess(output);
}

[Fact]
public void when_package_icon_default_then_packs_icon()
{
var result = Builder.BuildProject(
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageIcon>icon.png</PackageIcon>
<EnableDefaultItems>true</EnableDefaultItems>
</PropertyGroup>
</Project>
"""
, output: output,
files: ("icon.png", ""));

result.AssertSuccess(output);

Assert.Contains(result.Items, item => item.Matches(new
{
PackFolder = "Metadata",
Icon = "icon.png",
}));
Assert.Contains(result.Items, item => item.Matches(new
{
PackagePath = "icon.png",
}));
}

[Fact]
public void when_package_icon_relative_folder_default_then_packs_icon()
{
var result = Builder.BuildProject(
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageIcon>assets\icon.png</PackageIcon>
<EnableDefaultItems>true</EnableDefaultItems>
</PropertyGroup>
</Project>
"""
, output: output,
files: ("assets\\icon.png", ""));

result.AssertSuccess(output);

Assert.Contains(result.Items, item => item.Matches(new
{
PackFolder = "Metadata",
Icon = "assets/icon.png",
}));
Assert.Contains(result.Items, item => item.Matches(new
{
PackagePath = "assets/icon.png",
}));
}

[Fact]
public void when_package_icon_content_then_packs_icon_and_not_content()
{
var result = Builder.BuildProject(
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="icon.png" />
</ItemGroup>
</Project>
"""
, output: output,
files: ("icon.png", ""));

result.AssertSuccess(output);

Assert.Contains(result.Items, item => item.Matches(new
{
PackFolder = "Metadata",
Icon = "icon.png",
}));
// The icon that would be in the content folder is not packed as a contentFile
Assert.DoesNotContain(result.Items, item => item.Matches(new
{
PackFolder = "content"
}));
// And it's instead in the root of the package
Assert.Contains(result.Items, item => item.Matches(new
{
PackagePath = "icon.png",
}));
}

[Fact]
public void when_package_icon_linked_content_then_packs_link()
{
var result = Builder.BuildProject(
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageIcon>assets\icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="..\icon.png" Link="assets\icon.png" />
</ItemGroup>
</Project>
"""
, output: output,
files: ("..\\icon.png", ""));

result.AssertSuccess(output);

Assert.Contains(result.Items, item => item.Matches(new
{
PackFolder = "Metadata",
Icon = "assets/icon.png",
}));
// The icon that would be in the content folder is not packed as a contentFile
Assert.DoesNotContain(result.Items, item => item.Matches(new
{
PackFolder = "content"
}));
// And it's instead in the root of the package
Assert.Contains(result.Items, item => item.Matches(new
{
PackagePath = "assets/icon.png",
}));
}

}
}
10 changes: 4 additions & 6 deletions src/NuGetizer.Tests/Utilities/TaskItemExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public static bool Matches(this ITaskItem item, object metadata)
{
foreach (var prop in metadata.GetType().GetProperties())
{
var actual = item.GetMetadata(prop.Name);
var expected = prop.GetValue(metadata).ToString();
var actual = item.GetMetadata(prop.Name).Replace('\\', '/');
var expected = prop.GetValue(metadata).ToString().Replace('\\', '/');

if (!actual.Equals(expected, StringComparison.OrdinalIgnoreCase))
return false;
Expand All @@ -26,10 +26,8 @@ public static bool Matches(this ITaskItem item, object metadata)

public static Manifest GetManifest(this ITaskItem package)
{
using (var reader = new PackageArchiveReader(package.GetMetadata("FullPath")))
{
return reader.GetManifest();
}
using var reader = new PackageArchiveReader(package.GetMetadata("FullPath"));
return reader.GetManifest();
}
}
}

0 comments on commit 4cb987e

Please sign in to comment.