Skip to content

Commit 38ee8fc

Browse files
committed
Implement PackageIcon automatic packing
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.
1 parent bd6cc9c commit 38ee8fc

File tree

4 files changed

+193
-8
lines changed

4 files changed

+193
-8
lines changed

src/NuGetizer.Tasks/NuGetizer.Inference.targets

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ Copyright (c) .NET Foundation. All rights reserved.
187187
_SetPackTargetFramework;
188188
InferPackageContents
189189
</GetPackageContentsDependsOn>
190+
190191
</PropertyGroup>
191192

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

202+
<!--
203+
Updates None/Content to have the proper packaging metadata for the icon metadata.
204+
The PackageIcon can point to a relative project directory path, or a root relative path.
205+
In either case, the PackageIcon becomes the package path.
206+
The item can be a linked file too, in which case the PackageIcon should match the link
207+
within the project structure.
208+
If the item was Content, we change its packing properties so it doesn't become a
209+
contentFiles item in the package, which would end up in the users' solution.
210+
-->
211+
<Target Name="_InferPackageIcon" Condition="'$(PackageIcon)' != ''">
212+
<ItemGroup>
213+
<None Update="@(None)"
214+
Pack="true"
215+
PackagePath="%(None.Link)"
216+
Condition="%(None.Identity) == '$(PackageIcon)' or %(None.Link) == '$(PackageIcon)'" />
217+
218+
<Content Update="@(Content)"
219+
Pack="true"
220+
BuildAction="None"
221+
PackFolder="None"
222+
PackagePath="%(Content.Link)"
223+
Condition="%(Content.Identity) == '$(PackageIcon)' or %(Content.Link) == '$(PackageIcon)'" />
224+
</ItemGroup>
225+
</Target>
226+
201227
<Target Name="_CollectInferenceCandidates" Inputs="@(PackInference)" Outputs="|%(PackInference.Identity)|">
202228
<PropertyGroup>
203229
<PackExclude>%(PackInference.PackExclude)</PackExclude>
@@ -247,7 +273,7 @@ Copyright (c) .NET Foundation. All rights reserved.
247273

248274
</Target>
249275

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

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

290+
<ItemGroup Label="Icon" Condition="'$(PackageIcon)' != ''">
291+
<_PackageIcon Include="$(PackageIcon)" />
292+
</ItemGroup>
293+
<PropertyGroup Label="Icon" Condition="'$(PackageIcon)' != '' and @(_PackageIcon -> Count()) == '1'">
294+
<PackageIconExists Condition="Exists(@(_PackageIcon -> '%(FullPath)'))"></PackageIconExists>
295+
<PackageIconFilename Condition="'$(PackageIconExists)' == 'true'">@(_PackageIcon -> '%(Filename)')</PackageIconFilename>
296+
<PackageIconExtension Condition="'$(PackageIconExists)' == 'true'">@(_PackageIcon -> '%(Extension)')</PackageIconExtension>
297+
</PropertyGroup>
298+
299+
<!-- Even if all these conditions are false, the user can still explicitly pack the icon file, of course -->
300+
<ItemGroup Label="Icon" Condition="'$(PackageIcon)' != ''">
301+
<_ExistingIcon Include="@(None -> WithMetadataValue('Filename', '$(PackageReadmeFilename)'))" />
302+
303+
<_ExistingReadme Include="@(None -> WithMetadataValue('Filename', '$(PackageReadmeFilename)'))" />
304+
<_ExistingReadme Include="@(Content -> WithMetadataValue('Filename', '$(PackageReadmeFilename)'))" Condition="'@(_ExistingReadme)' == ''" />
305+
<_ExistingReadme Include="$(MSBuildProjectDirectory)\$(PackageReadmeFilename)$(PackageReadmeExtension)" Condition="'@(_ExistingReadme)' == '' and Exists('$(MSBuildProjectDirectory)\$(PackageReadmeFilename)$(PackageReadmeExtension)')" />
306+
307+
<InferenceCandidate Include="@(_ExistingReadme)"
308+
PackagePath="%(Filename)%(Extension)"
309+
Condition="'%(Extension)' == '$(PackageReadmeExtension)'" />
310+
</ItemGroup>
311+
264312
<ItemGroup>
265313
<InferenceCandidate>
266314
<ShouldPack Condition="('%(Pack)' == 'true' or '%(PackagePath)' != '' or '%(PackFolder)' != '' or '%(PackageReference)' != '') and '%(Pack)' != 'false'">true</ShouldPack>

src/NuGetizer.Tests/Builder.NuGetizer.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ public static TargetResult BuildProjects(
6868
}
6969
catch (System.Xml.XmlException)
7070
{
71-
File.WriteAllText(Path.Combine(scenarioDir, file.name), file.contents);
71+
var path = Path.Combine(scenarioDir, file.name);
72+
Directory.CreateDirectory(Path.GetDirectoryName(path));
73+
File.WriteAllText(path, file.contents);
7274
}
7375
}
7476

src/NuGetizer.Tests/InlineProjectTests.cs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,5 +817,142 @@ public void when_validating_package_then_succeeds()
817817

