diff --git a/src/CentralPackageVersions.UnitTests/CentralPackageVersionsTests.cs b/src/CentralPackageVersions.UnitTests/CentralPackageVersionsTests.cs index 36f86456..ada2be34 100644 --- a/src/CentralPackageVersions.UnitTests/CentralPackageVersionsTests.cs +++ b/src/CentralPackageVersions.UnitTests/CentralPackageVersionsTests.cs @@ -19,29 +19,93 @@ public class CentralPackageVersionsTests : MSBuildSdkTestBase [Fact] public void CanDisableCentralPackageVersions() { + WritePackagesProps(); + ProjectCreator.Templates .SdkCsproj( path: Path.Combine(TestRootPath, "test.csproj"), projectCollection: new ProjectCollection(new Dictionary { - { "EnableCentralPackageVersions", "false" }, - { "DisableImplicitFrameworkReferences", "true" } + ["EnableCentralPackageVersions"] = "false", + ["DisableImplicitFrameworkReferences"] = "true" }), projectCreator: creator => creator .ItemPackageReference("Foo", "10.0.0") .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) - .Save() .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput) .Project .GetItems("PackageReference").ToDictionary(i => i.EvaluatedInclude, i => i.GetMetadataValue("Version")) .ShouldBe(new Dictionary { - { "Foo", "10.0.0" } + ["Foo"] = "10.0.0" }); result.ShouldBeTrue(() => buildOutput.GetConsoleLog()); } + [Fact] + public void CanDisableGlobalPackageReferences() + { + WritePackagesProps(); + + ProjectCreator.Templates + .SdkCsproj( + path: Path.Combine(TestRootPath, "test.csproj"), + projectCollection: new ProjectCollection(new Dictionary + { + ["DisableImplicitFrameworkReferences"] = "true", + ["EnableGlobalPackageReferences"] = "false" + }), + projectCreator: creator => creator + .ItemPackageReference("Foo") + .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) + .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput) + .Project + .GetItems("PackageReference").ToDictionary(i => i.EvaluatedInclude, i => i.GetMetadataValue("Version")) + .ShouldBe( + new Dictionary + { + ["Foo"] = "1.2.3" + }, + ignoreOrder: true); + + result.ShouldBeTrue(() => buildOutput.GetConsoleLog()); + } + + [Fact] + public void CanOverridePackageVersion() + { + WritePackagesProps(); + + ProjectCreator.Templates + .SdkCsproj( + path: Path.Combine(TestRootPath, "test.csproj"), + projectCollection: new ProjectCollection(new Dictionary + { + ["DisableImplicitFrameworkReferences"] = "true" + }), + projectCreator: creator => creator + .ItemPackageReference( + "Foo", + metadata: new Dictionary + { + ["VersionOverride"] = "9.0.1" + }) + .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) + .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput) + .Project + .GetItems("PackageReference").ToDictionary(i => i.EvaluatedInclude, i => i.GetMetadataValue("Version")) + .ShouldBe( + new Dictionary + { + ["Foo"] = "9.0.1", + ["Global1"] = "1.0.0" + }, + ignoreOrder: true); + + result.ShouldBeTrue(() => buildOutput.GetConsoleLog()); + } + [Fact] public void LogErrorIfProjectSpecifiesGlobalPackageReference() { @@ -54,15 +118,11 @@ public void LogErrorIfProjectSpecifiesGlobalPackageReference() .ItemPackageReference("Foo") .ItemPackageReference("Global1") .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) - .Save() .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput); result.ShouldBeFalse(() => buildOutput.GetConsoleLog()); - buildOutput.Errors - .Select(i => i.Message) - .ToList() - .ShouldBe(new[] { $"The package reference \'Global1\' is already defined as a GlobalPackageReference in \'{packagesProps.FullPath}\'. Individual projects do not need to include a PackageReference if a GlobalPackageReference is declared." }); + buildOutput.Errors.ShouldBe(new[] { $"The package reference \'Global1\' is already defined as a GlobalPackageReference in \'{packagesProps.FullPath}\'. Individual projects do not need to include a PackageReference if a GlobalPackageReference is declared." }); } [Fact] @@ -77,15 +137,11 @@ public void LogErrorIfProjectSpecifiesUnknownPackage() .ItemPackageReference("Foo") .ItemPackageReference("Baz") .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) - .Save() .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput); result.ShouldBeFalse(() => buildOutput.GetConsoleLog()); - buildOutput.Errors - .Select(i => i.Message) - .ToList() - .ShouldBe(new[] { $"The package reference \'Baz\' must have a version defined in \'{packagesProps.FullPath}\'." }); + buildOutput.Errors.ShouldBe(new[] { $"The package reference \'Baz\' must have a version defined in \'{packagesProps.FullPath}\'." }); } [Fact] @@ -99,16 +155,34 @@ public void LogErrorIfProjectSpecifiesVersion() projectCreator: creator => creator .ItemPackageReference("Foo", "10.0.0") .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) + .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput); + + result.ShouldBeFalse(() => buildOutput.GetConsoleLog()); - .Save() + buildOutput.Errors.ShouldBe(new[] { $"The package reference \'Foo\' should not specify a version. Please specify the version in \'{packagesProps.FullPath}\' or set VersionOverride to override the centrally defined version." }); + } + + [Fact] + public void LogErrorIfProjectSpecifiesVersionAndVersionOverrideIsDisabled() + { + ProjectCreator packagesProps = WritePackagesProps(); + + ProjectCreator.Templates + .SdkCsproj( + path: Path.Combine(TestRootPath, "test.csproj"), + projectCollection: new ProjectCollection(new Dictionary + { + ["DisableImplicitFrameworkReferences"] = "true", + ["EnablePackageVersionOverride"] = "false" + }), + projectCreator: creator => creator + .ItemPackageReference("Foo", "10.0.0") + .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) .TryBuild("CheckPackageReferences", out bool result, out BuildOutput buildOutput); result.ShouldBeFalse(() => buildOutput.GetConsoleLog()); - buildOutput.Errors - .Select(i => i.Message) - .ToList() - .ShouldBe(new[] { $"The package reference \'Foo\' should not specify a version. Please specify the version in \'{packagesProps.FullPath}\'." }); + buildOutput.Errors.ShouldBe(new[] { $"The package reference \'Foo\' should not specify a version. Please specify the version in \'{packagesProps.FullPath}\'." }); } [Fact] @@ -121,14 +195,12 @@ public void PackageVersionsAreApplied() path: Path.Combine(TestRootPath, "test.csproj"), projectCollection: new ProjectCollection(new Dictionary { - { "DisableImplicitFrameworkReferences", "true" } + ["DisableImplicitFrameworkReferences"] = "true" }), projectCreator: creator => creator .ItemPackageReference("Foo") .ItemPackageReference("Bar") .Import(Path.Combine(Environment.CurrentDirectory, @"Sdk\Sdk.targets"))) - - .Save() .Project .GetItems("PackageReference").ToDictionary(i => i.EvaluatedInclude, i => i.GetMetadataValue("Version")) .ShouldBe(new Dictionary @@ -144,16 +216,15 @@ private ProjectCreator WritePackagesProps() return ProjectCreator.Templates .PackagesProps( path: Path.Combine(TestRootPath, "Packages.props"), - packageVersions: new Dictionary + packageReferences: new Dictionary { ["Foo"] = "1.2.3", ["Bar"] = "4.5.6", - ["Global1"] = "1.0.0", ["NETStandard.Library"] = "2.0.0" }, - globalPackageReferences: new List + globalPackageReferences: new Dictionary { - "Global1" + ["Global1"] = "1.0.0" }) .Save(); } diff --git a/src/CentralPackageVersions.UnitTests/CustomProjectCreatorTemplates.cs b/src/CentralPackageVersions.UnitTests/CustomProjectCreatorTemplates.cs index ef18d7c5..a83cc784 100644 --- a/src/CentralPackageVersions.UnitTests/CustomProjectCreatorTemplates.cs +++ b/src/CentralPackageVersions.UnitTests/CustomProjectCreatorTemplates.cs @@ -22,8 +22,8 @@ public static ProjectCreator PackagesProps( string treatAsLocalProperty = null, ProjectCollection projectCollection = null, NewProjectFileOptions? projectFileOptions = NewProjectFileOptions.IncludeXmlDeclaration | NewProjectFileOptions.IncludeXmlNamespace, - IReadOnlyDictionary packageVersions = null, - IReadOnlyCollection globalPackageReferences = null) + IReadOnlyDictionary packageReferences = null, + IReadOnlyDictionary globalPackageReferences = null) { return ProjectCreator.Create( path, @@ -34,9 +34,9 @@ public static ProjectCreator PackagesProps( treatAsLocalProperty, projectCollection, projectFileOptions) - .ForEach(packageVersions, (i, creator) => creator.ItemPackageVersion(i.Key, i.Value)) + .ForEach(packageReferences, (i, creator) => creator.ItemCentralPackageReference(i.Key, i.Value)) .ItemGroup() - .ForEach(globalPackageReferences, (i, creator) => creator.ItemGlobalPackageReference(i)) + .ForEach(globalPackageReferences, (i, creator) => creator.ItemGlobalPackageReference(i.Key, i.Value)) .CustomAction(customAction); } } diff --git a/src/CentralPackageVersions.UnitTests/ExtensionMethods.cs b/src/CentralPackageVersions.UnitTests/ExtensionMethods.cs index 6faa1478..1122d33e 100644 --- a/src/CentralPackageVersions.UnitTests/ExtensionMethods.cs +++ b/src/CentralPackageVersions.UnitTests/ExtensionMethods.cs @@ -9,13 +9,14 @@ namespace Microsoft.Build.CentralPackageVersions.UnitTests { public static class ExtensionMethods { - public static ProjectCreator ItemGlobalPackageReference(this ProjectCreator creator, string packageId, string includeAssets = null, string excludeAssets = null, string privateAssets = null, IDictionary metadata = null, string condition = null) + public static ProjectCreator ItemGlobalPackageReference(this ProjectCreator creator, string packageId, string version, string includeAssets = null, string excludeAssets = null, string privateAssets = null, IDictionary metadata = null, string condition = null) { return creator.ItemInclude( itemType: "GlobalPackageReference", include: packageId, metadata: metadata.Merge(new Dictionary { + { "Version", version }, { "IncludeAssets", includeAssets }, { "ExcludeAssets", excludeAssets }, { "PrivateAssets", privateAssets }, @@ -23,17 +24,14 @@ public static ProjectCreator ItemGlobalPackageReference(this ProjectCreator crea condition: condition); } - public static ProjectCreator ItemPackageVersion(this ProjectCreator creator, string packageId, string version, string includeAssets = null, string excludeAssets = null, string privateAssets = null, IDictionary metadata = null, string condition = null) + public static ProjectCreator ItemCentralPackageReference(this ProjectCreator creator, string packageId, string version, IDictionary metadata = null, string condition = null) { - return creator.ItemInclude( - itemType: "PackageVersion", - include: packageId, + return creator.ItemUpdate( + itemType: "PackageReference", + update: packageId, metadata: metadata.Merge(new Dictionary { { "Version", version }, - { "IncludeAssets", includeAssets }, - { "ExcludeAssets", excludeAssets }, - { "PrivateAssets", privateAssets }, }), condition: condition); } diff --git a/src/CentralPackageVersions.UnitTests/Microsoft.Build.CentralPackageVersions.UnitTests.csproj b/src/CentralPackageVersions.UnitTests/Microsoft.Build.CentralPackageVersions.UnitTests.csproj index 761000d6..c0b16708 100644 --- a/src/CentralPackageVersions.UnitTests/Microsoft.Build.CentralPackageVersions.UnitTests.csproj +++ b/src/CentralPackageVersions.UnitTests/Microsoft.Build.CentralPackageVersions.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/CentralPackageVersions/README.md b/src/CentralPackageVersions/README.md index 7ec0a37c..706447b0 100644 --- a/src/CentralPackageVersions/README.md +++ b/src/CentralPackageVersions/README.md @@ -4,6 +4,8 @@ The `Microsoft.Build.CentralPackageVersions` MSBuild project SDK allows project tree owners to manage their NuGet package versions in one place. Stock NuGet requires that each project contain a version. You can also use MSBuild properties to manage versions. +**NOTE: Please read about breaking changes at the bottom if you're upgrading from version 1.0 to version 2.0 of the package** + ## Centrally Managing Package Versions To get started, you will need to create an MSBuild project at the root of your repository named `Packages.props` that declares `PackageVersion` items. @@ -15,14 +17,10 @@ In this example, packages like `Newtonsoft.Json` are set to exactly version `10. - - - - - - - - + + + + ``` @@ -30,7 +28,7 @@ In this example, packages like `Newtonsoft.Json` are set to exactly version `10. **SampleProject.csproj** ```xml - + netstandard2.0 @@ -43,17 +41,33 @@ In this example, packages like `Newtonsoft.Json` are set to exactly version `10. ``` Each project still has a `PackageReference` but must not specify a version. This ensures that the correct packages are referenced for each project. +### Overriding a PackageReference version + +In some cases, you may need to override the version for a particular project. To do this, you must use the `VersionOverride` metadata. Having different versions in use in your tree can lead to undesired behavior and make diagnosing build errors more difficult. + +```xml + + + + + netstandard2.0 + + + + + + +``` + ## Global Package References -Some packages should be referenced by all projects in your tree. This includes packages that do versioning, extend your build, or do any other function that is needed repository-wide. +Some packages should be referenced by all projects in your tree. This includes packages that do versioning, extend your build, or do any other function that is needed repository-wide. Global package references have their `PrivateAssets` metadata set to `All` by default which prevents them from being picked up by downstream dependencies. **Packages.props** ```xml - - - + ``` @@ -64,7 +78,7 @@ Some packages should be referenced by all projects in your tree. This includes If a user attempts to add a version to a project, they will get a build error: ``` -The package reference 'Newtonsoft.Json' should not specify a version. Please specify the version in 'C:\repo\Packages.props'. +The package reference 'Newtonsoft.Json' should not specify a version. Please specify the version in 'C:\repo\Packages.props' or set VersionOverride to override the centrally defined version. ``` If a user attempts to add a package that does not specify a version in `Packages.props`, they will get a build error: @@ -76,7 +90,7 @@ The package reference 'Newtonsoft.Json' must have a version defined in 'C:\repo\ ## Extensibility -Setting the following properties control how Traversal works. +Setting the following properties control how Central Package Versions works. | Property | Description | |-------------------------------------|-------------| @@ -96,3 +110,49 @@ Use a custom file name for your project that defines package versions. ``` + +## Version 2.0 Breaking Changes + +In version 2.0 of the package, we have deprecated the `PackageVersion` item and instead are using ``. To migrate an existing code base to use the newer version, please do the following: + +1. Search and replace `PackageVersion Include` with `PackageReference Update` in your `Packages.props`
+ v1.0: + ```xml + + + + ``` + v2.0: + ```xml + + + + ``` +2. Remove all `PackageVersion` items in `Packages.props` for global package references and instead specify the version on the `` item
+ v1.0: + ```xml + + + + + ``` + v2.0: + ```xml + + + + ``` +3. Remove all `PackageVersion` items in individual projects, set `VersionOverride` to override a version, and move metadata to the corresponding `` item in the project file.
+ v1.0: + ```xml + + + + + ``` + v2.0: + ```xml + + + + ``` \ No newline at end of file diff --git a/src/CentralPackageVersions/Sdk/Sdk.targets b/src/CentralPackageVersions/Sdk/Sdk.targets index 3fd66843..c5569da4 100644 --- a/src/CentralPackageVersions/Sdk/Sdk.targets +++ b/src/CentralPackageVersions/Sdk/Sdk.targets @@ -6,102 +6,142 @@ --> - + + + $([MSBuild]::GetPathOfFileAbove('Packages.props', $(MSBuildProjectDirectory))) - false - - - - - + + false - + $(MSBuildThisFileFullPath);$(MSBuildAllProjects) - $(CentralPackagesFile);$(MSBuildAllProjects) + $(CentralPackagesFile);$(MSBuildAllProjects) - + + + + All + + + + <_PackageReferenceWithVersion Include="@(PackageReference->HasMetadata('Version'))" /> - - - - - - - - + + <_OriginalPackageReference Include="@(PackageReference->ClearMetadata())" /> + - - + - - <_PackagesNotReferenced Include="@(PackageVersion)" Exclude="@(PackageReference)" /> + + + - - + + <_PackageReferenceWithVersionOverride Include="@(PackageReference->HasMetadata('VersionOverride'))" + Version="%(VersionOverride)" + Condition=" '$(EnablePackageVersionOverride)' != 'false' " + /> - - + + - - <_PackagesNotReferenced Remove="@(_PackagesNotReferenced)" /> + + + <_PackageReferenceWithVersionOverride Remove="@(_PackageReferenceWithVersionOverride)" /> + Condition=" '$(EnableCentralPackageVersions)' != 'false' And @(PackageReference->Count()) > 0 "> - - - - - - + + + <_DuplicateGlobalPackageReference Include="@(_OriginalPackageReference)" + Condition=" '@(GlobalPackageReference)' == '@(_OriginalPackageReference)' and '%(Identity)' != '' " /> + Log an error if there are any duplicate items where a is already defined. + --> + Text="The package reference '%(_DuplicateGlobalPackageReference.Identity)' is already defined as a GlobalPackageReference in '$(CentralPackagesFile)'. Individual projects do not need to include a PackageReference if a GlobalPackageReference is declared." + Condition=" '$(EnableGlobalPackageReferences)' != 'false' And @(_DuplicateGlobalPackageReference->Count()) > 0" + File="$(MSBuildProjectFullPath)" /> + Generate an error if any explicit PackageReference has a version specified in a project. Users must specify a version in + the central pacakge management file or use VersionOverride. + --> + Text="The package reference '%(_PackageReferenceWithVersion.Identity)' should not specify a version. Please specify the version in '$(CentralPackagesFile)' or set VersionOverride to override the centrally defined version." + Condition=" @(_PackageReferenceWithVersion->Count()) > 0 And '%(_PackageReferenceWithVersion.IsImplicitlyDefined)' != 'true' And '$(EnablePackageVersionOverride)' != 'false' " + File="$(MSBuildProjectFullPath) "/> - + Text="The package reference '%(_PackageReferenceWithVersion.Identity)' should not specify a version. Please specify the version in '$(CentralPackagesFile)'." + Condition=" @(_PackageReferenceWithVersion->Count()) > 0 And '%(_PackageReferenceWithVersion.IsImplicitlyDefined)' != 'true' And '$(EnablePackageVersionOverride)' == 'false'" + File="$(MSBuildProjectFullPath) "/> + Generate an error if any explicit PackageReference did not have Version specified in the central package management file. + --> - - + + + \ No newline at end of file diff --git a/src/CentralPackageVersions/version.json b/src/CentralPackageVersions/version.json index eca070dc..8dbe6c05 100644 --- a/src/CentralPackageVersions/version.json +++ b/src/CentralPackageVersions/version.json @@ -1,6 +1,6 @@ { "inherit": true, - "version": "1.0-preview", + "version": "2.0-preview", "publicReleaseRefSpec": [ "^refs/tags/Microsoft\\.Build\\.CentralPackageVersions-v\\d+\\.\\d+.\\d+" ] diff --git a/src/NoTargets.UnitTests/Microsoft.Build.NoTargets.UnitTests.csproj b/src/NoTargets.UnitTests/Microsoft.Build.NoTargets.UnitTests.csproj index bcc07447..7b0855da 100644 --- a/src/NoTargets.UnitTests/Microsoft.Build.NoTargets.UnitTests.csproj +++ b/src/NoTargets.UnitTests/Microsoft.Build.NoTargets.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/NoTargets.UnitTests/NoTargetsTests.cs b/src/NoTargets.UnitTests/NoTargetsTests.cs index 35b3c9fb..29bf4cfe 100644 --- a/src/NoTargets.UnitTests/NoTargetsTests.cs +++ b/src/NoTargets.UnitTests/NoTargetsTests.cs @@ -35,11 +35,7 @@ public void SimpleBuild() result.ShouldBeTrue(() => buildOutput.GetConsoleLog()); - buildOutput - .MessagesHighImportance - .Select(i => i.Message) - .ToList() - .ShouldContain("86F00AF59170450E9D687652D74A6394"); + buildOutput.Messages.High.ShouldContain("86F00AF59170450E9D687652D74A6394"); } } } \ No newline at end of file diff --git a/src/Traversal.UnitTests/Microsoft.Build.Traversal.UnitTests.csproj b/src/Traversal.UnitTests/Microsoft.Build.Traversal.UnitTests.csproj index d212168a..34977ddf 100644 --- a/src/Traversal.UnitTests/Microsoft.Build.Traversal.UnitTests.csproj +++ b/src/Traversal.UnitTests/Microsoft.Build.Traversal.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Traversal.UnitTests/TraversalTests.cs b/src/Traversal.UnitTests/TraversalTests.cs index fe215611..80a460ec 100644 --- a/src/Traversal.UnitTests/TraversalTests.cs +++ b/src/Traversal.UnitTests/TraversalTests.cs @@ -59,11 +59,7 @@ public void TraversalTargetsRun(string target) result.ShouldBeTrue(customMessage: () => buildOutput.GetConsoleLog()); - buildOutput - .MessagesHighImportance - .Select(i => i.Message) - .ToList() - .ShouldBe( + buildOutput.Messages.High.ShouldBe( new[] { "BF0C6E1044514FE3AE4B78EC308D6F45",