Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to create a NuGet package feed #114

Merged
merged 1 commit into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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