|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.Packaging;
using NuGet.ProjectModel;
using NuGet.RuntimeModel;
using NuGet.Versioning;
namespace NuGet.Commands.Restore.Utility
{
/// <summary>
/// <seealso cref="RestoreRequest"/> uses <see cref="PackageSpec"/> instances to represent project restore inputs.
/// This class provides a single implementation of creating a <see cref="PackageSpec"/> from an <see cref="IProject"/>,
/// which each restore entry point is responsible for creating an adapter for.
/// </summary>
/// <remarks>
/// When modifying PackageSpecFactory with new properties or items, you still need to modify NuGet.targets for
/// MSBuild restore, and the dotnet/project-system repo for SDK style projects in Visual Studio. In addition, it
/// would be a good idea to manually test new features with non-SDK style projects in Visual Studio, and also static graph evaluation restores on the command line, to test all 4 restore entry points.
/// </remarks>
public static class PackageSpecFactory
{
/// <summary>
/// Environment variable to tell NuGet to not use the new PackageSpec factory.
/// If you have to use this, make sure that there's a issue in https://github.com/NuGet/Home to make sure
/// bugs are fixed before the old package spec methods are removed.
/// </summary>
public const string EnvironmentVariableName = "NUGET_USE_NEW_PACKAGESPEC_FACTORY";
/// <summary>
/// Convert an MSBuild project to a PackageSpec.
/// </summary>
public static PackageSpec? GetPackageSpec(IProject project, ISettings settings)
{
PackageSpec? packageSpec = GetIntermediatePackageSpec(project);
if (packageSpec == null)
{
return null;
}
ApplySettings(packageSpec, settings);
return packageSpec;
}
/// <summary>
/// Convert an MSBuild project to a PackageSpec without applying <see cref="ISettings"/>.
/// The result contains project-level restore properties only. Settings-dependent values
/// like <see cref="ProjectRestoreMetadata.PackagesPath"/>, <see cref="ProjectRestoreMetadata.Sources"/>,
/// <see cref="ProjectRestoreMetadata.FallbackFolders"/>, and <see cref="ProjectRestoreMetadata.ConfigFilePaths"/>
/// may be incomplete. Call <see cref="ApplySettings"/> to fill them in.
/// </summary>
public static PackageSpec? GetIntermediatePackageSpec(IProject project)
{
(ProjectRestoreMetadata? restoreMetadata, List<TargetFrameworkInformation>? targetFrameworkInfos) = GetProjectRestoreMetadataAndTargetFrameworkInformation(project);
if (restoreMetadata == null || targetFrameworkInfos == null)
{
return null;
}
var packageSpec = new PackageSpec(targetFrameworkInfos)
{
FilePath = project.FullPath,
Name = restoreMetadata.ProjectName,
RestoreMetadata = restoreMetadata,
RuntimeGraph = new RuntimeGraph(
MSBuildStringUtility.Split($"{project.OuterBuild.GetProperty("RuntimeIdentifiers")};{project.OuterBuild.GetProperty("RuntimeIdentifier")}")
.Concat(project.TargetFrameworks.Values.SelectMany(i => MSBuildStringUtility.Split($"{i.GetProperty("RuntimeIdentifiers")};{i.GetProperty("RuntimeIdentifier")}")))
.Distinct(StringComparer.Ordinal)
.Select(rid => new RuntimeDescription(rid))
.ToList(),
MSBuildStringUtility.Split(project.OuterBuild.GetProperty("RuntimeSupports"))
.Distinct(StringComparer.Ordinal)
.Select(s => new CompatibilityProfile(s))
.ToList()
),
Version = GetProjectVersion(project.OuterBuild),
RestoreSettings = new ProjectRestoreSettings()
{
SdkVersion = GetSdkVersion(project.OuterBuild)
}
};
return packageSpec;
}
/// <summary>
/// Apply <see cref="ISettings"/> to a <see cref="PackageSpec"/> created by <see cref="GetIntermediatePackageSpec"/>.
/// Fills in settings-dependent defaults for <see cref="ProjectRestoreMetadata.PackagesPath"/>,
/// <see cref="ProjectRestoreMetadata.Sources"/>, <see cref="ProjectRestoreMetadata.FallbackFolders"/>,
/// and <see cref="ProjectRestoreMetadata.ConfigFilePaths"/>.
/// </summary>
public static void ApplySettings(PackageSpec packageSpec, ISettings settings)
{
ProjectRestoreMetadata metadata = packageSpec.RestoreMetadata;
// PackagesPath: fill from settings if not set by the project
if (string.IsNullOrEmpty(metadata.PackagesPath))
{
metadata.PackagesPath = SettingsUtility.GetGlobalPackagesFolder(settings);
}
else
{
// Resolve relative path from the project file
string projectDirectory = Path.GetDirectoryName(metadata.ProjectPath) ?? throw new ArgumentException("Path.GetDirectoryName(metadata.ProjectPath) returned null");
metadata.PackagesPath = UriUtility.GetAbsolutePath(projectDirectory, metadata.PackagesPath);
}
// RepositoryPath (packages.config only): fill from settings if not set
if (metadata is PackagesConfigProjectRestoreMetadata pcMetadata && string.IsNullOrEmpty(pcMetadata.RepositoryPath))
{
pcMetadata.RepositoryPath = SettingsUtility.GetRepositoryPath(settings);
// Final fallback: solution-relative "packages" folder
if (string.IsNullOrEmpty(pcMetadata.RepositoryPath))
{
string projectDirectory = Path.GetDirectoryName(metadata.ProjectPath) ?? throw new ArgumentException("Path.GetDirectoryName.metadata.ProjectPath returned null");
pcMetadata.RepositoryPath = UriUtility.GetAbsolutePath(projectDirectory, PackagesConfig.PackagesNodeName);
}
}
// Sources: fill from settings if not set by the project.
// If the project used "Clear", the sources list contains the Clear marker — don't fill from settings.
ApplySettingsSources(metadata, settings);
// FallbackFolders: fill from settings if not set by the project.
// If the project used "Clear", the fallback folders list contains the Clear marker — don't fill from settings.
ApplySettingsFallbackFolders(metadata, settings);
// ConfigFilePaths: always set from settings
metadata.ConfigFilePaths = settings.GetConfigFilePaths();
}
private static void ApplySettingsSources(ProjectRestoreMetadata metadata, ISettings settings)
{
(List<string> sources, List<string> additionalSources) = SplitOnAdditionalMarker(
metadata.Sources?.Select(s => s.Source));
IEnumerable<string> processedSources;
if (sources.Count == 0)
{
processedSources = PackageSourceProvider.LoadPackageSources(settings)
.Where(e => e.IsEnabled)
.Select(e => e.Source);
}
else if (MSBuildRestoreUtility.ContainsClearKeyword(sources))
{
processedSources = Enumerable.Empty<string>();
}
else
{
processedSources = sources;
}
// Resolve relative source paths to absolute using the project directory.
// The intermediate PackageSpec stores raw relative paths; we resolve them here.
string projectDirectory = Path.GetDirectoryName(metadata.ProjectPath)!;
metadata.Sources = processedSources
.Concat(additionalSources)
.Select(s => new PackageSource(UriUtility.GetAbsolutePath(projectDirectory, s)!))
.ToList();
}
private static void ApplySettingsFallbackFolders(ProjectRestoreMetadata metadata, ISettings settings)
{
(List<string> fallbackFolders, List<string> additionalFallbackFolders) = SplitOnAdditionalMarker(
metadata.FallbackFolders);
IEnumerable<string> processedFallbackFolders;
if (fallbackFolders.Count == 0)
{
processedFallbackFolders = SettingsUtility.GetFallbackPackageFolders(settings);
}
else if (MSBuildRestoreUtility.ContainsClearKeyword(fallbackFolders))
{
processedFallbackFolders = Enumerable.Empty<string>();
}
else
{
processedFallbackFolders = fallbackFolders;
}
// Resolve relative fallback folder paths to absolute using the project directory.
string projectDirectory = Path.GetDirectoryName(metadata.ProjectPath)!;
metadata.FallbackFolders = processedFallbackFolders
.Concat(additionalFallbackFolders)
.Select(f => UriUtility.GetAbsolutePath(projectDirectory, f)!)
.ToList();
}
/// <summary>
/// Splits a list of entries on the <see cref="AdditionalValue"/> marker.
/// Entries before the marker are "main" entries; entries after are "additional" entries
/// that should always be appended regardless of whether main entries come from settings.
/// </summary>
private static (List<string> main, List<string> additional) SplitOnAdditionalMarker(IEnumerable<string>? entries)
{
var main = new List<string>();
var additional = new List<string>();
if (entries is not null)
{
bool readingAdditional = false;
foreach (string entry in entries)
{
if (StringComparer.Ordinal.Equals(AdditionalValue, entry))
{
readingAdditional = true;
}
else if (readingAdditional)
{
additional.Add(entry);
}
else
{
main.Add(entry);
}
}
}
return (main, additional);
}
/// <summary>
/// Gets the version of the project.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> representing the project.</param>
/// <returns>The <see cref="NuGetVersion" /> of the specified project if one was found, otherwise <see cref="PackageSpec.DefaultVersion" />.</returns>
internal static NuGetVersion GetProjectVersion(ITargetFramework project)
{
string? version = project.GetProperty("PackageVersion") ?? project.GetProperty("Version");
if (version == null)
{
return PackageSpec.DefaultVersion;
}
return NuGetVersion.Parse(version);
}
/// <summary>
/// Gets the .NET SDK version. If not specified, it will return null.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> representing the project.</param>
/// <returns>The <see cref="NuGetVersion" /> of the .NET SDK if one was found, otherwise <see langword="null">null</see>.</returns>
internal static NuGetVersion? GetSdkVersion(ITargetFramework project)
{
string? version = project.GetProperty("NETCoreSdkVersion");
if (version == null)
{
return null;
}
return NuGetVersion.Parse(version);
}
/// <summary>
/// Gets the restore metadata and target framework information for the specified project.
/// </summary>
/// <param name="project">An <see cref="IProject" /> representing the project.</param>
/// <returns>A <see cref="Tuple" /> containing the <see cref="ProjectRestoreMetadata" /> and <see cref="List{TargetFrameworkInformation}" /> for the specified project.</returns>
private static (ProjectRestoreMetadata? RestoreMetadata, List<TargetFrameworkInformation>? TargetFrameworkInfos) GetProjectRestoreMetadataAndTargetFrameworkInformation(IProject project)
{
ITargetFramework outerBuild = project.OuterBuild;
string projectName = GetProjectName(outerBuild);
string? outputPath = GetRestoreOutputPath(project);
(ProjectStyle projectStyle, string? packagesConfigFilePath) = GetProjectStyle(project);
(bool isCentralPackageManagementEnabled, bool isCentralPackageVersionOverrideDisabled, bool isCentralPackageTransitivePinningEnabled, bool isCentralPackageFloatingVersionsEnabled) =
GetCentralPackageManagementSettings(outerBuild, projectStyle);
RestoreAuditProperties? auditProperties = GetRestoreAuditProperties(project);
bool isPruningEnabledGlobally = GetPackagePruningDefault(project);
List<TargetFrameworkInformation> targetFrameworkInfos = GetTargetFrameworkInfos(project, isCentralPackageManagementEnabled, isPruningEnabledGlobally);
ProjectRestoreMetadata restoreMetadata;
if (projectStyle == ProjectStyle.PackagesConfig)
{
restoreMetadata = new PackagesConfigProjectRestoreMetadata
{
PackagesConfigPath = packagesConfigFilePath,
RepositoryPath = GetRepositoryPath(project),
RestoreAuditProperties = auditProperties,
};
}
else
{
restoreMetadata = new ProjectRestoreMetadata
{
// CrossTargeting is on, even if the TargetFrameworks property has only 1 tfm.
CrossTargeting = (projectStyle == ProjectStyle.PackageReference) && (
project.TargetFrameworks.Count > 1 || !string.IsNullOrWhiteSpace(project.OuterBuild.GetProperty("TargetFrameworks"))),
FallbackFolders = GetFallbackFolders(
SplitPropertyValueOrNull(outerBuild, "RestoreFallbackFolders"),
project.SplitGlobalPropertyValueOrNull("RestoreFallbackFolders"),
project.TargetFrameworks.Values.SelectMany(i => MSBuildStringUtility.Split(i.GetProperty("RestoreAdditionalProjectFallbackFolders"))),
project.TargetFrameworks.Values.SelectMany(i => MSBuildStringUtility.Split(i.GetProperty("RestoreAdditionalProjectFallbackFoldersExcludes")))),
SkipContentFileWrite = IsLegacyProject(outerBuild),
ValidateRuntimeAssets = outerBuild.IsPropertyTrue("ValidateRuntimeIdentifierCompatibility"),
CentralPackageVersionsEnabled = isCentralPackageManagementEnabled && projectStyle == ProjectStyle.PackageReference,
CentralPackageFloatingVersionsEnabled = isCentralPackageFloatingVersionsEnabled,
CentralPackageVersionOverrideDisabled = isCentralPackageVersionOverrideDisabled,
CentralPackageTransitivePinningEnabled = isCentralPackageTransitivePinningEnabled,
RestoreAuditProperties = auditProperties
};
}
restoreMetadata.CacheFilePath = NoOpRestoreUtilities.GetProjectCacheFilePath(outputPath, project.FullPath);
restoreMetadata.ConfigFilePaths = [];
restoreMetadata.OutputPath = outputPath;
targetFrameworkInfos.ForEach(tfi =>
{
// Validate the framework by always computing the short folder name.
// This will throw for invalid frameworks (e.g. "_,Version=2.0").
string shortFolderName = tfi.FrameworkName.GetShortFolderName();
restoreMetadata.OriginalTargetFrameworks.Add(
!string.IsNullOrEmpty(tfi.TargetAlias) ?
tfi.TargetAlias :
shortFolderName);
});
restoreMetadata.PackagesPath = GetPackagesPath(project);
restoreMetadata.ProjectName = projectName;
restoreMetadata.ProjectPath = project.FullPath;
restoreMetadata.ProjectStyle = projectStyle;
restoreMetadata.ProjectUniqueName = project.FullPath;
restoreMetadata.ProjectWideWarningProperties = WarningProperties.GetWarningProperties(outerBuild.GetProperty("TreatWarningsAsErrors"), outerBuild.GetProperty("WarningsAsErrors"), outerBuild.GetProperty("NoWarn"), outerBuild.GetProperty("WarningsNotAsErrors"));
restoreMetadata.RestoreLockProperties = new RestoreLockProperties(outerBuild.GetProperty("RestorePackagesWithLockFile"), outerBuild.GetProperty("NuGetLockFilePath"), outerBuild.IsPropertyTrue("RestoreLockedMode"));
restoreMetadata.Sources = GetSources(project);
restoreMetadata.TargetFrameworks = GetProjectRestoreMetadataFrameworkInfos(targetFrameworkInfos, project.TargetFrameworks);
restoreMetadata.UsingMicrosoftNETSdk = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(outerBuild.GetProperty("UsingMicrosoftNETSdk"));
restoreMetadata.SdkAnalysisLevel = MSBuildRestoreUtility.GetSdkAnalysisLevel(outerBuild.GetProperty("SdkAnalysisLevel"));
restoreMetadata.UseLegacyDependencyResolver = outerBuild.IsPropertyTrue("RestoreUseLegacyDependencyResolver");
restoreMetadata.RestoreDoNotWriteDependencyGraphSpec = outerBuild.IsPropertyTrue("RestoreDoNotWriteDependencyGraphSpec");
return (restoreMetadata, targetFrameworkInfos);
static (ProjectStyle, string? packagesConfigPath) GetProjectStyle(IProject project)
{
ProjectStyle? projectStyleOrNull = GetProjectRestoreStyleFromProjectProperty(project.OuterBuild.GetProperty("RestoreProjectStyle"));
bool hasPackageReferenceItems = project.TargetFrameworks.Values.Any(p => p.GetItems("PackageReference").Any());
string? projectName = project.OuterBuild.GetProperty("MSBuildProjectName");
if (projectName is null) throw new Exception("Something went wrong. MSBuildProjectName should always have a value, but did not.");
(ProjectStyle ProjectStyle, string? PackagesConfigFilePath) projectStyleResult =
GetProjectRestoreStyle(
restoreProjectStyle: projectStyleOrNull,
hasPackageReferenceItems: hasPackageReferenceItems,
projectDirectory: project.Directory,
projectName: projectName);
return (projectStyleResult.ProjectStyle, projectStyleResult.PackagesConfigFilePath);
}
}
/// <summary>
/// Gets the target framework information for the specified project. This includes the package references, package downloads, and framework references.
/// </summary>
/// <param name="project">An <see cref="IReadOnlyDictionary{NuGetFramework,ProjectInstance} "/> containing the projects by their target framework.</param>
/// <param name="isCpvmEnabled">A flag that is true if the Central Package Management was enabled.</param>
/// <param name="isPruningEnabledGlobally">A flag that tells us the default for pruning, if the pruning property is not set.</param>
/// <returns>A <see cref="List{TargetFrameworkInformation}" /> containing the target framework information for the specified project.</returns>
internal static List<TargetFrameworkInformation> GetTargetFrameworkInfos(IProject project, bool isCpvmEnabled, bool isPruningEnabledGlobally)
{
var targetFrameworkInfos = new List<TargetFrameworkInformation>(project.TargetFrameworks.Count);
foreach (var projectInnerNode in project.TargetFrameworks)
{
var msBuildProjectInstance = projectInnerNode.Value;
var targetAlias = string.IsNullOrEmpty(projectInnerNode.Key) ? string.Empty : projectInnerNode.Key;
NuGetFramework targetFramework = MSBuildProjectFrameworkUtility.GetProjectFramework(
projectFilePath: project.FullPath,
targetFrameworkMoniker: msBuildProjectInstance.GetProperty("TargetFrameworkMoniker"),
targetPlatformMoniker: msBuildProjectInstance.GetProperty("TargetPlatformMoniker"),
targetPlatformMinVersion: msBuildProjectInstance.GetProperty("TargetPlatformMinVersion"),
clrSupport: msBuildProjectInstance.GetProperty("CLRSupport"),
windowsTargetPlatformMinVersion: msBuildProjectInstance.GetProperty("WindowsTargetPlatformMinVersion"));
var packageTargetFallback = MSBuildStringUtility.Split(msBuildProjectInstance.GetProperty("PackageTargetFallback")).Select(NuGetFramework.Parse).ToList();
var assetTargetFallbackEnum = MSBuildStringUtility.Split(msBuildProjectInstance.GetProperty(nameof(TargetFrameworkInformation.AssetTargetFallback))).Select(NuGetFramework.Parse).ToList();
AssetTargetFallbackUtility.EnsureValidFallback(packageTargetFallback, assetTargetFallbackEnum, project.FullPath);
(targetFramework, ImmutableArray<NuGetFramework> imports, bool assetTargetFallback, bool warn) = AssetTargetFallbackUtility.GetFallbackFrameworkInformation(targetFramework, packageTargetFallback, assetTargetFallbackEnum);
IReadOnlyDictionary<string, CentralPackageVersion>? centralPackageVersions = null;
if (isCpvmEnabled)
{
centralPackageVersions = GetCentralPackageVersions(msBuildProjectInstance);
}
var dependencies = GetPackageReferences(msBuildProjectInstance, isCpvmEnabled, centralPackageVersions);
bool? restoreEnablePackagePruning = MSBuildStringUtility.GetBooleanOrNull(msBuildProjectInstance.GetProperty("RestoreEnablePackagePruning"));
bool isPackagePruningEnabled = restoreEnablePackagePruning == null ? isPruningEnabledGlobally : restoreEnablePackagePruning == true;
var prunedReferences = isPackagePruningEnabled ? GetPrunePackageReferences(msBuildProjectInstance) : [];
var targetFrameworkInformation = new TargetFrameworkInformation()
{
AssetTargetFallback = assetTargetFallback,
CentralPackageVersions = centralPackageVersions,
Dependencies = dependencies,
DownloadDependencies = GetPackageDownloads(msBuildProjectInstance).ToImmutableArray(),
FrameworkName = targetFramework,
Imports = imports,
FrameworkReferences = GetFrameworkReferences(msBuildProjectInstance),
PackagesToPrune = prunedReferences,
RuntimeIdentifierGraphPath = msBuildProjectInstance.GetProperty(nameof(TargetFrameworkInformation.RuntimeIdentifierGraphPath)),
TargetAlias = targetAlias,
Warn = warn
};
targetFrameworkInfos.Add(targetFrameworkInformation);
}
return targetFrameworkInfos;
}
private static bool GetPackagePruningDefault(IProject project)
{
foreach (var item in project.TargetFrameworks.NoAllocEnumerate())
{
if (item.Value.IsPropertyTrue("RestorePackagePruningDefault"))
{
return true;
}
}
return false;
}
private static RestoreAuditProperties? GetRestoreAuditProperties(IProject project)
{
string? enableAudit = project.OuterBuild.GetProperty("NuGetAudit");
string? auditLevel = project.OuterBuild.GetProperty("NuGetAuditLevel");
string? auditMode = GetAuditMode(project);
HashSet<string>? suppressionItems = GetAuditSuppressions(project.OuterBuild);
if (enableAudit != null || auditLevel != null || auditMode != null
|| (suppressionItems != null && suppressionItems.Count > 0))
{
return new RestoreAuditProperties()
{
EnableAudit = enableAudit,
AuditLevel = auditLevel,
AuditMode = auditMode,
SuppressedAdvisories = suppressionItems?.Count > 0 ? suppressionItems : null
};
}
return null;
// We want to set NuGetAuditMode to "all" if a multi-targeting project targets .NET 10 or higher.
// However, that can only be done by an "inner build" evaulation, but we read other audit settings
// from the project evaluation, not inner-builds. So, check the inner builds if any TFM sets mode
// to "all", otherwise use the project's "outer build" mode.
string? GetAuditMode(IProject project)
{
foreach (var item in project.TargetFrameworks.NoAllocEnumerate())
{
string? auditMode = item.Value.GetProperty("NuGetAuditMode");
if (string.Equals(auditMode, "all", StringComparison.OrdinalIgnoreCase))
{
return auditMode;
}
}
string? projectAuditMode = project.OuterBuild.GetProperty("NuGetAuditMode");
return projectAuditMode;
}
}
/// <summary>
/// Gets a value indicating if the specified project is a legacy project.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> representing the project.</param>
/// <returns><code>true</code> if the specified project is considered legacy, otherwise <code>false</code>.</returns>
internal static bool IsLegacyProject(ITargetFramework project)
{
// We consider the project to be legacy if it does not specify TargetFramework or TargetFrameworks
return project.GetProperty("TargetFrameworks") == null && project.GetProperty("TargetFramework") == null;
}
/// <summary>
/// Determines the restore style of a project.
/// </summary>
/// <param name="restoreProjectStyle">An optional user supplied restore style.</param>
/// <param name="hasPackageReferenceItems">A <see cref="bool"/> indicating whether or not the project has any PackageReference items.</param>
/// <param name="projectDirectory">The full path to the project directory.</param>
/// <param name="projectName">The name of the project file.</param>
/// <returns>A <see cref="Tuple{ProjectStyle, Boolean}"/> containing the project style and a value indicating if the project is using a style that is compatible with PackageReference.
/// If the value of <paramref name="restoreProjectStyle"/> is not empty and could not be parsed, <code>null</code> is returned.</returns>
private static (ProjectStyle ProjectStyle, string? PackagesConfigFilePath)
GetProjectRestoreStyle(ProjectStyle? restoreProjectStyle, bool hasPackageReferenceItems, string projectDirectory, string projectName)
{
ProjectStyle projectStyle;
string? packagesConfigFilePath = null;
// Allow a user to override by setting RestoreProjectStyle in the project.
if (restoreProjectStyle.HasValue)
{
projectStyle = restoreProjectStyle.Value;
}
else if (hasPackageReferenceItems)
{
// If any PackageReferences exist treat it as PackageReference. This has priority over project.json.
projectStyle = ProjectStyle.PackageReference;
}
else if (ProjectHasPackagesConfigFile(projectDirectory, projectName, out packagesConfigFilePath))
{
// If this is not a PackageReference or ProjectJson project check if packages.config or packages.ProjectName.config exists
projectStyle = ProjectStyle.PackagesConfig;
}
else
{
// This project is either a packages.config project or one that does not use NuGet at all.
projectStyle = ProjectStyle.Unknown;
}
return (projectStyle, packagesConfigFilePath);
}
/// <summary>
/// Determines if the project has a packages.config file.
/// </summary>
/// <param name="projectDirectory">The full path of the project directory.</param>
/// <param name="projectName">The name of the project file.</param>
/// <param name="packagesConfigPath">Receives the full path to the packages.config file if one exists, otherwise <code>null</code>.</param>
/// <returns><code>true</code> if a packages.config exists next to the project, otherwise <code>false</code>.</returns>
private static bool ProjectHasPackagesConfigFile(string projectDirectory, string projectName, [NotNullWhen(true)] out string? packagesConfigPath)
{
if (string.IsNullOrWhiteSpace(projectDirectory))
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(projectDirectory));
}
if (string.IsNullOrWhiteSpace(projectName))
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(projectName));
}
packagesConfigPath = GetPackagesConfigFilePath(projectDirectory, projectName);
return packagesConfigPath != null;
}
/// <summary>
/// Gets the path to a packages.config for the specified project if one exists.
/// </summary>
/// <param name="projectDirectory">The full path to the project directory.</param>
/// <param name="projectName">The name of the project file.</param>
/// <returns>The path to the packages.config file if one exists, otherwise <see langword="null" />.</returns>
/// <exception cref="ArgumentNullException"><paramref name="projectDirectory" /> -or- <paramref name="projectName" /> is <see langword="null" />.</exception>
private static string? GetPackagesConfigFilePath(string projectDirectory, string projectName)
{
if (string.IsNullOrWhiteSpace(projectDirectory))
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(projectDirectory));
}
if (string.IsNullOrWhiteSpace(projectName))
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(projectName));
}
string packagesConfigPath = Path.Combine(projectDirectory, NuGetConstants.PackageReferenceFile);
if (File.Exists(packagesConfigPath))
{
return packagesConfigPath;
}
packagesConfigPath = Path.Combine(projectDirectory, "packages." + projectName + ".config");
if (File.Exists(packagesConfigPath))
{
return packagesConfigPath;
}
return null;
}
/// <summary>
/// Gets the package fallback folders for a project.
/// </summary>
/// <param name="fallbackFolders">A <see cref="T:string[]" /> containing the fallback folders for the project.</param>
/// <param name="fallbackFoldersOverride">A <see cref="T:string[]" /> containing overrides for the fallback folders for the project.</param>
/// <param name="additionalProjectFallbackFolders">An <see cref="IEnumerable{String}" /> containing additional fallback folders for the project.</param>
/// <param name="additionalProjectFallbackFoldersExcludes">An <see cref="IEnumerable{String}" /> containing fallback folders to exclude.</param>
/// <returns>A <see cref="T:string[]" /> containing the package fallback folders for the project.</returns>
private static string[] GetFallbackFolders(string[]? fallbackFolders, string[]? fallbackFoldersOverride, IEnumerable<string> additionalProjectFallbackFolders, IEnumerable<string> additionalProjectFallbackFoldersExcludes)
{
// Fallback folders — keep as relative paths; ApplySettings will resolve to absolute.
var currentFallbackFolders = GetValue(
() => fallbackFoldersOverride,
() => MSBuildRestoreUtility.ContainsClearKeyword(fallbackFolders) ? new[] { MSBuildRestoreUtility.Clear } : null,
() => fallbackFolders);
// If the result contains Clear (e.g., override had Clear mixed in), collapse to just the marker.
if (currentFallbackFolders != null && MSBuildRestoreUtility.ContainsClearKeyword(currentFallbackFolders))
{
currentFallbackFolders = new[] { MSBuildRestoreUtility.Clear };
}
// Append additional fallback folders after removing excluded folders
var filteredAdditionalProjectFallbackFolders = MSBuildRestoreUtility.AggregateSources(
values: additionalProjectFallbackFolders,
excludeValues: additionalProjectFallbackFoldersExcludes);
// When no fallback folders are defined by MSBuild properties, return empty. ApplySettings will fill from ISettings later.
return AppendItems(currentFallbackFolders ?? Array.Empty<string>(), filteredAdditionalProjectFallbackFolders);
}
/// <summary>
/// Gets the packages path from project properties only, without falling back to <see cref="ISettings"/>.
/// Returns <see langword="null"/> if the project does not specify <c>RestorePackagesPath</c>.
/// </summary>
private static string? GetPackagesPath(IProject project)
{
return GetValue(
() => project.GetGlobalProperty("RestorePackagesPath"),
() => project.OuterBuild.GetProperty("RestorePackagesPath"));
}
/// <summary>
/// Determines the current settings for central package management for the specified project.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> to get the central package management settings from.</param>
/// <param name="projectStyle">The <see cref="ProjectStyle?" /> of the specified project. Specify <see langword="null" /> when the project does not define a restore style.</param>
/// <returns>A <see cref="Tuple{T1, T2, T3, T4}" /> containing values indicating whether or not central package management is enabled, if the ability to override a package version </returns>
private static (bool IsEnabled, bool IsVersionOverrideDisabled, bool IsCentralPackageTransitivePinningEnabled, bool isCentralPackageFloatingVersionsEnabled)
GetCentralPackageManagementSettings(ITargetFramework project, ProjectStyle projectStyle)
{
if (projectStyle == ProjectStyle.PackageReference)
{
bool isEnabled =
MSBuildStringUtility.IsTrue(project.GetProperty("BuildingInsideVisualStudio"))
? IsPropertyTrue(project, "ManagePackageVersionsCentrally")
: IsPropertyTrue(project, "_CentralPackageVersionsEnabled");
bool isVersionOverrideDisabled = IsPropertyFalse(project, "CentralPackageVersionOverrideEnabled");
bool isCentralPackageTransitivePinningEnabled = IsPropertyTrue(project, "CentralPackageTransitivePinningEnabled");
bool isCentralPackageFloatingVersionsEnabled = IsPropertyTrue(project, "CentralPackageFloatingVersionsEnabled");
return (isEnabled, isVersionOverrideDisabled, isCentralPackageTransitivePinningEnabled, isCentralPackageFloatingVersionsEnabled);
}
return (false, false, false, false);
}
/// <summary>
/// Gets the name of the specified project.
/// </summary>
/// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
/// <returns>The name of the specified project.</returns>
private static string GetProjectName(ITargetFramework project)
{
string? packageId = project.GetProperty("PackageId");
if (!string.IsNullOrWhiteSpace(packageId))
{
// If the PackageId property was specified, return that
return packageId!;
}
string? assemblyName = project.GetProperty("AssemblyName");
if (!string.IsNullOrWhiteSpace(assemblyName))
{
// If the AssemblyName property was specified, return that
return assemblyName!;
}
// By default return the MSBuildProjectName which is a built-in property that represents the name of the project file without the file extension
string? projectName = project.GetProperty("MSBuildProjectName");
if (!string.IsNullOrWhiteSpace(projectName))
{
return projectName!;
}
throw new Exception("Something went wrong. MSBuildProjectName should always have a value, but did not.");
}
/// <summary>
/// Try to parse the <paramref name="restoreProjectStyleProperty"/> and return the <see cref="ProjectStyle"/> value.
/// </summary>
/// <param name="restoreProjectStyleProperty">The value of the RestoreProjectStyle property value. It can be null.</param>
/// <returns>The <see cref="ProjectStyle"/>. If the <paramref name="restoreProjectStyleProperty"/> is null the return vale will be null. </returns>
private static ProjectStyle? GetProjectRestoreStyleFromProjectProperty(string? restoreProjectStyleProperty)
{
ProjectStyle projectStyle;
// Allow a user to override by setting RestoreProjectStyle in the project.
if (!string.IsNullOrWhiteSpace(restoreProjectStyleProperty))
{
if (!Enum.TryParse(restoreProjectStyleProperty, ignoreCase: true, out projectStyle))
{
projectStyle = ProjectStyle.Unknown;
}
return projectStyle;
}
return null;
}
/// <summary>
/// Gets the restore metadata framework information for the specified projects.
/// </summary>
/// <param name="projects">A <see cref="IReadOnlyDictionary{NuGetFramework,ProjectInstance}" /> representing the target frameworks and their corresponding projects.</param>
/// <returns>A <see cref="List{ProjectRestoreMetadataFrameworkInfo}" /> containing the restore metadata framework information for the specified project.</returns>
internal static List<ProjectRestoreMetadataFrameworkInfo> GetProjectRestoreMetadataFrameworkInfos(List<TargetFrameworkInformation> targetFrameworkInfos, IReadOnlyDictionary<string, ITargetFramework> projects)
{
var projectRestoreMetadataFrameworkInfos = new List<ProjectRestoreMetadataFrameworkInfo>(projects.Count);
foreach (var targetFrameworkInfo in targetFrameworkInfos)
{
var project = projects[targetFrameworkInfo.TargetAlias];
projectRestoreMetadataFrameworkInfos.Add(new ProjectRestoreMetadataFrameworkInfo(targetFrameworkInfo.FrameworkName)
{
TargetAlias = targetFrameworkInfo.TargetAlias,
ProjectReferences = GetProjectReferences(project)
});
}
return projectRestoreMetadataFrameworkInfos;
}
/// <summary>
/// Gets the project references of the specified project.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> to get project references for.</param>
/// <returns>A <see cref="List{ProjectRestoreReference}" /> containing the project references for the specified project.</returns>
internal static List<ProjectRestoreReference> GetProjectReferences(ITargetFramework project)
{
// Get the unique list of ProjectReference items that have the ReferenceOutputAssembly metadata set to "true", ignoring duplicates
var projectReferenceItems = project.GetItems("ProjectReference")
.Where(i => i.IsMetadataTrue("ReferenceOutputAssembly", defaultValue: true))
.Distinct(ProjectItemIdentityComparer.Default)
.ToList();
var projectReferences = new List<ProjectRestoreReference>(projectReferenceItems.Count);
foreach (var projectReferenceItem in projectReferenceItems)
{
string? fullPath = projectReferenceItem.GetMetadata("FullPath");
projectReferences.Add(new ProjectRestoreReference
{
ExcludeAssets = GetLibraryIncludeFlags(projectReferenceItem.GetMetadata("ExcludeAssets"), LibraryIncludeFlags.None),
IncludeAssets = GetLibraryIncludeFlags(projectReferenceItem.GetMetadata("IncludeAssets"), LibraryIncludeFlags.All),
PrivateAssets = GetLibraryIncludeFlags(projectReferenceItem.GetMetadata("PrivateAssets"), LibraryIncludeFlagUtils.DefaultSuppressParent),
ProjectPath = fullPath,
ProjectUniqueName = fullPath
});
}
return projectReferences;
}
/// <summary>
/// Gets the package references for the specified project.
/// </summary>
/// <param name="project">The <see cref="ProjectInstance" /> to get package references for.</param>
/// <param name="isCentralPackageVersionManagementEnabled">A flag for central package version management being enabled.</param>
/// <returns>A <see cref="List{LibraryDependency}" /> containing the package references for the specified project.</returns>
internal static ImmutableArray<LibraryDependency> GetPackageReferences(ITargetFramework project, bool isCentralPackageVersionManagementEnabled, IReadOnlyDictionary<string, CentralPackageVersion>? centralPackageVersions)
{
// Get the distinct PackageReference items, ignoring duplicates
List<IItem> packageReferenceItems = GetDistinctItemsOrEmpty(project, "PackageReference").ToList();
var libraryDependencies = new LibraryDependency[packageReferenceItems.Count];
for (int i = 0; i < packageReferenceItems.Count; i++)
{
var packageReferenceItem = packageReferenceItems[i];
bool autoReferenced = packageReferenceItem.IsMetadataTrue("IsImplicitlyDefined");
string? version = packageReferenceItem.GetMetadata("Version");
VersionRange? versionRange = string.IsNullOrWhiteSpace(version) ? null : VersionRange.Parse(version!);
bool versionDefined = versionRange != null;
if (versionRange == null && !isCentralPackageVersionManagementEnabled)
{
versionRange = VersionRange.All;
}
string? versionOverrideString = packageReferenceItem.GetMetadata("VersionOverride");
var versionOverrideRange = string.IsNullOrWhiteSpace(versionOverrideString) ? null : VersionRange.Parse(versionOverrideString!);
CentralPackageVersion? centralPackageVersion = null;
bool isCentrallyManaged = !versionDefined && !autoReferenced && isCentralPackageVersionManagementEnabled && versionOverrideRange == null && centralPackageVersions != null && centralPackageVersions.TryGetValue(packageReferenceItem.Identity, out centralPackageVersion);
if (isCentrallyManaged)
{
versionRange = centralPackageVersion!.VersionRange;
}
versionRange = versionOverrideRange ?? versionRange;
ImmutableArray<NuGetLogCode> noWarn = MSBuildStringUtility.GetNuGetLogCodes(packageReferenceItem.GetMetadata("NoWarn"));
libraryDependencies[i] = new LibraryDependency()
{
AutoReferenced = autoReferenced,
GeneratePathProperty = packageReferenceItem.IsMetadataTrue("GeneratePathProperty"),
Aliases = packageReferenceItem.GetMetadata("Aliases"),
IncludeType = GetLibraryIncludeFlags(packageReferenceItem.GetMetadata("IncludeAssets"), LibraryIncludeFlags.All) & ~GetLibraryIncludeFlags(packageReferenceItem.GetMetadata("ExcludeAssets"), LibraryIncludeFlags.None),
LibraryRange = new LibraryRange(
packageReferenceItem.Identity,
versionRange,
LibraryDependencyTarget.Package),
SuppressParent = GetLibraryIncludeFlags(packageReferenceItem.GetMetadata("PrivateAssets"), LibraryIncludeFlagUtils.DefaultSuppressParent),
VersionOverride = versionOverrideRange,
NoWarn = noWarn,
VersionCentrallyManaged = isCentrallyManaged,
};
}
return ImmutableCollectionsMarshal.AsImmutableArray(libraryDependencies);
}
internal static Dictionary<string, PrunePackageReference> GetPrunePackageReferences(ITargetFramework project)
{
var result = new Dictionary<string, PrunePackageReference>(StringComparer.OrdinalIgnoreCase);
IEnumerable<IItem> PrunePackageReferences = GetDistinctItemsOrEmpty(project, "PrunePackageReference");
foreach (var projectItemInstance in PrunePackageReferences)
{
string id = projectItemInstance.Identity;
string? versionString = projectItemInstance.GetMetadata("Version") ?? string.Empty;
result.Add(id, PrunePackageReference.Create(id, versionString));
}
return result;
}
/// <summary>
/// Gets the package downloads for the specified project.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> to get package downloads for.</param>
/// <returns>An <see cref="IEnumerable{DownloadDependency}" /> containing the package downloads for the specified project.</returns>
internal static IEnumerable<DownloadDependency> GetPackageDownloads(ITargetFramework project)
{
// Get the distinct PackageDownload items, ignoring duplicates
foreach (IItem projectItemInstance in GetDistinctItemsOrEmpty(project, "PackageDownload"))
{
string id = projectItemInstance.Identity;
// PackageDownload items can contain multiple versions
string? versionRanges = projectItemInstance.GetMetadata("Version");
if (string.IsNullOrEmpty(versionRanges))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Error_PackageDownload_NoVersion, id));
}
foreach (var version in MSBuildStringUtility.Split(versionRanges))
{
// Validate the version range
VersionRange versionRange = !string.IsNullOrWhiteSpace(version) ? VersionRange.Parse(version) : VersionRange.All;
if (!(versionRange.HasLowerAndUpperBounds && versionRange.MinVersion.Equals(versionRange.MaxVersion)))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Error_PackageDownload_OnlyExactVersionsAreAllowed, id, versionRange.OriginalString));
}
yield return new DownloadDependency(id, versionRange);
}
}
}
/// <summary>
/// Gets the framework references per target framework for the specified project.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> to get framework references for.</param>
/// <returns>A <see cref="List{FrameworkDependency}" /> containing the framework references for the specified project.</returns>
internal static IReadOnlyCollection<FrameworkDependency>? GetFrameworkReferences(ITargetFramework project)
{
// Get the unique FrameworkReference items, ignoring duplicates
List<IItem> frameworkReferenceItems = GetDistinctItemsOrEmpty(project, "FrameworkReference").ToList();
if (frameworkReferenceItems.Count == 0)
{
return null;
}
// For best performance, its better to create a list with the exact number of items needed rather than using a LINQ statement or AddRange. This is because if the list
// is not allocated with enough items, the list has to be grown which can slow things down
var frameworkDependencies = new FrameworkDependency[frameworkReferenceItems.Count];
for (int i = 0; i < frameworkReferenceItems.Count; i++)
{
var frameworkReferenceItem = frameworkReferenceItems[i];
var privateAssets = MSBuildStringUtility.Split(frameworkReferenceItem.GetMetadata("PrivateAssets"));
frameworkDependencies[i] = new FrameworkDependency(frameworkReferenceItem.Identity, FrameworkDependencyFlagsUtils.GetFlags(privateAssets));
}
return frameworkDependencies;
}
/// <summary>
/// Gets the <see cref="LibraryIncludeFlags" /> for the specified value.
/// </summary>
/// <param name="value">A semicolon delimited list of include flags.</param>
/// <param name="defaultValue">The default value ot return if the value contains no flags.</param>
/// <returns>The <see cref="LibraryIncludeFlags" /> for the specified value, otherwise the <paramref name="defaultValue" />.</returns>
private static LibraryIncludeFlags GetLibraryIncludeFlags(string? value, LibraryIncludeFlags defaultValue)
{
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
string[] parts = MSBuildStringUtility.Split(value);
return parts.Length > 0 ? LibraryIncludeFlagUtils.GetFlags(parts) : defaultValue;
}
/// <summary>
/// Gets the repository path for the specified project.
/// </summary>
/// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
/// <returns>The repository path of the specified project.</returns>
private static string GetRepositoryPath(IProject project)
{
var path = GetValue(
() => UriUtility.GetAbsolutePath(project.Directory, project.GetGlobalProperty("RestoreRepositoryPath")),
() => UriUtility.GetAbsolutePath(project.Directory, project.OuterBuild.GetProperty("RestoreRepositoryPath")),
() =>
{
string? solutionDir = project.OuterBuild.GetProperty("SolutionPath");
solutionDir = string.Equals(solutionDir, "*Undefined*", StringComparison.OrdinalIgnoreCase)
? project.Directory
: Path.GetDirectoryName(solutionDir)!;
return UriUtility.GetAbsolutePath(solutionDir, PackagesConfig.PackagesNodeName);
});
// GetValue doesn't understand that the last func will always provide a non-null value.
return path!;
}
/// <summary>
/// Gets the centrally defined package version information.
/// </summary>
/// <param name="project">The <see cref="ProjectInstance" /> to get PackageVersion for.</param>
/// <returns>An <see cref="IEnumerable{CentralPackageVersion}" /> containing the package versions for the specified project.</returns>
private static Dictionary<string, CentralPackageVersion> GetCentralPackageVersions(ITargetFramework project)
{
var result = new Dictionary<string, CentralPackageVersion>(StringComparer.OrdinalIgnoreCase);
IEnumerable<IItem> packageVersionItems = GetDistinctItemsOrEmpty(project, "PackageVersion");
foreach (var projectItemInstance in packageVersionItems)
{
string id = projectItemInstance.Identity;
string? version = projectItemInstance.GetMetadata("Version");
VersionRange versionRange = string.IsNullOrWhiteSpace(version) ? VersionRange.All : VersionRange.Parse(version!);
result.Add(id, new CentralPackageVersion(id, versionRange));
}
return result;
}
/// <summary>
/// Gets the package sources of the specified project.
/// </summary>
/// <param name="project">An <see cref="IProject" /> representing the project..</param>
/// <returns>A <see cref="List{PackageSource}" /> object containing the packages sources for the specified project.</returns>
internal static List<PackageSource> GetSources(IProject project)
{
return GetSources(
project.GetGlobalProperty("OriginalMSBuildStartupDirectory"),
project.Directory,
project.OuterBuild.SplitPropertyValueOrNull("RestoreSources"),
project.SplitGlobalPropertyValueOrNull("RestoreSources"),
project.TargetFrameworks.Values.SelectMany(i => MSBuildStringUtility.Split(i.GetProperty("RestoreAdditionalProjectSources"))))
.Select(i => new PackageSource(i))
.ToList();
}
private static string[] GetSources(string? startupDirectory, string projectDirectory, string[]? sources, string[]? sourcesOverride, IEnumerable<string> additionalProjectSources)
{
// Sources — keep as relative paths; ApplySettings will resolve to absolute.
// Exception: override sources must be resolved against startupDirectory here,
// because ApplySettings only knows projectDirectory.
var currentSources = GetValue(
() => sourcesOverride?.Select(MSBuildRestoreUtility.FixSourcePath).Select(e => UriUtility.GetAbsolutePath(startupDirectory, e)!).ToArray(),
() => MSBuildRestoreUtility.ContainsClearKeyword(sources) ? new[] { MSBuildRestoreUtility.Clear } : null,
() => sources?.Select(MSBuildRestoreUtility.FixSourcePath).ToArray());
// If the result contains Clear (e.g., override had Clear mixed in), collapse to just the marker.
if (currentSources != null && MSBuildRestoreUtility.ContainsClearKeyword(currentSources))
{
currentSources = new[] { MSBuildRestoreUtility.Clear };
}
// Append additional sources
// Escape strings to avoid xplat path issues with msbuild.
var filteredAdditionalProjectSources = MSBuildRestoreUtility.AggregateSources(
values: additionalProjectSources,
excludeValues: Enumerable.Empty<string>())
.Select(MSBuildRestoreUtility.FixSourcePath);
// When no sources are defined by MSBuild properties, return empty. ApplySettings will fill from ISettings later.
return AppendItems(currentSources ?? Array.Empty<string>(), filteredAdditionalProjectSources);
}
/// <summary>
/// Marker value that separates main entries from additional entries in the combined sources/fallback folders list.
/// Must match <c>VSRestoreSettingsUtilities.AdditionalValue</c>.
/// </summary>
internal const string AdditionalValue = "$Additional$";
private static string[] AppendItems(string[] current, IEnumerable<string>? additional)
{
if (additional == null || !additional.Any())
{
// noop
return current;
}
return current.Concat(new[] { AdditionalValue }).Concat(additional).ToArray();
}
/// <summary>
/// Return the value from the first function that returns non-null.
/// </summary>
private static T? GetValue<T>(params Func<T>[] funcs)
{
var result = default(T);
// Run until a value is returned from a function.
#pragma warning disable CS8604 // Possible null reference argument. net framework and netstandard BCLs don't have nullable annotations
for (var i = 0; EqualityComparer<T>.Default.Equals(result, default) && i < funcs.Length; i++)
#pragma warning restore CS8604 // Possible null reference argument.
{
result = funcs[i]();
}
return result;
}
private static HashSet<string>? GetAuditSuppressions(ITargetFramework project)
{
IEnumerable<string> suppressions = GetDistinctItemsOrEmpty(project, "NuGetAuditSuppress")
.Select(i => i.Identity);
return new HashSet<string>(suppressions, StringComparer.Ordinal);
}
/// <summary>
/// Returns the list of distinct items with the <paramref name="itemName"/> name.
/// Two items are equal if they have the same <see cref="IItem.Identity"/>.
/// </summary>
/// <param name="project">The project.</param>
/// <param name="itemName">The item name.</param>
/// <returns>Returns the list of items with the <paramref name="itemName"/>. If the item does not exist it will return an empty list.</returns>
private static IEnumerable<IItem> GetDistinctItemsOrEmpty(ITargetFramework project, string itemName)
{
return project.GetItems(itemName)?.Distinct(ProjectItemIdentityComparer.Default) ?? Enumerable.Empty<IItem>();
}
/// <summary>
/// Gets the restore output path for the specified project.
/// </summary>
/// <param name="project">The <see cref="IProject" /> representing the project.</param>
/// <returns>The full path to the restore output directory for the specified project if a value is specified, otherwise <code>null</code>.</returns>
internal static string? GetRestoreOutputPath(IProject project)
{
string? outputPath = project.OuterBuild.GetProperty("RestoreOutputPath") ?? project.OuterBuild.GetProperty("MSBuildProjectExtensionsPath");
return outputPath == null ? null : Path.GetFullPath(Path.Combine(project.Directory, outputPath));
}
internal static bool IsPropertyTrue(this ITargetFramework project, string propertyName, bool defaultValue = false)
{
string? value = project.GetProperty(propertyName);
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
return string.Equals(value!.Trim(), bool.TrueString, StringComparison.OrdinalIgnoreCase);
}
internal static bool IsPropertyFalse(this ITargetFramework project, string propertyName, bool defaultValue = false)
{
string? value = project.GetProperty(propertyName);
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
return string.Equals(value!.Trim(), bool.FalseString, StringComparison.OrdinalIgnoreCase);
}
internal static bool IsMetadataTrue(this IItem item, string metadataName, bool defaultValue = false)
{
string? value = item.GetMetadata(metadataName);
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
return string.Equals(value, bool.TrueString, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Splits the value of the specified property and returns an array if the property has a value, otherwise returns <code>null</code>.
/// </summary>
/// <param name="project">The <see cref="ITargetFramework" /> to get the property value from.</param>
/// <param name="name">The name of the property to get the value of and split.</param>
/// <returns>A <see cref="T:string[]" /> containing the split value of the property if the property had a value, otherwise <code>null</code>.</returns>
private static string[]? SplitPropertyValueOrNull(this ITargetFramework project, string name)
{
string? value = project.GetProperty(name);
return value is null ? null : MSBuildStringUtility.Split(value);
}
/// <summary>
/// Splits the value of the specified global property and returns an array if the property has a value, otherwise returns <code>null</code>.
/// </summary>
/// <param name="project">The <see cref="IProject" /> to get the global property value from.</param>
/// <param name="name">The name of the global property to get the value of and split.</param>
/// <returns>A <see cref="T:string[]" /> containing the split value of the global property if it had a value, otherwise <code>null</code>.</returns>
private static string[]? SplitGlobalPropertyValueOrNull(this IProject project, string name)
{
string? value = project.GetGlobalProperty(name);
return value is null ? null : MSBuildStringUtility.Split(value);
}
}
}
|