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

Dotnet add package support cpm #4986

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