Skip to content

Commit

Permalink
Dotnet add package support cpm (#4986)
Browse files Browse the repository at this point in the history
* dotnet add package cli support for cpm projects (#4700)
  • Loading branch information
martinrrm authored Dec 16, 2022
1 parent d3639a5 commit 6d1a300
Show file tree
Hide file tree
Showing 7 changed files with 1,069 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public async Task<int> ExecuteCommand(PackageReferenceArgs packageReferenceArgs,

var originalPackageSpec = matchingPackageSpecs.FirstOrDefault();

// Check if the project files are correct for CPM
if (originalPackageSpec.RestoreMetadata.CentralPackageVersionsEnabled && !msBuild.AreCentralVersionRequirementsSatisfied(packageReferenceArgs, originalPackageSpec))
{
return 1;
}

// 2. Determine the version

// Setup the Credential Service before making any potential http calls.
Expand Down
74 changes: 73 additions & 1 deletion src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 30 additions & 1 deletion src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -783,4 +783,33 @@ Non-HTTPS access will be removed in a future version. Consider migrating to 'HTT
<value>'--output-version' option not applicable for console output, it can only be used together with `--format json` option.</value>
<comment>Don't localize '--output-version' and `--format json`</comment>
</data>
</root>
<data name="Error_CentralPackageVersions_VersionsNotAllowed" xml:space="preserve">
<value>Projects that use central package version management should not define the version on the PackageReference items but on the PackageVersion items: {0}</value>
<comment>0 - package id</comment>
</data>
<data name="Error_AddPkg_CentralPackageVersions_EmptyVersionOverride" xml:space="preserve">
<value>VersionOverride for package '{0}' should not be empty.</value>
<comment>0 - package id</comment>
</data>
<data name="Error_AddPkg_CentralPackageVersions_PackageReference_WrongLocation" xml:space="preserve">
<value>Package reference for package '{0}' defined in incorrect location, PackageReference should be defined in project file.</value>
<comment>0 - package id</comment>
</data>
<data name="Error_AddPkg_CentralPackageVersions_PackageVersion_WrongLocation" xml:space="preserve">
<value>PackageVersion for package '{0}' defined in incorrect location, PackageVersion should be defined in Directory.Package.props.</value>
<comment>0 - package id</comment>
</data>
<data name="Error_CentralPackageVersions_AutoreferencedReferencesNotAllowed" xml:space="preserve">
<value>The packages {0} are implicitly referenced. You do not typically need to reference them from your project or in your central package versions management file. For more information, see https://aka.ms/sdkimplicitrefs</value>
</data>
<data name="Error_CentralPackageVersions_FloatingVersionsAreNotAllowed" xml:space="preserve">
<value>Centrally defined floating package versions are not allowed.</value>
</data>
<data name="Error_CentralPackageVersions_MissingPackageVersion" xml:space="preserve">
<value>The PackageReference items {0} do not have corresponding PackageVersion.</value>
</data>
<data name="Error_CentralPackageVersions_VersionOverrideDisabled" xml:space="preserve">
<value>The package reference {0} specifies a VersionOverride but the ability to override a centrally defined version is currently disabled.</value>
<comment>0 - packagereference name</comment>
</data>
</root>
136 changes: 125 additions & 11 deletions src/NuGet.Core/NuGet.CommandLine.XPlat/Utility/MSBuildAPIUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,87 @@ public int RemovePackageReference(string projectPath, LibraryDependency libraryD
}
}

/// <summary>
/// Check if the project files format are correct for CPM
/// </summary>
/// <param name="packageReferenceArgs">Arguments used in the command</param>
/// <param name="packageSpec"></param>
/// <returns></returns>
public bool AreCentralVersionRequirementsSatisfied(PackageReferenceArgs packageReferenceArgs, PackageSpec packageSpec)
{
var project = GetProject(packageReferenceArgs.ProjectPath);
string directoryPackagesPropsPath = project.GetPropertyValue(DirectoryPackagesPropsPathPropertyName);

// Get VersionOverride if it exisits in the package reference.
IEnumerable<LibraryDependency> dependenciesWithVersionOverride = null;

if (packageSpec.RestoreMetadata.CentralPackageVersionOverrideDisabled)
{
dependenciesWithVersionOverride = packageSpec.TargetFrameworks.SelectMany(tfm => tfm.Dependencies.Where(d => !d.AutoReferenced && d.VersionOverride != null));
// Emit a error if VersionOverride was specified for a package reference but that functionality is disabled
foreach (var item in dependenciesWithVersionOverride)
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_CentralPackageVersions_VersionOverrideDisabled, string.Join(";", dependenciesWithVersionOverride.Select(d => d.Name))));
return false;
}
}

