From c5364e56e7cc56bf3f9cffcb6bd57550d4bcb8ad Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Wed, 30 Sep 2020 16:35:06 -0300 Subject: [PATCH 1/3] Add extra tests for pack inference. --- src/NuGetizer.Tests/given_packinference.cs | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/NuGetizer.Tests/given_packinference.cs diff --git a/src/NuGetizer.Tests/given_packinference.cs b/src/NuGetizer.Tests/given_packinference.cs new file mode 100644 index 00000000..5e934bbb --- /dev/null +++ b/src/NuGetizer.Tests/given_packinference.cs @@ -0,0 +1,82 @@ +using Microsoft.Build.Execution; +using Xunit; +using Xunit.Abstractions; + +namespace NuGetizer +{ + public class given_packinference + { + ITestOutputHelper output; + + public given_packinference(ITestOutputHelper output) => this.output = output; + + [Fact] + public void when_none_has_PackagePath_then_packs() + { + var result = Builder.BuildProject(@" + + + Library + netstandard2.0 + + + + +", + "GetPackageContents", output); + + result.AssertSuccess(output); + Assert.Contains(result.Items, item => item.Matches(new + { + PackagePath = @"build\netstandard2.0\Library.props", + })); + } + + [Fact] + public void when_PackContent_false_but_content_has_PackagePath_then_packs() + { + var result = Builder.BuildProject(@" + + + Library + netstandard2.0 + false + + + + +", + "GetPackageContents", output); + + result.AssertSuccess(output); + Assert.Contains(result.Items, item => item.Matches(new + { + PackagePath = @"build\netstandard2.0\Library.props", + })); + } + + [Fact] + public void when_PackContent_false_but_content_has_Pack_then_packs() + { + var result = Builder.BuildProject(@" + + + Library + netstandard2.0 + false + + + + +", + "GetPackageContents", output); + + result.AssertSuccess(output); + Assert.Contains(result.Items, item => item.Matches(new + { + Filename = "Library", + Extension = ".props", + })); + } + } +} From e5a723965f32af7f632f037bfd8050ebf40d181a Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Wed, 30 Sep 2020 17:17:30 -0300 Subject: [PATCH 2/3] Added tests for content files metadata in nuspec Content files can have additional metadata that controls how they are included in the referencing project. See https://docs.microsoft.com/en-us/nuget/reference/nuspec#using-the-contentfiles-element-for-content-files. These tests ensure we set the right metadata in the nuspec we generate. Added the nuspec full path as metadata to the output item from CreatePackage if nuspec generation is opted-in, which now allows us to test the final nuspec without generating the full nupkg too :) (leveraging something the dotnet-nugetize tool already uses for the same purpose). --- src/NuGetizer.Tasks/CreatePackage.cs | 2 + src/NuGetizer.Tasks/NuGetizer.Shared.targets | 5 +- src/NuGetizer.Tasks/dotnet-nugetize.targets | 10 +- src/NuGetizer.Tests/Builder.NuGetizer.cs | 8 +- src/NuGetizer.Tests/given_packinference.cs | 173 ++++++++++++++++++- 5 files changed, 189 insertions(+), 9 deletions(-) diff --git a/src/NuGetizer.Tasks/CreatePackage.cs b/src/NuGetizer.Tasks/CreatePackage.cs index b587b697..f81ed0e9 100644 --- a/src/NuGetizer.Tasks/CreatePackage.cs +++ b/src/NuGetizer.Tasks/CreatePackage.cs @@ -51,6 +51,8 @@ public override bool Execute() OutputPackage = new TaskItem(TargetPath); Manifest.CopyMetadataTo(OutputPackage); + if (emitSpec) + OutputPackage.SetMetadata("Nuspec", NuspecFile); return !Log.HasLoggedErrors; } diff --git a/src/NuGetizer.Tasks/NuGetizer.Shared.targets b/src/NuGetizer.Tasks/NuGetizer.Shared.targets index 427bd3e6..e3a7f8f8 100644 --- a/src/NuGetizer.Tasks/NuGetizer.Shared.targets +++ b/src/NuGetizer.Tasks/NuGetizer.Shared.targets @@ -237,7 +237,10 @@ Copyright (c) .NET Foundation. All rights reserved. - + + + diff --git a/src/NuGetizer.Tasks/dotnet-nugetize.targets b/src/NuGetizer.Tasks/dotnet-nugetize.targets index 9cc79f80..a4783f8f 100644 --- a/src/NuGetizer.Tasks/dotnet-nugetize.targets +++ b/src/NuGetizer.Tasks/dotnet-nugetize.targets @@ -14,13 +14,11 @@ Copyright (c) .NET Foundation. All rights reserved. - - <_AbsoluteNuspecFile Condition="$([System.IO.Path]::IsPathRooted($(NuspecFile)))">$(NuspecFile) - <_AbsoluteNuspecFile Condition="'$(_NuspecFile)' == ''">$(MSBuildProjectDirectory.TrimEnd('\'))\$(NuspecFile) - <_AbsoluteNuspecFile>$([System.IO.Path]::GetFullPath('$(_AbsoluteNuspecFile)')) - - + + + + diff --git a/src/NuGetizer.Tests/Builder.NuGetizer.cs b/src/NuGetizer.Tests/Builder.NuGetizer.cs index 172388b8..b4ff5abe 100644 --- a/src/NuGetizer.Tests/Builder.NuGetizer.cs +++ b/src/NuGetizer.Tests/Builder.NuGetizer.cs @@ -25,7 +25,8 @@ public static TargetResult BuildProject( string projectContent = null, string target = "GetPackageContents", ITestOutputHelper output = null, - LoggerVerbosity? verbosity = null) + LoggerVerbosity? verbosity = null, + params (string name, string contents)[] files) { using var sha = new SHA1Managed(); var hash = Base62.Encode(Math.Abs(BitConverter.ToInt64( @@ -40,6 +41,11 @@ public static TargetResult BuildProject( doc.Save(Path.Combine(scenarioDir, "library.csproj")); + foreach (var file in files) + { + File.WriteAllText(Path.Combine(scenarioDir, file.name), file.contents); + } + var openLog = OpenBuildLogAttribute.IsActive; try { diff --git a/src/NuGetizer.Tests/given_packinference.cs b/src/NuGetizer.Tests/given_packinference.cs index 5e934bbb..52efdfe6 100644 --- a/src/NuGetizer.Tests/given_packinference.cs +++ b/src/NuGetizer.Tests/given_packinference.cs @@ -1,4 +1,5 @@ -using Microsoft.Build.Execution; +using System.Linq; +using System.Xml.Linq; using Xunit; using Xunit.Abstractions; @@ -32,6 +33,51 @@ public void when_none_has_PackagePath_then_packs() })); } + [Fact] + public void when_none_has_Kind_then_packs() + { + var result = Builder.BuildProject(@" + + + Library + netstandard2.0 + + + + +", + "GetPackageContents", output); + + result.AssertSuccess(output); + Assert.Contains(result.Items, item => item.Matches(new + { + Filename = "Library", + Extension = ".props", + })); + } + + [Fact] + public void when_none_has_Kind_FrameworkSpecific_then_packs() + { + var result = Builder.BuildProject(@" + + + Library + netstandard2.0 + + + + +", + "GetPackageContents", output); + + result.AssertSuccess(output); + Assert.Contains(result.Items, item => item.Matches(new + { + PackagePath = @"build\netstandard2.0\Library.props", + })); + } + [Fact] public void when_PackContent_false_but_content_has_PackagePath_then_packs() { @@ -78,5 +124,130 @@ public void when_PackContent_false_but_content_has_Pack_then_packs() Extension = ".props", })); } + + [Fact] + public void when_PackContent_false_but_content_has_Kind_then_packs() + { + var result = Builder.BuildProject(@" + + + Library + netstandard2.0 + false + + + + +", + "GetPackageContents", output); + + result.AssertSuccess(output); + Assert.Contains(result.Items, item => item.Matches(new + { + Filename = "Library", + Extension = ".props", + })); + } + + [Fact] + public void when_content_has_buildaction_then_spec_has_attribute() + { + var result = Builder.BuildProject(@" + + + Library + true + false + netstandard2.0 + + + + +", + "Pack", output); + + result.AssertSuccess(output); + + Assert.Single(result.Items); + Assert.NotEmpty(result.Items[0].GetMetadata("Nuspec")); + + var nuspec = XDocument.Load(result.Items[0].GetMetadata("Nuspec")); + + // Nuspec should contain: + // + Assert.Equal("None", nuspec + .Descendants("{http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd}files") + .Select(x => x.Attribute("buildAction")?.Value) + .FirstOrDefault()); + } + + [Fact] + public void when_content_has_copytooutput_then_spec_has_attribute() + { + var result = Builder.BuildProject(@" + + + Library + true + false + netstandard2.0 + + + + +", + "Pack", output); + + result.AssertSuccess(output); + + Assert.Single(result.Items); + Assert.NotEmpty(result.Items[0].GetMetadata("Nuspec")); + + var nuspec = XDocument.Load(result.Items[0].GetMetadata("Nuspec")); + + // Nuspec should contain: + // + Assert.Equal("true", nuspec + .Descendants("{http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd}files") + .Select(x => x.Attribute("copyToOutput")?.Value) + .FirstOrDefault()); + } + + [Fact] + public void when_content_has_copytooutput_flatten_then_spec_has_attributes() + { + var result = Builder.BuildProject(@" + + + Library + true + false + netstandard2.0 + + + + +", + "Pack", output); + + result.AssertSuccess(output); + + Assert.Single(result.Items); + Assert.NotEmpty(result.Items[0].GetMetadata("Nuspec")); + + var nuspec = XDocument.Load(result.Items[0].GetMetadata("Nuspec")); + + // Nuspec should contain: + // + Assert.Equal("true", nuspec + .Descendants("{http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd}files") + .Select(x => x.Attribute("copyToOutput")?.Value) + .FirstOrDefault()); + + Assert.Equal("true", nuspec + .Descendants("{http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd}files") + .Select(x => x.Attribute("flatten")?.Value) + .FirstOrDefault()); + } } } From 0f09cef54a0ef04f153f15dd6d0cf627e036b742 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Wed, 30 Sep 2020 17:42:17 -0300 Subject: [PATCH 3/3] Render content files metadata too This is very useful for contentFiles which might provide buildAction, copyToOutput and Flatten metadata too. --- src/dotnet-nugetize/Program.cs | 40 ++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/dotnet-nugetize/Program.cs b/src/dotnet-nugetize/Program.cs index 90d1dd7b..dcc098a9 100644 --- a/src/dotnet-nugetize/Program.cs +++ b/src/dotnet-nugetize/Program.cs @@ -53,7 +53,7 @@ static int Execute(bool binlog, bool debug) var foundPackage = false; foreach (var metadata in items.Root.Descendants("PackageMetadata") - .Distinct(AnonymousEqualityComparer.Create( + .Distinct(AnonymousComparer.Create( (x, y) => x.Element("PackageId")?.Value == y.Element("PackageId")?.Value, x => x.Element("PackageId")?.Value.GetHashCode() ?? 0))) { @@ -102,12 +102,12 @@ static int Execute(bool binlog, bool debug) .Where(x => x.Element("PackagePath") != null && x.Element("PackageId")?.Value == packageId) - .Select(x => x.Element("PackagePath").Value) - .Distinct() - .OrderBy(x => Path.GetDirectoryName(x)) - .ThenBy(x => x); + //.Select(x => x.Element("PackagePath").Value) + .Distinct(AnonymousComparer.Create(x => x.Element("PackagePath").Value)) + .OrderBy(x => Path.GetDirectoryName(x.Element("PackagePath").Value)) + .ThenBy(x => x.Element("PackagePath").Value); - RenderContents(contents.ToList(), 0, 0, ""); + Render(contents.ToList(), 0, 0, ""); Console.WriteLine(); } @@ -123,12 +123,12 @@ static int Execute(bool binlog, bool debug) return 0; } - static int RenderContents(IList files, int index, int level, string path) + static int Render(IList files, int index, int level, string path) { while (index < files.Count) - { - var file = files[index]; + var element = files[index]; + var file = element.Element("PackagePath").Value; var dir = Path.GetDirectoryName(file); var paths = dir.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); @@ -143,7 +143,7 @@ static int RenderContents(IList files, int index, int level, string path ColorConsole.Write("/".Gray()); ColorConsole.WriteLine(paths[level].Green(), "/".Gray()); - index = RenderContents(files, index, level + 1, string.Join(Path.DirectorySeparatorChar, paths[..(level + 1)])); + index = Render(files, index, level + 1, string.Join(Path.DirectorySeparatorChar, paths[..(level + 1)])); } else { @@ -151,7 +151,20 @@ static int RenderContents(IList files, int index, int level, string path if (level == 0) ColorConsole.Write("/".Gray()); - ColorConsole.WriteLine(Path.GetFileName(file).White()); + var attributes = new List(); + if (element.Element("BuildAction")?.Value is string buildAction) + attributes.Add("buildAction=" + buildAction); + if (element.Element("CopyToOutput")?.Value is string copyToOutput) + attributes.Add("copyToOutput=" + copyToOutput); + if (element.Element("Flatten")?.Value is string flatten) + attributes.Add("flatten=" + flatten); + + ColorConsole.Write(Path.GetFileName(file).White()); + if (attributes.Count > 0) + ColorConsole.Write((" (" + string.Join(',', attributes) + ")").Gray()); + + Console.WriteLine(); + index++; } } @@ -183,10 +196,13 @@ static bool Execute(string program, string arguments, bool debug = false) return proc.ExitCode == 0; } - static class AnonymousEqualityComparer + static class AnonymousComparer { public static IEqualityComparer Create(Func equals, Func getHashCode) => new AnonymousEqualityComparer(equals, getHashCode); + + public static IEqualityComparer Create(Func value) + => new AnonymousEqualityComparer((x, y) => Equals(value(x), value(y)), x => value(x).GetHashCode()); } class AnonymousEqualityComparer : IEqualityComparer