Skip to content

Commit

Permalink
Add ability to create a NuGet package feed
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffkl committed Aug 26, 2021
1 parent 15b173f commit 341c8d7
Show file tree
Hide file tree
Showing 33 changed files with 1,583 additions and 76 deletions.
8 changes: 2 additions & 6 deletions Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@
</ItemGroup>

<ItemGroup Condition=" '$(EnableStyleCopAnalyzers)' != 'false' ">
<Compile Include="..\Shared\GlobalSuppressions.cs">
<Link>Shared\GlobalSuppressions.cs</Link>
</Compile>
<AdditionalFiles Include="..\Shared\stylecop.json">
<Link>Shared\stylecop.json</Link>
</AdditionalFiles>
<Compile Include="..\Shared\GlobalSuppressions.cs" />
<AdditionalFiles Include="..\Shared\stylecop.json" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Jeff Kluge. All rights reserved.
//
// Licensed under the MIT license.

using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using Shouldly;
using System.IO;
using Xunit;

namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests.PackageFeedTests
{
public class PackageFeedFileTests : PackageFeedTestBase
{
[Fact]
public void ContentFileTextCustom()
{
const string language = "cs";

PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.ContentFileText("file.cs", "A1CF42B9F20B4155B6B70753784615B5", FrameworkConstants.CommonFrameworks.NetStandard20, BuildAction.Compile, copyToOutput: false, flatten: true, language)
.Save();

using PackageArchiveReader packageArchiveReader = GetPackageArchiveReader(packageA);

VerifyContentFile(
packageArchiveReader,
"file.cs",
"A1CF42B9F20B4155B6B70753784615B5",
FrameworkConstants.CommonFrameworks.NetStandard20,
expectedBuildAction: BuildAction.Compile.Value,
expectedCopyToOutput: false,
expectedFlatten: true,
expectedLanguage: language);
}

[Fact]
public void ContentFileTextDefault()
{
PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.ContentFileText("file.txt", "F1BE6E0E408141459C9728FBE0CD5751", FrameworkConstants.CommonFrameworks.Net45)
.Save();

using PackageArchiveReader packageArchiveReader = GetPackageArchiveReader(packageA);

VerifyContentFile(
packageArchiveReader,
"file.txt",
"F1BE6E0E408141459C9728FBE0CD5751",
FrameworkConstants.CommonFrameworks.Net45,
expectedBuildAction: BuildAction.Content.Value,
expectedCopyToOutput: true);
}

[Fact]
public void FileText()
{
PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.FileText(@"something\nothing.txt", "607779BADE3645F8A288543213BFE948")
.Save();

using PackageArchiveReader packageArchiveReader = GetPackageArchiveReader(packageA);

GetFileContents(packageArchiveReader, "something/nothing.txt").ShouldBe("607779BADE3645F8A288543213BFE948");
}

[Fact]
public void FileTextThrowsIfNoPackageAdded()
{
ShouldThrowExceptionIfNoPackageAdded(() =>
{
PackageFeed.Create(FeedRootPath)
.FileText("something.txt", string.Empty);
});
}

private void VerifyContentFile(PackageArchiveReader packageArchiveReader, string relativePath, string expectedContents, NuGetFramework expectedTargetFramework, string expectedExclude = null, string expectedBuildAction = null, bool expectedCopyToOutput = false, bool expectedFlatten = false, string expectedLanguage = "any")
{
GetFileContents(packageArchiveReader, $"contentFiles/{expectedLanguage}/{expectedTargetFramework.GetShortFolderName()}/{relativePath}").ShouldBe(expectedContents);

ContentFilesEntry file = packageArchiveReader.NuspecReader.GetContentFiles().ShouldHaveSingleItem();

file.BuildAction.ShouldBe(expectedBuildAction);

if (expectedCopyToOutput)
{
file.CopyToOutput.ShouldNotBeNull().ShouldBeTrue();
}
else
{
file.CopyToOutput.ShouldNotBeNull().ShouldBeFalse();
}

file.Include.ShouldBe(Path.Combine(expectedLanguage, expectedTargetFramework.GetShortFolderName(), relativePath));

if (expectedExclude == null)
{
file.Exclude.ShouldBeNull();
}
else
{
file.Exclude.ShouldBe(expectedExclude);
}

if (expectedFlatten)
{
file.Flatten.ShouldNotBeNull().ShouldBeTrue();
}
else
{
file.Flatten.ShouldNotBeNull().ShouldBeFalse();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) Jeff Kluge. All rights reserved.
//
// Licensed under the MIT license.

using NuGet.Frameworks;
using NuGet.Packaging;
using Shouldly;
using System.Reflection;
using Xunit;

namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests.PackageFeedTests
{
public class PackageFeedLibraryTests : PackageFeedTestBase
{
[Fact]
public void LibraryCustom()
{
PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.Library("net45", "CustomFile.dll", "Custom.Namespace", "CustomClass", "2.3.4.5")
.Save();

ValidateAssembly(packageA, @"lib/net45/CustomFile.dll", "CustomFile, Version=2.3.4.5, Culture=neutral, PublicKeyToken=null", "Custom.Namespace.CustomClass");
}

[Fact]
public void LibraryDefault()
{
PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.Library(FrameworkConstants.CommonFrameworks.Net45)
.Save();

ValidateAssembly(packageA, @"lib/net45/PackageA.dll", "PackageA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "PackageA.PackageA_Class");
}

[Fact]
public void LibraryThrowsIfNoPackageAdded()
{
ShouldThrowExceptionIfNoPackageAdded(() =>
{
PackageFeed.Create(FeedRootPath)
.Library(FrameworkConstants.CommonFrameworks.Net45);
});
}

[Fact]
public void MultipleTargetFrameworks()
{
NuGetFramework[] targetFrameworks =
{
FrameworkConstants.CommonFrameworks.Net45,
FrameworkConstants.CommonFrameworks.Net46,
};

PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.ForEach(
targetFrameworks,
(targetFramework, feed) =>
{
feed.Library(targetFramework)
.ReferenceAssembly(targetFramework);
})
.Save();

foreach (NuGetFramework targetFramework in targetFrameworks)
{
ValidateAssembly(packageA, $@"lib/{targetFramework.GetShortFolderName()}/PackageA.dll", "PackageA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "PackageA.PackageA_Class");
ValidateAssembly(packageA, $@"ref/{targetFramework.GetShortFolderName()}/PackageA.dll", "PackageA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "PackageA.PackageA_Class");
}
}

[Fact]
public void ReferenceAssemblyCustom()
{
PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.ReferenceAssembly("net45", "CustomFile.dll", "Custom.Namespace", "CustomClass", "2.3.4.5")
.Save();

ValidateAssembly(packageA, @"ref/net45/CustomFile.dll", "CustomFile, Version=2.3.4.5, Culture=neutral, PublicKeyToken=null", "Custom.Namespace.CustomClass");
}

[Fact]
public void ReferenceAssemblyDefault()
{
PackageFeed.Create(FeedRootPath)
.Package("PackageA", "1.0.0", out Package packageA)
.ReferenceAssembly(FrameworkConstants.CommonFrameworks.Net45)
.Save();

ValidateAssembly(packageA, @"ref/net45/PackageA.dll", "PackageA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "PackageA.PackageA_Class");
}

[Fact]
public void ReferenceAssemblyThrowsIfNoPackageAdded()
{
ShouldThrowExceptionIfNoPackageAdded(() =>
{
PackageFeed.Create(FeedRootPath)
.ReferenceAssembly(FrameworkConstants.CommonFrameworks.Net45);
});
}

private void ValidateAssembly(Package package, string filePath, string expectedAssemblyFullName, string expectedTypeFullName)
{
PackageArchiveReader packageArchiveReader = GetPackageArchiveReader(package);

Assembly assembly = LoadAssembly(packageArchiveReader, filePath);

assembly.FullName.ShouldBe(expectedAssemblyFullName);

assembly.GetTypes().ShouldHaveSingleItem().FullName.ShouldBe(expectedTypeFullName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Jeff Kluge. All rights reserved.
//
// Licensed under the MIT license.

using Shouldly;
using Xunit;

namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests.PackageFeedTests
{
public class PackageFeedTemplatesTest : PackageFeedTestBase
{
[Fact]
public void SinglePackageTemplate()
{
PackageFeed.Templates.SinglePackage(FeedRootPath, out Package package);

package.Id.ShouldBe("SomePackage");
package.Version.OriginalVersion.ShouldBe("1.0.0");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Jeff Kluge. All rights reserved.
//
// Licensed under the MIT license.

using NuGet.Packaging;
using Shouldly;
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;

namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests.PackageFeedTests
{
public abstract class PackageFeedTestBase : TestBase
{
protected PackageFeedTestBase()
{
FeedRootPath = Directory.CreateDirectory(Path.Combine(TestRootPath, "Feed"));
}

/// <summary>
/// Gets a <see cref="DirectoryInfo" /> of the root of the package feed.
/// </summary>
protected DirectoryInfo FeedRootPath { get; }

protected byte[] GetFileBytes(PackageArchiveReader packageArchiveReader, string filePath)
{
return ReadFile(packageArchiveReader, filePath, (stream, archiveEntry) =>
{
using BinaryReader binaryReader = new BinaryReader(stream);

return binaryReader.ReadBytes((int)archiveEntry.Length);
});
}

protected string GetFileContents(PackageArchiveReader packageArchiveReader, string filePath)
{
return ReadFile(packageArchiveReader, filePath, (stream, _) =>
{
using TextReader reader = new StreamReader(stream);

return reader.ReadToEnd();
});
}

protected PackageArchiveReader GetPackageArchiveReader(Package package)
{
return new PackageArchiveReader(
new FileStream(package.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096),
leaveStreamOpen: false);
}

protected Assembly LoadAssembly(PackageArchiveReader packageArchiveReader, string filePath)
{
byte[] bytes = GetFileBytes(packageArchiveReader, filePath);

return Assembly.Load(bytes);
}

protected void ShouldThrowExceptionIfNoPackageAdded(Action action)
{
InvalidOperationException exception = Should.Throw<InvalidOperationException>(action);

exception.Message.ShouldBe(Strings.ErrorWhenAddingAnythingBeforePackage);
}

private T ReadFile<T>(PackageArchiveReader packageArchiveReader, string filePath, Func<Stream, ZipArchiveEntry, T> streamFunc)
{
packageArchiveReader.GetFiles().ShouldContain(filePath);

ZipArchiveEntry archiveEntry = packageArchiveReader.GetEntry(filePath);

using Stream fileStream = archiveEntry.Open();

return streamFunc(fileStream, archiveEntry);
}
}
}
Loading

0 comments on commit 341c8d7

Please sign in to comment.