// The dependencies should not have versions explicitly defined if cpvm is enabled.
IEnumerable<LibraryDependency> dependenciesWithDefinedVersion = packageSpec.TargetFrameworks.SelectMany(tfm => tfm.Dependencies.Where(d => !d.VersionCentrallyManaged && !d.AutoReferenced && d.VersionOverride == null));
if (dependenciesWithDefinedVersion.Any())
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_CentralPackageVersions_VersionsNotAllowed, string.Join(";", dependenciesWithDefinedVersion.Select(d => d.Name))));
return false;
}
IEnumerable<LibraryDependency> autoReferencedAndDefinedInCentralFile = packageSpec.TargetFrameworks.SelectMany(tfm => tfm.Dependencies.Where(d => d.AutoReferenced && tfm.CentralPackageVersions.ContainsKey(d.Name)));
if (autoReferencedAndDefinedInCentralFile.Any())
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_CentralPackageVersions_AutoreferencedReferencesNotAllowed, string.Join(";", autoReferencedAndDefinedInCentralFile.Select(d => d.Name))));
return false;
}
IEnumerable<LibraryDependency> packageReferencedDependenciesWithoutCentralVersionDefined = packageSpec.TargetFrameworks.SelectMany(tfm => tfm.Dependencies.Where(d => d.LibraryRange.VersionRange == null));
if (packageReferencedDependenciesWithoutCentralVersionDefined.Any())
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_CentralPackageVersions_MissingPackageVersion, string.Join(";", packageReferencedDependenciesWithoutCentralVersionDefined.Select(d => d.Name))));
return false;
}
var floatingVersionDependencies = packageSpec.TargetFrameworks.SelectMany(tfm => tfm.CentralPackageVersions.Values).Where(cpv => cpv.VersionRange.IsFloating);
if (floatingVersionDependencies.Any())
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_CentralPackageVersions_FloatingVersionsAreNotAllowed));
return false;
}

// PackageVersion should not be defined outside the project file.
var packageVersions = project.Items.Where(item => item.ItemType == PACKAGE_VERSION_TYPE_TAG && item.EvaluatedInclude.Equals(packageReferenceArgs.PackageId) && !item.Xml.ContainingProject.FullPath.Equals(directoryPackagesPropsPath));
if (packageVersions.Any())
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_AddPkg_CentralPackageVersions_PackageVersion_WrongLocation, packageReferenceArgs.PackageId));
return false;
}

// PackageReference should not be defined in Directory.Packages.props
var packageReferenceOutsideProjectFile = project.Items.Where(item => item.ItemType == PACKAGE_REFERENCE_TYPE_TAG && item.Xml.ContainingProject.FullPath.Equals(directoryPackagesPropsPath));
if (packageReferenceOutsideProjectFile.Any())
{
packageReferenceArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_AddPkg_CentralPackageVersions_PackageReference_WrongLocation, packageReferenceArgs.PackageId));
return false;
}

ProjectItem packageReference = project.Items.Where(item => item.ItemType == PACKAGE_REFERENCE_TYPE_TAG && item.EvaluatedInclude.Equals(packageReferenceArgs.PackageId)).LastOrDefault();
ProjectItem packageVersionInProps = packageVersions.LastOrDefault();
var versionOverride = dependenciesWithVersionOverride?.FirstOrDefault(d => d.Name.Equals(packageReferenceArgs.PackageId));

// If package reference exists and the user defined a VersionOverride or PackageVersions but didn't specified a version, no-op
if (packageReference != null && (versionOverride != null || packageVersionInProps != null) && packageReferenceArgs.NoVersion)
{
return false;
}

ProjectCollection.GlobalProjectCollection.UnloadProject(project);
return true;
}

