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 9945525 commit 8744246
Show file tree
Hide file tree
Showing 34 changed files with 1,614 additions and 80 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>
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,16 @@ And the resulting project would look like this:
</Project>
```

# Package Repositories
NuGet and MSBuild are very tightly coupled and a lot of times you need packages available when building projects.
# Package Repositories and Feeds
NuGet and MSBuild are very tightly coupled and a lot of times you need packages available when building projects. This API offers two solutions:

## Example
1. Package repository - This allows you to create a repository of restored packages as if NuGet has already installed them.
2. Package feed - This allows you to create a file-based package feed of actual `.nupkg` files.

## Package Repository
Create a package repository if you want to generate packages as if they've already been installed. If you want to create actual `.nupkg` packages, see [Package Feed]

### Example

Create a package repository with a package that supports two target frameworks:

Expand Down Expand Up @@ -190,4 +196,25 @@ using(PackageRepository.Create(rootPath)
}
```

The result would be a project that references the `MyPackage` package and would restore and build accordingly.
The result would be a project that references the `MyPackage` package and would restore and build accordingly.

## Package Feed
Create a package feed if you want to generate `.nupkg` packages that can be installed by NuGet. If you want to create a repository of packages as if they've already been installed, see [Package Repository].

### Example

Create a package feed with a package that supports two target frameworks:

```C#
PackageFeed.Create(rootPath)
.Package("MyPackage", "1.2.3", out Package package)
.Library("net472")
.Library("netstandard2.0"))
.Save();

ProjectCreator projectCreator = ProjectCreator.Create()
.ItemPackageReference(package)

```

The resulting package would have a `lib\net472\MyPackage.dll` and `lib\netstandard2.0\MyPackage.dll` class library. This allows you to restore and build projects that consume the packages
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");
}
}
}
Loading

0 comments on commit 8744246

Please sign in to comment.