818818
result.AssertSuccess(output);
819819
}
820+
821+
[Fact]
822+
public void when_package_icon_default_then_packs_icon()
823+
{
824+
var result = Builder.BuildProject(
825+
"""
826+
<Project Sdk="Microsoft.NET.Sdk">
827+
<PropertyGroup>
828+
<TargetFramework>netstandard2.0</TargetFramework>
829+
<IsPackable>true</IsPackable>
830+
<PackageIcon>icon.png</PackageIcon>
831+
<EnableDefaultItems>true</EnableDefaultItems>
832+
</PropertyGroup>
833+
</Project>
834+
"""
835+
, output: output,
836+
files: ("icon.png", ""));
837+
838+
result.AssertSuccess(output);
839+
840+
Assert.Contains(result.Items, item => item.Matches(new
841+
{
842+
PackFolder = "Metadata",
843+
Icon = "icon.png",
844+
}));
845+
Assert.Contains(result.Items, item => item.Matches(new
846+
{
847+
PackagePath = "icon.png",
848+
}));
849+
}
850+
851+
[Fact]
852+
public void when_package_icon_relative_folder_default_then_packs_icon()
853+
{
854+
var result = Builder.BuildProject(
855+
"""
856+
<Project Sdk="Microsoft.NET.Sdk">
857+
<PropertyGroup>
858+
<TargetFramework>netstandard2.0</TargetFramework>
859+
<IsPackable>true</IsPackable>
860+
<PackageIcon>assets\icon.png</PackageIcon>
861+
<EnableDefaultItems>true</EnableDefaultItems>
862+
</PropertyGroup>
863+
</Project>
864+
"""
865+
, output: output,
866+
files: ("assets\\icon.png", ""));
867+
868+
result.AssertSuccess(output);
869+
870+
Assert.Contains(result.Items, item => item.Matches(new
871+
{
872+
PackFolder = "Metadata",
873+
Icon = "assets/icon.png",
874+
}));
875+
Assert.Contains(result.Items, item => item.Matches(new
876+
{
877+
PackagePath = "assets/icon.png",
878+
}));
879+
}
880+
881+
[Fact]
882+
public void when_package_icon_content_then_packs_icon_and_not_content()
883+
{
884+
var result = Builder.BuildProject(
885+
"""
886+
<Project Sdk="Microsoft.NET.Sdk">
887+
<PropertyGroup>
888+
<TargetFramework>netstandard2.0</TargetFramework>
889+
<IsPackable>true</IsPackable>
890+
<PackageIcon>icon.png</PackageIcon>
891+
</PropertyGroup>
892+
<ItemGroup>
893+
<Content Include="icon.png" />
894+
</ItemGroup>
895+
</Project>
896+
"""
897+
, output: output,
898+
files: ("icon.png", ""));
899+
900+
result.AssertSuccess(output);
901+
902+
Assert.Contains(result.Items, item => item.Matches(new
903+
{
904+
PackFolder = "Metadata",
905+
Icon = "icon.png",
906+
}));
907+
// The icon that would be in the content folder is not packed as a contentFile
908+
Assert.DoesNotContain(result.Items, item => item.Matches(new
909+
{
910+
PackFolder = "content"
911+
}));
912+
// And it's instead in the root of the package
913+
Assert.Contains(result.Items, item => item.Matches(new
914+
{
915+
PackagePath = "icon.png",
916+
}));
917+
}
918+
919+
[Fact]
920+
public void when_package_icon_linked_content_then_packs_link()
921+
{
922+
var result = Builder.BuildProject(
923+
"""
924+
<Project Sdk="Microsoft.NET.Sdk">
925+
<PropertyGroup>
926+
<TargetFramework>netstandard2.0</TargetFramework>
927+
<IsPackable>true</IsPackable>
928+
<PackageIcon>assets\icon.png</PackageIcon>
929+
</PropertyGroup>
930+
<ItemGroup>
931+
<Content Include="..\icon.png" Link="assets\icon.png" />
932+
</ItemGroup>
933+
</Project>
934+
"""
935+
, output: output,
936+
files: ("..\\icon.png", ""));
937+
938+
result.AssertSuccess(output);
939+
940+
Assert.Contains(result.Items, item => item.Matches(new
941+
{
942+
PackFolder = "Metadata",
943+
Icon = "assets/icon.png",
944+
}));
945+
// The icon that would be in the content folder is not packed as a contentFile
946+
Assert.DoesNotContain(result.Items, item => item.Matches(new
947+
{
948+
PackFolder = "content"
949+
}));
950+
// And it's instead in the root of the package
951+
Assert.Contains(result.Items, item => item.Matches(new
952+
{
953+
PackagePath = "assets/icon.png",
954+
}));
955+
}
956+
820957
}
821958
}

src/NuGetizer.Tests/Utilities/TaskItemExtensions.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public static bool Matches(this ITaskItem item, object metadata)
1414
{
1515
foreach (var prop in metadata.GetType().GetProperties())
1616
{
17-
var actual = item.GetMetadata(prop.Name);
18-
var expected = prop.GetValue(metadata).ToString();
17+
var actual = item.GetMetadata(prop.Name).Replace('\\', '/');
18+
var expected = prop.GetValue(metadata).ToString().Replace('\\', '/');
1919

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

2727
public static Manifest GetManifest(this ITaskItem package)
2828
{
29-
using (var reader = new PackageArchiveReader(package.GetMetadata("FullPath")))
30-
{
31-
return reader.GetManifest();
32-
}
29+
using var reader = new PackageArchiveReader(package.GetMetadata("FullPath"));
30+
return reader.GetManifest();
3331
}
3432
}
3533
}

0 commit comments

Comments
 (0)