diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsLogic.cs b/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsLogic.cs new file mode 100644 index 00000000000..6bec686a34f --- /dev/null +++ b/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsLogic.cs @@ -0,0 +1,115 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NuGet.Commands; +using NuGet.Common; +using NuGet.Versioning; + +namespace NuGet.Build.Tasks.Pack +{ + public static class GetPackOutputItemsLogic + { + public static void GetOutputFilePaths(IOutputFilePathProvider source, IOutputFilePath output) + { + var packageId = source.PackageId; + var packageVersion = source.PackageVersion; + + if (!string.IsNullOrWhiteSpace(source.NuspecFile)) + { + bool hasVersionInNuspecProperties = false; + bool hasIdInNuspecProperties = false; + if (source.NuspecProperties != null && source.NuspecProperties.Length > 0) + { + PackArgs packArgs = new PackArgs() { Version = packageVersion }; + PackTaskLogic.SetPackArgsPropertiesFromNuspecProperties(packArgs, MSBuildStringUtility.TrimAndExcludeNullOrEmpty(source.NuspecProperties)); + // If the logic depends only on checking for a non-null value, it may incorrectly detect cases where the parsing logic changes the version based on a key other than the "version" key. + if (packArgs.Properties.ContainsKey("version")) + { + packageVersion = packArgs.Version; + hasVersionInNuspecProperties = true; + } + if (packArgs.Properties.TryGetValue("id", out var idTemp)) + { + packageId = idTemp; + hasIdInNuspecProperties = true; + } + } + + var nuspecReader = new NuGet.Packaging.NuspecReader(source.NuspecFile); + if (!hasIdInNuspecProperties) + { + packageId = nuspecReader.GetId(); + } + if (!hasVersionInNuspecProperties) + { + packageVersion = nuspecReader.GetVersion().ToNormalizedString(); + } + } + + if (!NuGetVersion.TryParse(packageVersion, out var versionTemp)) + { + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidPackageVersion, + packageVersion)); + } + NuGetVersion version = versionTemp!; + + var symbolPackageFormat = PackArgs.GetSymbolPackageFormat(MSBuildStringUtility.TrimAndGetNullForEmpty(source.SymbolPackageFormat)); + var nupkgFileName = PackCommandRunner.GetOutputFileName(packageId, version!, isNupkg: true, symbols: false, symbolPackageFormat: symbolPackageFormat, excludeVersion: source.OutputFileNamesWithoutVersion); + var nuspecFileName = PackCommandRunner.GetOutputFileName(packageId, version!, isNupkg: false, symbols: false, symbolPackageFormat: symbolPackageFormat, excludeVersion: source.OutputFileNamesWithoutVersion); + + var outputs = new List(); + + output.OutputNupkgFilePath = Path.Combine(source.PackageOutputPath, nupkgFileName); + output.OutputNuspecFilePath = Path.Combine(source.NuspecOutputPath, nuspecFileName); + + outputs.Add(new TaskItem(output.OutputNupkgFilePath)); + outputs.Add(new TaskItem(output.OutputNuspecFilePath)); + + if (source.IncludeSource || source.IncludeSymbols) + { + var nupkgSymbolsFileName = PackCommandRunner.GetOutputFileName(packageId, version, isNupkg: true, symbols: true, symbolPackageFormat: symbolPackageFormat, excludeVersion: source.OutputFileNamesWithoutVersion); + var nuspecSymbolsFileName = PackCommandRunner.GetOutputFileName(packageId, version, isNupkg: false, symbols: true, symbolPackageFormat: symbolPackageFormat, excludeVersion: source.OutputFileNamesWithoutVersion); + + output.OutputNupkgSymbolsFilePath = Path.Combine(source.PackageOutputPath, nupkgSymbolsFileName); + output.OutputNuspecSymbolsFilePath = Path.Combine(source.NuspecOutputPath, nuspecSymbolsFileName); + + outputs.Add(new TaskItem(output.OutputNupkgSymbolsFilePath)); + outputs.Add(new TaskItem(output.OutputNuspecSymbolsFilePath)); + } + + output.OutputPackItems = outputs.ToArray(); + } + + public static IOutputFilePath GetOutputFilePaths(IOutputFilePathProvider source) + { + var output = new GetPackOutputItemsTask(); + Copy(source, output); + GetOutputFilePaths(source, output); + return output; + } + + public static void Copy(IOutputFilePathProvider source, IOutputFilePathProvider destination) + { + destination.PackageId = source.PackageId; + destination.PackageVersion = source.PackageVersion; + destination.PackageOutputPath = source.PackageOutputPath; + destination.NuspecOutputPath = source.NuspecOutputPath; + destination.NuspecFile = source.NuspecFile; + destination.NuspecProperties = source.NuspecProperties; + destination.IncludeSource = source.IncludeSource; + destination.IncludeSymbols = source.IncludeSymbols; + destination.SymbolPackageFormat = source.SymbolPackageFormat; + destination.OutputFileNamesWithoutVersion = source.OutputFileNamesWithoutVersion; + } + } +} diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsTask.cs b/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsTask.cs index 8a4efe84194..e46a2e0657e 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsTask.cs +++ b/src/NuGet.Core/NuGet.Build.Tasks.Pack/GetPackOutputItemsTask.cs @@ -3,19 +3,11 @@ #nullable disable -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using NuGet.Commands; -using NuGet.Common; -using NuGet.Versioning; namespace NuGet.Build.Tasks.Pack { - public class GetPackOutputItemsTask : Microsoft.Build.Utilities.Task + public class GetPackOutputItemsTask : Microsoft.Build.Utilities.Task, IOutputFilePathProvider, IOutputFilePath { [Required] public string PackageId { get; set; } @@ -29,47 +21,32 @@ public class GetPackOutputItemsTask : Microsoft.Build.Utilities.Task [Required] public string NuspecOutputPath { get; set; } + public string NuspecFile { get; set; } + + public string[] NuspecProperties { get; set; } + public bool IncludeSymbols { get; set; } public bool IncludeSource { get; set; } public string SymbolPackageFormat { get; set; } + public bool OutputFileNamesWithoutVersion { get; set; } + /// /// Output items /// [Output] public ITaskItem[] OutputPackItems { get; set; } + public string OutputNupkgFilePath { get; set; } + public string OutputNuspecFilePath { get; set; } + public string OutputNupkgSymbolsFilePath { get; set; } + public string OutputNuspecSymbolsFilePath { get; set; } + public override bool Execute() { - NuGetVersion version; - if (!NuGetVersion.TryParse(PackageVersion, out version)) - { - throw new ArgumentException(string.Format( - CultureInfo.CurrentCulture, - Strings.InvalidPackageVersion, - PackageVersion)); - } - - var symbolPackageFormat = PackArgs.GetSymbolPackageFormat(MSBuildStringUtility.TrimAndGetNullForEmpty(SymbolPackageFormat)); - var nupkgFileName = PackCommandRunner.GetOutputFileName(PackageId, version, isNupkg: true, symbols: false, symbolPackageFormat: symbolPackageFormat); - var nuspecFileName = PackCommandRunner.GetOutputFileName(PackageId, version, isNupkg: false, symbols: false, symbolPackageFormat: symbolPackageFormat); - - var outputs = new List(); - outputs.Add(new TaskItem(Path.Combine(PackageOutputPath, nupkgFileName))); - outputs.Add(new TaskItem(Path.Combine(NuspecOutputPath, nuspecFileName))); - - if (IncludeSource || IncludeSymbols) - { - var nupkgSymbolsFileName = PackCommandRunner.GetOutputFileName(PackageId, version, isNupkg: true, symbols: true, symbolPackageFormat: symbolPackageFormat); - var nuspecSymbolsFileName = PackCommandRunner.GetOutputFileName(PackageId, version, isNupkg: false, symbols: true, symbolPackageFormat: symbolPackageFormat); - - outputs.Add(new TaskItem(Path.Combine(PackageOutputPath, nupkgSymbolsFileName))); - outputs.Add(new TaskItem(Path.Combine(NuspecOutputPath, nuspecSymbolsFileName))); - } - - OutputPackItems = outputs.ToArray(); + GetPackOutputItemsLogic.GetOutputFilePaths(this, this); return true; } } diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Pack/IOutputFilePath.cs b/src/NuGet.Core/NuGet.Build.Tasks.Pack/IOutputFilePath.cs new file mode 100644 index 00000000000..852b331ca3d --- /dev/null +++ b/src/NuGet.Core/NuGet.Build.Tasks.Pack/IOutputFilePath.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable disable + +using Microsoft.Build.Framework; + +namespace NuGet.Build.Tasks.Pack +{ + public interface IOutputFilePath + { + [Output] + public ITaskItem[] OutputPackItems { get; set; } + + public string OutputNupkgFilePath { get; set; } + public string OutputNuspecFilePath { get; set; } + public string OutputNupkgSymbolsFilePath { get; set; } + public string OutputNuspecSymbolsFilePath { get; set; } + } +} diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Pack/IOutputFilePathProvider.cs b/src/NuGet.Core/NuGet.Build.Tasks.Pack/IOutputFilePathProvider.cs new file mode 100644 index 00000000000..f277c62534a --- /dev/null +++ b/src/NuGet.Core/NuGet.Build.Tasks.Pack/IOutputFilePathProvider.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable disable + +using Microsoft.Build.Framework; + +namespace NuGet.Build.Tasks.Pack +{ + public interface IOutputFilePathProvider + { + [Required] + string PackageId { get; set; } + + [Required] + string PackageVersion { get; set; } + + [Required] + string PackageOutputPath { get; set; } + + [Required] + string NuspecOutputPath { get; set; } + + string NuspecFile { get; set; } + + string[] NuspecProperties { get; set; } + + bool IncludeSource { get; set; } + + bool IncludeSymbols { get; set; } + + string SymbolPackageFormat { get; set; } + + bool OutputFileNamesWithoutVersion { get; set; } + } +} diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTask.cs b/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTask.cs index fc28b03d1ff..43dc1257fb8 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTask.cs +++ b/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTask.cs @@ -16,7 +16,7 @@ namespace NuGet.Build.Tasks.Pack { - public class PackTask : Microsoft.Build.Utilities.Task, IPackTaskRequest + public class PackTask : Microsoft.Build.Utilities.Task, IPackTaskRequest, IOutputFilePathProvider { private readonly IEnvironmentVariableReader _environmentVariableReader; diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTaskLogic.cs b/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTaskLogic.cs index 0c1bf078b0c..2d2609f3b66 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTaskLogic.cs +++ b/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTaskLogic.cs @@ -71,14 +71,7 @@ public PackArgs GetPackArgs(IPackTaskRequest request) if (!string.IsNullOrEmpty(request.NuspecFile)) { - if (request.NuspecProperties != null && request.NuspecProperties.Any()) - { - packArgs.Properties.AddRange(ParsePropertiesAsDictionary(request.NuspecProperties)); - if (packArgs.Properties.TryGetValue("version", out var version)) - { - packArgs.Version = version; - } - } + SetPackArgsPropertiesFromNuspecProperties(packArgs, request.NuspecProperties); } else { @@ -1172,6 +1165,27 @@ private static IDictionary ParsePropertiesAsDictionary(string[] return dictionary; } + internal static void SetPackArgsPropertiesFromNuspecProperties(PackArgs packArgs, string[] nuspecProperties) + { + if (nuspecProperties == null || !nuspecProperties.Any()) + { + return; + } + + packArgs.Properties.AddRange(ParsePropertiesAsDictionary(nuspecProperties)); + if (packArgs.Properties.TryGetValue("version", out var packageVersion)) + { + if (!NuGetVersion.TryParse(packageVersion, out var version)) + { + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidPackageVersion, + packageVersion)); + } + packArgs.Version = version.ToNormalizedString(); + } + } + private HashSet InitOutputExtensions(IEnumerable outputExtensions) { return new HashSet(outputExtensions.Distinct(StringComparer.OrdinalIgnoreCase)); diff --git a/src/NuGet.Core/NuGet.Build.Tasks/NuGet.Build.Tasks.Pack.targets b/src/NuGet.Core/NuGet.Build.Tasks/NuGet.Build.Tasks.Pack.targets index cd86627edcb..06a8064ef88 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks/NuGet.Build.Tasks.Pack.targets +++ b/src/NuGet.Core/NuGet.Build.Tasks/NuGet.Build.Tasks.Pack.targets @@ -108,15 +108,21 @@ Copyright (c) .NET Foundation. All rights reserved. + + + + - + SymbolPackageFormat="$(SymbolPackageFormat)" + OutputFileNamesWithoutVersion="$(OutputFileNamesWithoutVersion)"> diff --git a/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/GetPackOutputItemsTaskTests.cs b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/GetPackOutputItemsTaskTests.cs new file mode 100644 index 00000000000..e501e02a004 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/GetPackOutputItemsTaskTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using NuGet.Test.Utility; +using Xunit; +using Xunit.Abstractions; + +namespace NuGet.Build.Tasks.Pack.Test +{ + public class GetPackOutputItemsTaskTests + { + private readonly ITestOutputHelper _testOutputHelper; + + public static IEnumerable PackageFileNameTestCases => PackageFileNameTestCase.TestCases; + + public GetPackOutputItemsTaskTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + // This unit test verifies that GetPackOutputItemsTask outputs the expected file name. + [Theory] + [MemberData(nameof(PackageFileNameTestCases))] + public void GetPackOutputItemsTaskTests_Execute_CheckPackageFileName(PackageFileNameTestCase testCase) + { + var outputItemTask = new NuGet.Build.Tasks.Pack.GetPackOutputItemsTask(); + outputItemTask.PackageId = testCase.IdProjProp; + outputItemTask.PackageVersion = testCase.VersionProjProp; + outputItemTask.IncludeSymbols = testCase.IncludeSymbols; + outputItemTask.SymbolPackageFormat = PackageFileNameTestsCommon.GetSymbolPackageFormatText(testCase.SymbolPackageFormat); + outputItemTask.OutputFileNamesWithoutVersion = testCase.OutputFileNamesWithoutVersion; + if (!string.IsNullOrWhiteSpace(testCase.VersionNuspecProperties)) + { + outputItemTask.NuspecProperties = new string[] { $"version={testCase.VersionNuspecProperties}" }; + } + + using (var testDirectory = TestDirectory.Create()) + { + outputItemTask.PackageOutputPath = testDirectory.Path; + outputItemTask.NuspecOutputPath = testDirectory.Path; + if (testCase.UseNuspecFile) + { + outputItemTask.NuspecFile = System.IO.Path.Combine(testDirectory.Path, PackageFileNameTestsCommon.FILENAME_NUSPEC_FILE); + } + + PackageFileNameTestsCommon.CreateTestProjectFileAndNuspecFile(testCase, testDirectory); + + Assert.True(outputItemTask.Execute()); + + foreach (string outputNupkgName in testCase.OutputNupkgNames) + { + string[] itemSpecs = outputItemTask.OutputPackItems.Select(item => item.ItemSpec).ToArray(); + var matchCount = PackageFileNameTestsCommon.GetNameMatchFilePathCount(outputNupkgName, itemSpecs); + Assert.True(matchCount == 1, $"{outputNupkgName} is not found in output. [{string.Join(" , ", itemSpecs.Select(_ => System.IO.Path.GetFileName(_)))}]"); + } + } + } + } +} diff --git a/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/NuGet.Build.Tasks.Pack.Test.csproj b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/NuGet.Build.Tasks.Pack.Test.csproj index 91b23e340ff..19f4419a677 100644 --- a/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/NuGet.Build.Tasks.Pack.Test.csproj +++ b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/NuGet.Build.Tasks.Pack.Test.csproj @@ -1,15 +1,20 @@ - + $(TargetFrameworksUnitTest) Unit tests for NuGet.Build.Tasks.Pack. + + $(DefineConstants);TEST_FOR_UNIT + + + diff --git a/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackTaskTests.cs b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackTaskTests.cs index dc9d3392522..538abe23fc4 100644 --- a/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackTaskTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackTaskTests.cs @@ -17,11 +17,19 @@ using NuGet.Packaging; using NuGet.Test.Utility; using Xunit; +using Xunit.Abstractions; namespace NuGet.Build.Tasks.Pack.Test { public class PackTaskTests { + private readonly ITestOutputHelper _testOutputHelper; + + public PackTaskTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + [Fact] public void PackTask_DelegatesToPackLogic() { @@ -435,5 +443,151 @@ protected override IList CreateProperties(Type type, MemberSeriali .ToList(); } } + + + public static IEnumerable PackageFileNameTestCases => PackageFileNameTestCase.TestCases; + + // This unit test verifies that GetPackOutputItemsTask outputs the expected file name. + [Theory] + [MemberData(nameof(PackageFileNameTestCases))] + public void PackTask_Execute_CheckPackageFileName(PackageFileNameTestCase testCase) + { + using (var testDirectory = TestDirectory.Create()) + { + string outputDir = System.IO.Path.Combine(testDirectory, "output"); + System.IO.Directory.CreateDirectory(outputDir); + + string objDir = System.IO.Path.Combine(testDirectory, "obj"); + System.IO.Directory.CreateDirectory(objDir); + + if (testCase.IncludeSymbols) + { + //needs .pdb file (see PackCommandRunner.BuildSymbolsPackage) + + string binDir = System.IO.Path.Combine(testDirectory, "bin"); + System.IO.Directory.CreateDirectory(binDir); + System.IO.File.WriteAllBytes(System.IO.Path.Combine(binDir, "dummy.pdb"), new byte[0]); + } + + // Create .csproj and .nuspec + PackageFileNameTestsCommon.CreateTestProjectFileAndNuspecFile(testCase, testDirectory); + + // Create project.assets.json + var path = string.Join(".", typeof(PackTaskLogicTests).Namespace, "compiler.resources", "json.assets.project"); + using (var mstream = GetType().Assembly.GetManifestResourceStream(path)) + { + Assert.NotNull(mstream); + + using (var reader = new StreamReader(mstream)) + { + var contents = reader.ReadToEnd(); + File.WriteAllText(Path.Combine(objDir, "project.assets.json"), contents); + } + } + + // dummy BuildEngine + System.Text.StringBuilder logError = new System.Text.StringBuilder(); + System.Text.StringBuilder logWarning = new System.Text.StringBuilder(); + var mockEngine = new Moq.Mock(); + mockEngine.Setup(x => x.LogErrorEvent(Moq.It.IsAny())) + .Callback((e) => { logError.Append(e.Message); }); + mockEngine.Setup(x => x.LogWarningEvent(Moq.It.IsAny())) + .Callback((e) => { logWarning.Append(e.Message); }); + + var packTask = new NuGet.Build.Tasks.Pack.PackTask() + { + PackItem = FileTaskItem.FromPath(System.IO.Path.Combine(testDirectory, PackageFileNameTestsCommon.FILENAME_PROJECT_FILE)), + RestoreOutputPath = System.IO.Path.Combine(testDirectory, "obj"), + + Authors = ["Nuget Team"], + Description = "description", + BuildOutputInPackage = new Microsoft.Build.Framework.ITaskItem[0], + + ContinuePackingAfterGeneratingNuspec = true, + + + NuspecBasePath = testDirectory, + + // Dummy for Logger (see Microsoft.Build.Utilities.TaskLoggingHelper.LogWarning) + BuildEngine = mockEngine.Object + }; + + var filePathsSource = (NuGet.Build.Tasks.Pack.IOutputFilePathProvider)packTask; + filePathsSource.PackageId = testCase.IdProjProp; //Required + filePathsSource.PackageVersion = testCase.VersionProjProp;//Required + filePathsSource.PackageOutputPath = outputDir;//Required + filePathsSource.NuspecOutputPath = outputDir;//Required + + filePathsSource.NuspecFile = (testCase.UseNuspecFile ? System.IO.Path.Combine(testDirectory, PackageFileNameTestsCommon.FILENAME_NUSPEC_FILE) : null); + filePathsSource.NuspecProperties = (!string.IsNullOrWhiteSpace(testCase.VersionNuspecProperties) ? (new string[] { "version=" + testCase.VersionNuspecProperties }) : null); + filePathsSource.IncludeSymbols = testCase.IncludeSymbols; + filePathsSource.SymbolPackageFormat = PackageFileNameTestsCommon.GetSymbolPackageFormatText(testCase.SymbolPackageFormat); + filePathsSource.OutputFileNamesWithoutVersion = testCase.OutputFileNamesWithoutVersion; + + // Execute() + Assert.True(packTask.Execute(), "PackTask.Execute Fail\r\n" + logError.ToString()); + + _testOutputHelper.WriteLine(logWarning.ToString()); + _testOutputHelper.WriteLine(logError.ToString()); + + // get generated files + string[] outputExtensions = PackageFileNameTestsCommon.GetOutputExtensions(testCase.IncludeSymbols, testCase.SymbolPackageFormat); + var nupkgGeneratedFiles = outputExtensions + .SelectMany(outputExtension => Directory.GetFiles(testDirectory, $"*{outputExtension}", SearchOption.AllDirectories)) + .Where(line => !line.StartsWith(objDir)) + .Distinct().ToArray(); + + // compare generated and testCase + foreach (string outputNupkgName in testCase.OutputNupkgNames) + { + var matchCountInFileSystem = PackageFileNameTestsCommon.GetNameMatchFilePathCount(outputNupkgName, nupkgGeneratedFiles); + Assert.True(matchCountInFileSystem == 1, $"{outputNupkgName} is not found in filesystem. [{string.Join(" , ", nupkgGeneratedFiles.Select(_ => System.IO.Path.GetFileName(_)))}]"); + } + + // compare generated and GetPackOutputItemsTask + var outputPaths = NuGet.Build.Tasks.Pack.GetPackOutputItemsLogic.GetOutputFilePaths(filePathsSource).OutputPackItems; + foreach (var outputPath in outputPaths) + { + if (outputPath.GetMetadata("Extension") == ".nuspec" && testCase.UseNuspecFile) + { + continue; + } + Assert.True(System.IO.File.Exists(outputPath.GetMetadata("FullPath")), $"{outputPath} is not found in filesystem"); + } + } + } + + class FileTaskItem : Microsoft.Build.Framework.ITaskItem + { + public static FileTaskItem FromPath(string path) + { + var fullpath = System.IO.Path.GetFullPath(path); + + FileTaskItem item = new FileTaskItem(fullpath); + item.SetMetadata("RootDir", System.IO.Path.GetDirectoryName(fullpath) ?? ""); + item.SetMetadata("Directory", System.IO.Path.GetDirectoryName(fullpath) ?? ""); + item.SetMetadata("FileName", System.IO.Path.GetFileName(fullpath)); + item.SetMetadata("Extension", System.IO.Path.GetExtension(fullpath)); + item.SetMetadata("FullPath", fullpath); + return item; + } + + + private Dictionary _dic = new Dictionary(); + + public FileTaskItem(string itemSpec) + { + ItemSpec = itemSpec; + } + + public string GetMetadata(string metadataName) => _dic[metadataName]; + public void SetMetadata(string metadataName, string metadataValue) => _dic[metadataName] = metadataValue; + public void RemoveMetadata(string metadataName) => _dic.Remove(metadataName); + public void CopyMetadataTo(ITaskItem destinationItem) => throw new NotSupportedException(); + public System.Collections.IDictionary CloneCustomMetadata() => throw new NotSupportedException(); + public string ItemSpec { get; set; } = ""; + public System.Collections.ICollection MetadataNames => _dic.Keys; + public int MetadataCount => _dic.Count; + } } } diff --git a/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackageFileNameTestCase.cs b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackageFileNameTestCase.cs new file mode 100644 index 00000000000..7584d6f6d82 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Build.Tasks.Pack.Test/PackageFileNameTestCase.cs @@ -0,0 +1,262 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System.IO; +using System.Linq; +using Xunit.Abstractions; + +#if TEST_FOR_UNIT +namespace NuGet.Build.Tasks.Pack.Test +#elif IS_DESKTOP +namespace Msbuild.Integration.Test +#elif IS_CORECLR +namespace Dotnet.Integration.Test +#endif +{ + // NuGet.Build.Tasks.Pack.Test + /// + /// + /// + /// + /// + /// + public class PackageFileNameTestCase : IXunitSerializable + { + public static System.Collections.Generic.IEnumerable TestCases + { + get + { + var cases = new PackageFileNameTestCase[] + { + + //// without nuspec input + new PackageFileNameTestCase("000",["proj.1.9.0.nupkg" ], "proj", "nusp", "1.9", "", "", false), + new PackageFileNameTestCase("001",["proj.2.0.0.nupkg" ], "proj", "nusp", "2.0.0.0", " ", "4.0.0.0", false), + new PackageFileNameTestCase("002",["proj.2.0.0.1.nupkg" ], "proj", "nusp", "2.0.0.1", " ", "4.0.0.1", false), + new PackageFileNameTestCase("003",["proj.2.0.0.2.nupkg" ], "proj", "nusp", "2.0.0.2", "3.0.0.2", "4.0.0.2", false), + new PackageFileNameTestCase("004",["proj.2.0.0.3-preview.nupkg"], "proj", "nusp", "2.0.0.3-preview", "3.0.0.2", "4.0.0.2", false), + new PackageFileNameTestCase("005",["proj.2.0.0.4-release.nupkg"], "proj", "$meta_id$", "2.0.0.4-release", "3.0.0.2", "$meta_version$", false), + new PackageFileNameTestCase("100",["proj.nupkg" ], "proj", "nusp", "1.9", "", "", false, outputFileNamesWithoutVersion:true), + new PackageFileNameTestCase("104",["proj.nupkg" ], "proj", "nusp", "2.0.0.3-preview", "3.0.0.2", "4.0.0.2", false,outputFileNamesWithoutVersion:true), + + // with nuspec input + new PackageFileNameTestCase("010",["nusp.4.0.0.nupkg" ], "proj", "nusp", "2.0.0.0", " ", "4.0.0.0", true), + new PackageFileNameTestCase("011",["nusp.4.0.0.3.nupkg" ], "proj", "nusp", "2.0.0.3", " ", "4.0.0.3", true), + new PackageFileNameTestCase("012",["nusp.3.0.0.4.nupkg" ], "proj", "nusp", "2.0.0.4", "3.0.0.4", "4.0.0.4", true), + new PackageFileNameTestCase("013",["nusp.5.0.0-preview.nupkg" ], "proj", "nusp", "2.0.0.0", " ", "5.0.0.0-preview", true), + new PackageFileNameTestCase("014",["nusp.5.0.0.2-preview.nupkg"], "proj", "nusp", "2.0.0.0", " ", "5.0.0.2-preview", true), + new PackageFileNameTestCase("015",["nusp.6.0.0-beta.nupkg" ], "proj", "nusp", "2.0.0.0", "6-beta ", "5.0.0.3-preview", true), + new PackageFileNameTestCase("110",["nusp.nupkg" ], "proj", "nusp", "2.0.0.0", " ", "4.0.0.0", true, outputFileNamesWithoutVersion:true), + new PackageFileNameTestCase("115",["nusp.nupkg" ], "proj", "nusp", "2.0.0.0", "6-beta ", "5.0.0.3-preview", true, outputFileNamesWithoutVersion:true), + + // has symbol + new PackageFileNameTestCase("020",["proj.2.1.0.snupkg"], "proj", "nusp", "2.1.0.0", "7.1.1", "5.0.0.3-preview", false, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.Snupkg ), + new PackageFileNameTestCase("021",["nusp.7.1.2.snupkg"], "proj", "nusp", "2.0.0.0", "7.1.2", "5.0.0.4-preview", true, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.Snupkg ), + new PackageFileNameTestCase("120",["proj.snupkg" ], "proj", "nusp", "2.1.0.0", "7.1.1", "5.0.0.3-preview", false, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.Snupkg ,outputFileNamesWithoutVersion:true), + new PackageFileNameTestCase("121",["nusp.snupkg" ], "proj", "nusp", "2.0.0.0", "7.1.2", "5.0.0.4-preview", true, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.Snupkg ,outputFileNamesWithoutVersion:true), + + new PackageFileNameTestCase("022",["proj.2.2.0.nupkg", "proj.2.2.0.symbols.nupkg"], "proj", "nusp", "2.2.0.0", "7.1.1", "5.0.0.3-preview", false, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.SymbolsNupkg ), + new PackageFileNameTestCase("023",["nusp.7.2.2.nupkg", "nusp.7.2.2.symbols.nupkg"], "proj", "nusp", "2.0.0.0", "7.2.2", "5.0.0.4-preview", true, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.SymbolsNupkg ), + new PackageFileNameTestCase("122",["proj.nupkg", "proj.symbols.nupkg" ], "proj", "nusp", "2.2.0.0", "7.1.1", "5.0.0.3-preview", false, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.SymbolsNupkg, outputFileNamesWithoutVersion:true), + new PackageFileNameTestCase("123",["nusp.nupkg", "nusp.symbols.nupkg" ], "proj", "nusp", "2.0.0.0", "7.2.2", "5.0.0.4-preview", true, includeSymbols: true, symbolPackageFormat: NuGet.Commands.SymbolPackageFormat.SymbolsNupkg, outputFileNamesWithoutVersion:true), + + }; + + + return (object[][])cases.Select((c, i) => new object[] { c }).ToArray(); + } + } + + public PackageFileNameTestCase + (string testNumber + , string[] outputNupkgNames + , string idProjProp + , string idNuspecMeta + , string versionProjProp + , string versionNuspecProperties + , string versionNuspecMeta + , bool useNuspecFile + , bool outputFileNamesWithoutVersion = false + , bool includeSymbols = false + , NuGet.Commands.SymbolPackageFormat symbolPackageFormat = NuGet.Commands.SymbolPackageFormat.Snupkg) + { + CaseNumber = testNumber; + OutputNupkgNames = outputNupkgNames; + IdProjProp = idProjProp; + IdNuspecMeta = idNuspecMeta; + VersionProjProp = versionProjProp; + VersionNuspecProperties = versionNuspecProperties; + VersionNuspecMeta = versionNuspecMeta; + UseNuspecFile = useNuspecFile; + OutputFileNamesWithoutVersion = outputFileNamesWithoutVersion; + IncludeSymbols = includeSymbols; + SymbolPackageFormat = symbolPackageFormat; + } + + public string CaseNumber { get; set; } = string.Empty; + public string[] OutputNupkgNames { get; set; } = System.Array.Empty(); + public string IdProjProp { get; set; } = string.Empty; + public string IdNuspecMeta { get; set; } = string.Empty; + public string VersionProjProp { get; set; } = string.Empty; + public string VersionNuspecProperties { get; set; } = string.Empty; + public string VersionNuspecMeta { get; set; } = string.Empty; + public bool UseNuspecFile { get; set; } + public bool OutputFileNamesWithoutVersion { get; set; } + public bool IncludeSymbols { get; set; } + public NuGet.Commands.SymbolPackageFormat SymbolPackageFormat { get; set; } = NuGet.Commands.SymbolPackageFormat.Snupkg; + + #region IXunitSerializable + + [System.Obsolete] + public PackageFileNameTestCase() : this("", [], "", "", "", "", "", false) { } + + void IXunitSerializable.Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(CaseNumber), CaseNumber); + info.AddValue(nameof(OutputNupkgNames), OutputNupkgNames); + info.AddValue(nameof(IdProjProp), IdProjProp); + info.AddValue(nameof(IdNuspecMeta), IdNuspecMeta); + info.AddValue(nameof(VersionProjProp), VersionProjProp); + info.AddValue(nameof(VersionNuspecProperties), VersionNuspecProperties); + info.AddValue(nameof(VersionNuspecMeta), VersionNuspecMeta); + info.AddValue(nameof(UseNuspecFile), UseNuspecFile); + info.AddValue(nameof(OutputFileNamesWithoutVersion), OutputFileNamesWithoutVersion); + info.AddValue(nameof(IncludeSymbols), IncludeSymbols); + info.AddValue(nameof(SymbolPackageFormat), SymbolPackageFormat); + } + void IXunitSerializable.Deserialize(IXunitSerializationInfo info) + { + CaseNumber = (string)info.GetValue(nameof(CaseNumber), typeof(string)); + OutputNupkgNames = (string[])info.GetValue(nameof(OutputNupkgNames), typeof(string[])); + IdProjProp = (string)info.GetValue(nameof(IdProjProp), typeof(string)); + IdNuspecMeta = (string)info.GetValue(nameof(IdNuspecMeta), typeof(string)); + VersionProjProp = (string)info.GetValue(nameof(VersionProjProp), typeof(string)); + VersionNuspecProperties = (string)info.GetValue(nameof(VersionNuspecProperties), typeof(string)); + VersionNuspecMeta = (string)info.GetValue(nameof(VersionNuspecMeta), typeof(string)); + UseNuspecFile = (bool)info.GetValue(nameof(UseNuspecFile), typeof(bool)); + OutputFileNamesWithoutVersion = (bool)info.GetValue(nameof(OutputFileNamesWithoutVersion), typeof(bool)); + IncludeSymbols = (bool)info.GetValue(nameof(IncludeSymbols), typeof(bool)); + SymbolPackageFormat = (NuGet.Commands.SymbolPackageFormat)info.GetValue(nameof(SymbolPackageFormat), typeof(NuGet.Commands.SymbolPackageFormat)); + } + #endregion + } + + internal static class PackageFileNameTestsCommon + { + public const string FILENAME_PROJECT_FILE = "test.csproj"; + public const string FILENAME_NUSPEC_FILE = "test.nuspec"; + public const string FILENAME_GETOUTPUTITEMSTASK_OUTPUTPACKITEMS_TEST = "_OutputPackItems.txt"; + public static void CreateTestProjectFileAndNuspecFile + (PackageFileNameTestCase testCase + , string testDirectory) + { + CreateTestProjectFileAndNuspecFile(testCase, testDirectory, null, null, "netstandard2.0"); + } + + public static void CreateTestProjectFileAndNuspecFile + (PackageFileNameTestCase testCase + , string testDirectory + , string? pathDllFile + , string? pathTargetsFile + , string testFrameworkMoniker) + { + + var csprojPath = Path.Combine(testDirectory, FILENAME_PROJECT_FILE); + var nuspecPath = Path.Combine(testDirectory, FILENAME_NUSPEC_FILE); + + var csprojContent = $""" + + + + {pathDllFile} + + + + {testFrameworkMoniker} + NU5100;NU5119;CS2008 + + + + true + + true + false + True + + {testCase.IdProjProp} + {testCase.VersionProjProp} + tagA;tagB + + {FILENAME_NUSPEC_FILE} + version={testCase.VersionNuspecProperties} + + {testCase.IncludeSymbols} + {GetSymbolPackageFormatText(testCase.SymbolPackageFormat)} + + {testCase.OutputFileNamesWithoutVersion} + + + + + + + + + +"""; + + var nuspecContent = $""" + + + + {testCase.IdNuspecMeta} + {testCase.VersionNuspecMeta?.Trim()} + Unit Test + Sample Description + en-US + + +"""; + + File.WriteAllText(csprojPath, csprojContent, System.Text.Encoding.Unicode); + if (testCase.UseNuspecFile) + { + File.WriteAllText(nuspecPath, nuspecContent, new System.Text.UTF8Encoding(true)); + } + } + + public static string GetSymbolPackageFormatText(NuGet.Commands.SymbolPackageFormat symbolPackageFormat) + { + switch (symbolPackageFormat) + { + case NuGet.Commands.SymbolPackageFormat.Snupkg: return "snupkg"; + case NuGet.Commands.SymbolPackageFormat.SymbolsNupkg: return "symbols.nupkg"; + default: throw new System.ArgumentOutOfRangeException(); + } + } + + public static string[] GetOutputExtensions(bool includeSymbols, NuGet.Commands.SymbolPackageFormat symbolPackageFormat) + { + if (includeSymbols) + { + switch (symbolPackageFormat) + { + case NuGet.Commands.SymbolPackageFormat.Snupkg: return new string[] { ".snupkg" }; + case NuGet.Commands.SymbolPackageFormat.SymbolsNupkg: return new string[] { ".nupkg", ".symbols.nupkg" }; + default: throw new System.ArgumentOutOfRangeException(); + } + } + else + { + return new string[] { ".nupkg" }; + } + } + + public static int GetNameMatchFilePathCount(string fileName, System.Collections.Generic.IEnumerable fullpaths) + { + return fullpaths.Count(file => string.Equals(fileName, System.IO.Path.GetFileName(file), System.StringComparison.OrdinalIgnoreCase)); + } + } +}