Skip to content

Commit

Permalink
Merge pull request #33 from kzu/dev
Browse files Browse the repository at this point in the history
Dev > Main
  • Loading branch information
kzu authored Nov 25, 2020
2 parents da5d096 + d95ef23 commit 75e514c
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 38 deletions.
60 changes: 57 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ Simple, flexible, intuitive and powerful NuGet packaging.
[![GitHub](https://img.shields.io/badge/-source-181717.svg?logo=GitHub)](https://github.com/kzu/stunts)

[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.io/vpre/nugetizer/main&label=nuget.ci&color=brightgreen)](https://pkg.kzu.io/index.json)
[![GH CI Status](https://github.com/kzu/nugetizer/workflows/build/badge.svg?branch=main)](https://github.com/kzu/nugetizer/actions?query=branch%3Amain+workflow%3Abuild+)
[![AzDO CI Status](https://dev.azure.com/kzu/oss/_apis/build/status/nugetizer?branchName=main)](http://build.azdo.io/kzu/oss/44)
[![CI Status](https://github.com/kzu/nugetizer/workflows/build/badge.svg?branch=main)](https://github.com/kzu/nugetizer/actions?query=branch%3Amain+workflow%3Abuild+)


# Why
Expand Down Expand Up @@ -176,7 +175,7 @@ As usual, you can change this default behavior by using `Pack=false` metadata.

### ProjectReference

Unlike SDK Pack that [considers project references as package references by default](https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#project-to-project-references), NuGetizer has an explicit contract between projects: the `GetPackageContents` target. This target is invoked when packing project references, and it returns whatever the referenced project exposes as package contents (including the inference rules above). If the project is *packable* (that is, it produces a package, denoted by the presence of a `PackageId` property), it will be packed as a dependency/package reference instead.
Unlike SDK Pack that [considers project references as package references by default](https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#project-to-project-references), NuGetizer has an explicit contract between projects: the `GetPackageContents` target. This target is invoked when packing project references, and it returns whatever the referenced project exposes as package contents (including the inference rules above). If the project is *packable* (that is, it produces a package, denoted by the presence of a `PackageId` property or `IsPackable=true`, for compatibility with SDK Pack), it will be packed as a dependency/package reference instead.

This means that by default, things Just Work: if you reference a library with no `PackageId`, it becomes part of whatever output your main project produces (analyzer, tools, plain lib). The moment you decide you want to make it a package on its own, you add the required metadata properties to that project and it automatically becomes a dependency instead.

Expand Down Expand Up @@ -262,3 +261,58 @@ Authoring, testing and iterating with your nuget packages should be easy and str
c. Clean the NuGet HTTP cache: this avoids a subsequent restore from a test/sample project from getting an older version from there, in case you build locally the same version of a previously restored one from an HTTP source.

These cleanups only apply in local builds, never in CI, and you can turn them all off by setting `EnablePackCleanup=false`.

## Advanced Features

This section contains miscelaneous useful features that are typically used in advanced scenarios and
are not necessarily mainstream.

### Packing arbitrary files from referenced packages

If you want to pack files from referenced packages, you can simply add `PackageReference` attribute
to `PackageFile`. Say we want to resuse the awesome icon from the
[ThisAssembly](https://nuget.org/packages/ThisAssembly) package, we can just bring it in with:

```xml
<ItemGroup>
<PackageFile Include="icon-128.png" PackagePath="icon.png" PackageReference="ThisAssembly" />
</ItemGroup>
```

The project will need to reference that package too, of course:

```xml
<ItemGroup>
<PackageReference Include="ThisAssembly" Version="1.0.0" GeneratePathProperty="true" Pack="false" />
</ItemGroup>
```

Note that we had to add the `GeneratePathProperty` to the reference, so that the package-relative
path `icon-128.png` can be properly resolved to the package install location. Also note that in this
particular case, we don't want to pack the reference as a dependency (it's a build-only or development
dependency package). That is, this feature does not require a package dependency for the package reference
content we're bringing in.

It even works for inferred content item types, such as `None`:

```xml
<PropertyGroup>
<PackNone>true</PackNone>
</PropertyGroup>
<ItemGroup>
<None Include="icon-128.png" PackageReference="ThisAssembly" />
</ItemGroup>
```

### Skip Build during Pack

If you are building explicitly prior to running `Pack` (and you're not using
`PackOnBuild=true`), you might want to optimize the process by skipping the
automatic `Build` run that happens by default when you run `Pack` by setting
`BuildOnPack=false`. Not building before `Pack` with `BuildOnPack=false`
can cause the target run to fail since output files expected by the packaging
might be missing (i.e. the primary output, content files, etc.).

This option is useful in combination with `BuildProjectReferences=false` when
packing on CI, since at that point all that's run are the P2P protocol involving
`GetPackageContents`.
3 changes: 3 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<!-- Allows source control information to always be present in ThisAssembly -->
<EnableSourceLink>true</EnableSourceLink>
<EnableSourceControlManagerQueries>true</EnableSourceControlManagerQueries>

<!-- We explicitly Build separately from Pack, because otherwise tasks assembly gets locked -->
<BuildOnPack>false</BuildOnPack>
</PropertyGroup>

<PropertyGroup Label="CI" Condition="'$(CI)' == ''">
Expand Down
32 changes: 14 additions & 18 deletions src/NuGetizer.Tasks/InferImplicitPackageReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,27 @@ public override bool Execute()
}
}

var inferred = new HashSet<PackageIdentity>();

foreach (var reference in PackageReferences.Where(x =>
"all".Equals(x.GetMetadata("PrivateAssets"), StringComparison.OrdinalIgnoreCase) &&
// Unless explicitly set to Pack=false
(!x.TryGetBoolMetadata("Pack", out var pack) || pack != false) &&
// NETCore/NETStandard are implicitly defined, we never need to bring them as deps.
!(bool.TryParse(x.GetMetadata("IsImplicitlyDefined"), out var isImplicit) && isImplicit)))
var inferred = new Dictionary<PackageIdentity, ITaskItem>();

foreach (var reference in PackageReferences)
{
var identity = new PackageIdentity(reference.ItemSpec, reference.GetMetadata("Version"));
var originalMetadata = (IDictionary<string, string>)reference.CloneCustomMetadata();
foreach (var dependency in FindDependencies(identity, packages))
{
inferred.Add(dependency);
if (!inferred.ContainsKey(dependency))
{
var item = new TaskItem(dependency.Id);
foreach (var metadata in originalMetadata)
item.SetMetadata(metadata.Key, metadata.Value);

item.SetMetadata("Version", dependency.Version);
inferred.Add(dependency, item);
}
}
}

ImplicitPackageReferences = inferred
.Select(x => new TaskItem(
x.Id,
new Dictionary<string, string>
{
{ "Version", x.Version } ,
{ "PrivateAssets", "all" },
}))
.ToArray();
ImplicitPackageReferences = inferred.Values.ToArray();

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/NuGetizer.Tasks/NuGetizer.Inference.targets
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ Copyright (c) .NET Foundation. All rights reserved.

</Target>

<Target Name="_CollectPrimaryOutputDependencies" DependsOnTargets="BuildOnlySettings;ResolveReferences" Returns="@(ImplicitPackageReference)">
<Target Name="_CollectPrimaryOutputDependencies" DependsOnTargets="BuildOnlySettings;RunResolvePackageDependencies;ResolveReferences" Returns="@(ImplicitPackageReference)">
<Error Code="NG1003" Text="Centrally managed package versions is only supported when using the Microsoft.NET.Sdk."
Condition="'$(ManagePackageVersionsCentrally)' == 'true' and '$(UsingMicrosoftNETSdk)' != 'true'" />
<ItemGroup>
Expand Down
15 changes: 6 additions & 9 deletions src/NuGetizer.Tasks/NuGetizer.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -239,23 +239,20 @@ Copyright (c) .NET Foundation. All rights reserved.
=================================================================================================
-->
<PropertyGroup Label="Hidden">
<!-- If we're packing on build, just add Pack as a dependency for Build -->
<_IsInnerBuild Condition="'$(TargetFramework)' != '' and '$(TargetFrameworks)' != ''">true</_IsInnerBuild>
<_ShouldPackOnBuild Condition="'$(PackOnBuild)' == 'true' And '$(IsPackable)' == 'true'">true</_ShouldPackOnBuild>
<BuildDependsOn Condition="'$(NoBuild)' != 'true' And '$(_ShouldPackOnBuild)' == 'true'">
$(BuildDependsOn);
Pack;
</BuildDependsOn>
<!-- If we're not packing on build, set up a dependency from Pack>Build for non-NuProj that are packable, so Build runs before Pack -->
<PackDependsOn Condition="'$(NoBuild)' != 'true' And '$(IsPackagingProject)' != 'true' And '$(_ShouldPackOnBuild)' != 'true' And '$(IsPackable)' == 'true'">
Build;
</PackDependsOn>
<PackDependsOn>
$(PackDependsOn)
GetPackageTargetPath;
GetPackageContents
</PackDependsOn>
</PropertyGroup>

<Target Name="_PackAfterBuild" AfterTargets="Build" DependsOnTargets="Pack"
Condition="'$(_ShouldPackOnBuild)' == 'true' And '$(_IsInnerBuild)' != 'true'" />
<Target Name="_BuildBeforePack" BeforeTargets="Pack" DependsOnTargets="Build"
Condition="'$(BuildOnPack)' != 'false' And '$(_IsInnerBuild)' != 'true' And '$(NoBuild)' != 'true' And '$(IsPackagingProject)' != 'true' And '$(_ShouldPackOnBuild)' != 'true' And '$(IsPackable)' == 'true'" />

<Target Name="Pack" DependsOnTargets="$(PackDependsOn)" Returns="@(_PackageTargetPath)" Condition="'$(IsPackable)' == 'true'">
<ItemGroup Condition="'@(NuspecFile)' == ''">
<NuspecFile Include="$(NuspecFile)" />
Expand Down
3 changes: 2 additions & 1 deletion src/NuGetizer.Tasks/NuGetizer.Tasks.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
Expand All @@ -25,6 +25,7 @@
<None Update="NuGetizer.MultiTargeting.targets" PackagePath="buildMultiTargeting\NuGetizer.targets" />
<None Include="NuGetizer.PackageMetadata.targets;dotnet-nugetize.props;dotnet-nugetize.targets" PackagePath="buildMultiTargeting\%(Filename)%(Extension)" Pack="true" />
<None Include="..\..\img\nugetizer-256.png" Link="icon.png" PackagePath="icon.png" />
<None Update="NuGetizer.Tasks.targets" Pack="false" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 9 additions & 1 deletion src/NuGetizer.Tasks/NuGetizer.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ Copyright (c) .NET Foundation. All rights reserved.
the automatic discovery will annotate those with Implicit instead.
This allows the duplicate item detection to either warn (Implicit) or error (Explicit). -->
<Source>Explicit</Source>
<!-- Used to include files from referenced packages -->
<PackageReference />
<PackageReferencePathProperty />
<PackageReferencePath />
<!-- Populated by content inference to preserve original identity -->
<OriginalItemSpec />
</PackageFile>
<PackageReference>
<!-- See https://github.com/NuGet/Home/wiki/PackageReference-Specification -->
Expand Down Expand Up @@ -136,8 +142,10 @@ Copyright (c) .NET Foundation. All rights reserved.
</ItemGroup>

<Target Name="_GetPackFolders" Returns="@(PackFolderKind)" />
<!-- Redefined in Common targets. See PackageOutputGroup target -->
<!-- Redefined in Current\Bin\Microsoft.Common.CurrentVersion.targets. See PackageOutputGroup target -->
<Target Name="AllProjectOutputGroups" />
<!-- Redefined in Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets -->
<Target Name="RunResolvePackageDependencies" />

<PropertyGroup Label="Hidden">
<!-- Flag this project as having been "nugetized" -->
Expand Down
34 changes: 34 additions & 0 deletions src/NuGetizer.Tasks/NuGetizer.targets
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,40 @@ Copyright (c) .NET Foundation. All rights reserved.
</PackageFile>
</ItemGroup>

<ItemGroup>
<_FromPackageReference Include="@(PackageFile -> '%(PackageReference)')" Condition="'%(PackageReference)' != ''" />
<FromPackageReference Include="@(_FromPackageReference -> Distinct())">
<PathProperty>Pkg$([MSBuild]::ValueOrDefault('%(_FromPackageReference.Identity)', '').Replace('.', '_'))</PathProperty>
</FromPackageReference>
<FromPackageReference>
<PathValue>$(%(FromPackageReference.PathProperty))</PathValue>
</FromPackageReference>
</ItemGroup>

<Error Condition="'@(FromPackageReference)' != '' and '%(FromPackageReference.PathValue)' == ''"
Code="NG0014"
Text="In order to reference content from package '%(FromPackageReference.Identity)', make sure its package reference specifies GeneratePathProperty='true'." />

<ItemGroup Condition="'@(FromPackageReference)' != ''">
<PackageFile Condition="'%(PackageReference)' != ''">
<PackageReferencePathProperty>Pkg$([MSBuild]::ValueOrDefault('%(PackageReference)', '').Replace('.', '_'))</PackageReferencePathProperty>
</PackageFile>
<PackageFile Condition="'%(PackageReference)' != ''">
<PackageReferencePath>$(%(PackageReferencePathProperty))</PackageReferencePath>
</PackageFile>
<PackageFileFromPackageReference Include="@(PackageFile -> '%(PackageReferencePath)/%(Identity)')" Condition="'%(PackageReference)' != '' and '%(OriginalItemSpec)' == ''" />
<PackageFileFromPackageReference Include="@(PackageFile -> '%(PackageReferencePath)/%(OriginalItemSpec)')" Condition="'%(PackageReference)' != '' and '%(OriginalItemSpec)' != ''" />
<PackageFile Remove="@(PackageFile)" Condition="'%(PackageReference)' != ''" />
<PackageFile Include="@(PackageFileFromPackageReference)">
<!-- Preserve original PackageReference for reference -->
<OriginalPackageReference>%(PackageReference)</OriginalPackageReference>
<!-- Clear values to avoid re-processing item in P2P scenarios -->
<PackageReference />
<PackageReferencePathProperty />
<PackageReferencePath />
</PackageFile>
</ItemGroup>

<!-- We batch depending on the IsPackaging metadata, which is only specified for referenced content
if the current project is building a package, to force retargeting of the referenced content. -->
<AssignPackagePath Files="@(PackageFile)" KnownFolders="@(PackFolderKind)" IsPackaging="%(PackageFile.IsPackaging)">
Expand Down
5 changes: 4 additions & 1 deletion src/NuGetizer.Tasks/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,15 @@
<value>Package file '{0}' must have either 'PackFolder' or 'PackagePath' metadata.</value>
</data>
<data name="ErrorCode_NG0011" xml:space="preserve">
<value>Project references to include in package must be nugetized.</value>
<value>Some project references cannot be properly packaged. Please install the NuGetizer package on the following projects: {0}.</value>
</data>
<data name="ErrorCode_NG0012" xml:space="preserve">
<value>Duplicate package source files with distinct content detected. Duplicates are not allowed in the package. Please remove the conflict between these files: {0}</value>
</data>
<data name="ErrorCode_NG0013" xml:space="preserve">
<value>Content files cannot specify the reserved 'contentFiles' relative directory. Please use the 'CodeLanguage' and 'TargetFramework' item metadata for '{0}' to control its relative path within 'contentFiles'.</value>
</data>
<data name="ErrorCode_NG0014" xml:space="preserve">
<value>In order to reference content from package '{0}', make sure its package reference specifies GeneratePathProperty="true".</value>
</data>
</root>
2 changes: 1 addition & 1 deletion src/NuGetizer.Tests/Builder.NuGetizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public static TargetResult BuildScenario(
if (OpenBuildLogAttribute.IsActive)
Process.Start(scenarioName + ".binlog");

return new TargetResult(projectOrSolution, result, target, logger);
return new TargetResult(projectOrSolution, result, target.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Last(), logger);
}

public class TargetResult : ITargetResult
Expand Down
Loading

0 comments on commit 75e514c

Please sign in to comment.