/// <summary>
/// Add an unconditional package reference to the project.
/// </summary>
Expand Down Expand Up @@ -194,28 +275,42 @@ private void AddPackageReference(Project project,
}
else
{
// Get package version and VersionOverride if it already exists in the props file. Returns null if there is no matching package version.
ProjectItem packageReferenceInProps = project.Items.LastOrDefault(i => i.ItemType == PACKAGE_REFERENCE_TYPE_TAG && i.EvaluatedInclude.Equals(libraryDependency.Name));
var versionOverrideExists = packageReferenceInProps?.Metadata.FirstOrDefault(i => i.Name.Equals("VersionOverride") && !string.IsNullOrWhiteSpace(i.EvaluatedValue));

if (!existingPackageReferences.Any())
{
//Add <PackageReference/> to the project file.
AddPackageReferenceIntoItemGroupCPM(project, itemGroup, libraryDependency);
}

// Get package version if it already exists in the props file. Returns null if there is no matching package version.
ProjectItem packageVersionInProps = project.Items.LastOrDefault(i => i.ItemType == PACKAGE_VERSION_TYPE_TAG && i.EvaluatedInclude.Equals(libraryDependency.Name, StringComparison.OrdinalIgnoreCase));

// If no <PackageVersion /> exists in the Directory.Packages.props file.
if (packageVersionInProps == null)
if (versionOverrideExists != null)
{
// Modifying the props file if project is onboarded to CPM.
AddPackageVersionIntoItemGroupCPM(project, libraryDependency);
// Update if VersionOverride instead of Directory.Packages.props file
string packageVersion = libraryDependency.LibraryRange.VersionRange.OriginalString;
UpdateVersionOverride(project, packageReferenceInProps, packageVersion);
}
else
{
// Modify the Directory.Packages.props file with the version that is passed in.
if (!noVersion)
// Get package version if it already exists in the props file. Returns null if there is no matching package version.
ProjectItem packageVersionInProps = project.Items.LastOrDefault(i => i.ItemType == PACKAGE_VERSION_TYPE_TAG && i.EvaluatedInclude.Equals(libraryDependency.Name));

// If no <PackageVersion /> exists in the Directory.Packages.props file.
if (packageVersionInProps == null)
{
string packageVersion = libraryDependency.LibraryRange.VersionRange.OriginalString;
UpdatePackageVersion(project, packageVersionInProps, packageVersion);
// Modifying the props file if project is onboarded to CPM.
AddPackageVersionIntoItemGroupCPM(project, libraryDependency);
}
else
{
// Modify the Directory.Packages.props file with the version that is passed in.
if (!noVersion)
{
string packageVersion = libraryDependency.LibraryRange.VersionRange.OriginalString;
UpdatePackageVersion(project, packageVersionInProps, packageVersion);
}

}
}
}
Expand Down Expand Up @@ -422,6 +517,25 @@ private void UpdatePackageReferenceItems(IEnumerable<ProjectItem> packageReferen
}
}

/// <summary>
/// Updates VersionOverride from <PackageReference /> element if version is passed in as a CLI argument
/// </summary>
/// <param name="project"></param>
/// <param name="packageReference"></param>
/// <param name="versionCLIArgument"></param>
internal void UpdateVersionOverride(Project project, ProjectItem packageReference, string versionCLIArgument)
{
// Determine where the <PackageVersion /> item is decalred
ProjectItemElement packageReferenceItemElement = project.GetItemProvenance(packageReference).LastOrDefault()?.ItemElement;

// Get the Version attribute on the packageVersionItemElement.
ProjectMetadataElement versionOverrideAttribute = packageReferenceItemElement.Metadata.FirstOrDefault(i => i.Name.Equals("VersionOverride"));

// Update the version
versionOverrideAttribute.Value = versionCLIArgument;
packageReferenceItemElement.ContainingProject.Save();
}

/// <summary>
/// Update the <PackageVersion /> element if a version is passed in as a CLI argument.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/NuGet.Core/NuGet.LibraryModel/LibraryDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public bool Equals(LibraryDependency other)
GeneratePathProperty == other.GeneratePathProperty &&
VersionCentrallyManaged == other.VersionCentrallyManaged &&
Aliases == other.Aliases &&
VersionOverride == other.VersionOverride &&
EqualityUtility.EqualsWithNullCheck(VersionOverride, other.VersionOverride) &&
ReferenceType == other.ReferenceType;
}

Expand Down
Loading

0 comments on commit 6d1a300

Please sign in to comment.