|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using NuGet.Common;
using NuGet.DependencyResolver;
using NuGet.LibraryModel;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Repositories;
using NuGet.Versioning;
using XmlUtility = NuGet.Shared.XmlUtility;
namespace NuGet.Commands
{
public static class BuildAssetsUtils
{
private static readonly XNamespace Namespace = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
internal const string CrossTargetingCondition = "'$(TargetFramework)' == ''";
internal const string TargetFrameworkCondition = "'$(TargetFramework)' == '{0}'";
internal const string LanguageCondition = "'$(Language)' == '{0}'";
internal const string NegativeLanguageCondition = "'$(Language)' != '{0}'";
internal const string ExcludeAllCondition = "'$(ExcludeRestorePackageImports)' != 'true'";
public const string TargetsExtension = ".targets";
public const string PropsExtension = ".props";
/// <summary>
/// This value is written into generated Restore props files, but
/// if it changes across tooling that ships different NuGet versions
/// (like Visual Studio and the dotnet CLI) it can cause unintentional
/// problems with MSBuild incrementality. As a result, we set it to a new,
/// fixed value higher than the current NuGet version (6.14.x) and
/// never change it again, so that starting in .NET 10 onwards this
/// won't cause rebuilds.
///
/// We could remove the property entirely, but there are some uses of it
/// on public GitHub that aren't just from checked-in generated files, so
/// keeping it around but stable is the most compatible option.
/// </summary>
internal const string PermanentNuGetToolsVersionValue = "7.0.0";
/// <summary>
/// The macros that we may use in MSBuild to replace path roots.
/// </summary>
public static readonly string[] MacroCandidates = new[]
{
"UserProfile", // e.g. C:\users\myusername
};
/// <summary>
/// Write XML to disk.
/// Delete files which do not have new XML.
/// </summary>
public static void WriteFiles(IEnumerable<MSBuildOutputFile> files, ILogger log)
{
foreach (var file in files)
{
if (file.Content == null)
{
// Remove the file if the XML is null
FileUtility.Delete(file.Path);
}
else
{
log.LogInformation(string.Format(CultureInfo.CurrentCulture, Strings.Log_GeneratingMsBuildFile, file.Path));
// Create the directory if it doesn't exist
Directory.CreateDirectory(Path.GetDirectoryName(file.Path));
// Write out XML file
WriteXML(file.Path, file.Content);
}
}
}
/// <summary>
/// Create MSBuild targets and props files.
/// Null will be returned for files that should be removed.
/// </summary>
public static List<MSBuildOutputFile> GenerateMultiTargetFailureFiles(
string targetsPath,
string propsPath,
ProjectStyle restoreType)
{
XDocument targetsXML = null;
XDocument propsXML = null;
// Create an error file for MSBuild to stop the build.
targetsXML = GenerateMultiTargetFrameworkWarning();
if (restoreType == ProjectStyle.PackageReference)
{
propsXML = GenerateEmptyImportsFile();
}
var files = new List<MSBuildOutputFile>()
{
new MSBuildOutputFile(propsPath, propsXML),
new MSBuildOutputFile(targetsPath, targetsXML),
};
return files;
}
private static string ReplacePathsWithMacros(string path, IEnvironmentVariableReader environmentVariableReader)
{
foreach (var macroName in MacroCandidates)
{
string macroValue = environmentVariableReader.GetEnvironmentVariable(macroName);
if (!string.IsNullOrEmpty(macroValue)
&& path.StartsWith(macroValue, StringComparison.OrdinalIgnoreCase))
{
path = $"$({macroName})" + $"{path.Substring(macroValue.Length)}";
}
break;
}
return path;
}
public static XDocument GenerateMultiTargetFrameworkWarning()
{
var doc = GenerateEmptyImportsFile();
doc.Root.Add(new XElement(Namespace + "Target",
new XAttribute("Name", "EmitMSBuildWarning"),
new XAttribute("BeforeTargets", "Build"),
new XElement(Namespace + "Warning",
new XAttribute("Text", Strings.MSBuildWarning_MultiTarget))));
return doc;
}
/// <summary>
/// Add standard properties to only props file if it exists, otherwise the targets.
/// </summary>
public static void AddNuGetPropertiesToFirstImport(IEnumerable<MSBuildOutputFile> files,
IEnumerable<string> packageFolders,
string repositoryRoot,
ProjectStyle projectStyle,
string assetsFilePath,
bool success)
{
// For project.json not all files are written out. Find the first one
// or if no files exist skip this.
var firstImport = files.Where(file => file.Content != null)
.OrderByDescending(file => file.Path.EndsWith(PropsExtension, StringComparison.Ordinal) ? 1 : 0)
.FirstOrDefault();
if (firstImport != null)
{
// Write the assets file path to the props file in an MSBuild resolvable manner.
// This allows the project to be moved and avoid a large number of project errors
// until restore can run again.
var resolvableAssetsFilePath = @"$(MSBuildThisFileDirectory)" + Path.GetFileName(assetsFilePath);
AddNuGetProperties(firstImport.Content, packageFolders, repositoryRoot, projectStyle, resolvableAssetsFilePath, success);
}
}
/// <summary>
/// Apply standard properties in a property group.
/// Additionally add a SourceRoot item to point to the package folders.
/// </summary>
public static void AddNuGetProperties(
XDocument doc,
IEnumerable<string> packageFolders,
string repositoryRoot,
ProjectStyle projectStyle,
string assetsFilePath,
bool success)
{
AddNuGetProperties(doc, packageFolders, repositoryRoot, projectStyle, assetsFilePath, success, EnvironmentVariableWrapper.Instance);
}
internal static void AddNuGetProperties(
XDocument doc,
IEnumerable<string> packageFolders,
string repositoryRoot,
ProjectStyle projectStyle,
string assetsFilePath,
bool success,
IEnvironmentVariableReader environmentVariableReader)
{
doc.Root.AddFirst(
new XElement(Namespace + "PropertyGroup",
new XAttribute("Condition", $" {ExcludeAllCondition} "),
GenerateProperty("RestoreSuccess", success.ToString(CultureInfo.CurrentCulture)),
GenerateProperty("RestoreTool", "NuGet"),
GenerateProperty("ProjectAssetsFile", assetsFilePath),
GenerateProperty("NuGetPackageRoot", ReplacePathsWithMacros(repositoryRoot, environmentVariableReader)),
GenerateProperty("NuGetPackageFolders", string.Join(";", packageFolders)),
GenerateProperty("NuGetProjectStyle", projectStyle.ToString()),
GenerateProperty("NuGetToolVersion", PermanentNuGetToolsVersionValue)),
new XElement(Namespace + "ItemGroup",
new XAttribute("Condition", $" {ExcludeAllCondition} "),
packageFolders.Select(e => GenerateItem("SourceRoot", PathUtility.EnsureTrailingSlash(e)))));
}
/// <summary>
/// Get empty file with the base properties.
/// </summary>
public static XDocument GenerateEmptyImportsFile()
{
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", "no"),
new XElement(Namespace + "Project",
new XAttribute("ToolsVersion", "14.0"),
new XAttribute("xmlns", Namespace.NamespaceName)));
return doc;
}
public static XElement GenerateProperty(string propertyName, string content)
{
return new XElement(Namespace + propertyName,
new XAttribute("Condition", $" '$({propertyName})' == '' "),
content);
}
internal static XElement GenerateItem(string itemName, string path)
{
return new XElement(Namespace + itemName, new XAttribute("Include", path));
}
public static XElement GenerateImport(string path)
{
return new XElement(Namespace + "Import",
new XAttribute("Project", path),
new XAttribute("Condition", $"Exists('{path}')"));
}
public static XElement GenerateContentFilesItem(string path, LockFileContentFile item, string packageId, string packageVersion)
{
var entry = new XElement(Namespace + item.BuildAction.Value,
new XAttribute("Include", path),
new XAttribute("Condition", $"Exists('{path}')"),
new XElement(Namespace + "NuGetPackageId", packageId),
new XElement(Namespace + "NuGetPackageVersion", packageVersion),
new XElement(Namespace + "NuGetItemType", item.BuildAction),
new XElement(Namespace + "Pack", false));
var privateFlag = false;
if (item.CopyToOutput)
{
var outputPath = item.OutputPath ?? item.PPOutputPath;
if (outputPath != null)
{
// Convert / to \
outputPath = LockFileUtils.ToDirectorySeparator(outputPath);
privateFlag = true;
entry.Add(new XElement(Namespace + "CopyToOutputDirectory", "PreserveNewest"));
entry.Add(new XElement(Namespace + "TargetPath", outputPath));
var destinationSubDirectory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(destinationSubDirectory))
{
entry.Add(new XElement(Namespace + "DestinationSubDirectory", destinationSubDirectory + Path.DirectorySeparatorChar));
}
}
}
entry.Add(new XElement(Namespace + "Private", privateFlag.ToString(CultureInfo.CurrentCulture)));
// Remove contentFile/lang/tfm/ from start of the path
var linkPath = string.Join(string.Empty + Path.DirectorySeparatorChar,
item.Path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(3)
.ToArray());
if (linkPath != null)
{
entry.Add(new XElement(Namespace + "Link", linkPath));
}
return entry;
}
/// <summary>
/// Returns null if the result should not exist on disk.
/// </summary>
public static XDocument GenerateMSBuildFile(List<MSBuildRestoreItemGroup> groups, ProjectStyle outputType)
{
XDocument doc = null;
// Always write out netcore props/targets. For project.json only write the file if it has items.
if (outputType == ProjectStyle.PackageReference || groups.SelectMany(e => e.Items).Any())
{
doc = GenerateEmptyImportsFile();
// Add import groups, order by position, then by the conditions to keep the results deterministic
// Skip empty groups
foreach (var group in groups
.Where(e => e.Items.Count > 0)
.OrderBy(e => e.Position)
.ThenBy(e => e.Condition, StringComparer.OrdinalIgnoreCase))
{
var itemGroup = new XElement(Namespace + group.RootName, group.Items);
// Add a conditional statement if multiple TFMs exist or cross targeting is present
var conditionValue = group.Condition;
if (!string.IsNullOrEmpty(conditionValue))
{
itemGroup.Add(new XAttribute("Condition", conditionValue));
}
// Add itemgroup to file
doc.Root.Add(itemGroup);
}
}
return doc;
}
public static void WriteXML(string path, XDocument doc)
{
FileUtility.Replace((outputPath) =>
{
using (var output = new FileStream(outputPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
doc.Save(output);
}
},
path);
}
public static string GetPathWithMacros(string absolutePath, string repositoryRoot)
{
return GetPathWithMacros(absolutePath, repositoryRoot, EnvironmentVariableWrapper.Instance);
}
internal static string GetPathWithMacros(string absolutePath, string repositoryRoot, IEnvironmentVariableReader environmentVariableReader)
{
var path = absolutePath;
if (absolutePath.StartsWith(repositoryRoot, StringComparison.Ordinal))
{
path = $"$(NuGetPackageRoot){absolutePath.Substring(repositoryRoot.Length)}";
}
else
{
path = ReplacePathsWithMacros(absolutePath, environmentVariableReader);
}
return path;
}
/// <summary>
/// Check if the file has changes compared to the original on disk.
/// </summary>
public static bool HasChanges(XDocument newFile, string path, ILogger log)
{
if (newFile == null)
{
// The file should be deleted if it is null.
return File.Exists(path);
}
else
{
var existing = ReadExisting(path, log);
if (existing != null)
{
return !XDocument.DeepEquals(existing, newFile);
}
}
return true;
}
public static XDocument ReadExisting(string path, ILogger log)
{
XDocument result = null;
if (File.Exists(path))
{
try
{
result = XmlUtility.Load(path);
}
catch (Exception ex)
{
// Log a debug message and ignore, this will force an overwrite
log.LogDebug($"Failed to open imports file: {path} Error: {ex.Message}");
}
}
return result;
}
public static string GetMSBuildFilePath(PackageSpec project, string extension)
{
string path;
if (project.RestoreMetadata?.ProjectStyle == ProjectStyle.PackageReference)
{
// PackageReference style projects
var projFileName = Path.GetFileName(project.RestoreMetadata.ProjectPath);
path = Path.Combine(project.RestoreMetadata.OutputPath, $"{projFileName}.nuget.g{extension}");
}
else
{
// Project.json style projects
var dir = Path.GetDirectoryName(project.FilePath);
path = Path.Combine(dir, $"{project.Name}.nuget{extension}");
}
return path;
}
public static string GetMSBuildFilePathForPackageReferenceStyleProject(PackageSpec project, string extension)
{
var projFileName = Path.GetFileName(project.RestoreMetadata.ProjectPath);
return Path.Combine(project.RestoreMetadata.OutputPath, $"{projFileName}.nuget.g{extension}");
}
public static List<MSBuildOutputFile> GetMSBuildOutputFiles(PackageSpec project,
LockFile assetsFile,
IEnumerable<RestoreTargetGraph> targetGraphs,
IReadOnlyList<NuGetv3LocalRepository> repositories,
RestoreRequest request,
string assetsFilePath,
bool restoreSuccess,
ILogger log)
{
// Generate file names
var targetsPath = GetMSBuildFilePath(project, TargetsExtension);
var propsPath = GetMSBuildFilePath(project, PropsExtension);
// Targets files contain a macro for the repository root. If only the user package folder was used
// allow a replacement. If fallback folders were used the macro cannot be applied.
// Do not use macros for fallback folders. Use only the first repository which is the user folder.
var repositoryRoot = repositories[0].RepositoryRoot;
// Invalid msbuild projects should write out an msbuild error target
if (!targetGraphs.Any())
{
return GenerateMultiTargetFailureFiles(
targetsPath,
propsPath,
request.ProjectStyle);
}
// Add additional conditionals for multi targeting
var multiTargetingFromMetadata = (request.Project.RestoreMetadata?.CrossTargeting == true);
var isMultiTargeting = multiTargetingFromMetadata
|| request.Project.TargetFrameworks.Count > 1;
// MultiTargeting imports are shared between TFMs, to avoid
// duplicate import warnings only add each once.
var multiTargetingImportsAdded = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var packagesWithTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < assetsFile.Libraries.Count; ++i)
{
var library = assetsFile.Libraries[i];
if (library.HasTools)
{
packagesWithTools.Add(library.Name);
}
}
// ItemGroups for each file.
var props = new List<MSBuildRestoreItemGroup>();
var targets = new List<MSBuildRestoreItemGroup>();
foreach (var target in assetsFile.Targets)
{
// Skip runtime graphs, msbuild targets may not come from RID specific packages.
if (!string.IsNullOrEmpty(target.RuntimeIdentifier))
{
continue;
}
var ridlessTarget = target;
var frameworkConditions = string.Format(
CultureInfo.InvariantCulture,
TargetFrameworkCondition,
ridlessTarget.TargetAlias);
// Find matching target in the original target graphs.
RestoreTargetGraph targetGraph = null;
foreach (RestoreTargetGraph graph in targetGraphs)
{
if (string.IsNullOrEmpty(graph.RuntimeIdentifier) && ridlessTarget.TargetFramework == graph.Framework && ridlessTarget.TargetAlias == graph.TargetAlias)
{
targetGraph = graph;
break;
}
}
// Sort by dependency order, child package assets should appear higher in the
// msbuild targets and props files so that parents can depend on them.
var sortedGraph = TopologicalSortUtility.SortPackagesByDependencyOrder(ConvertToPackageDependencyInfo(targetGraph.Flattened));
// Filter out to packages only, exclude projects.
var packageType = new HashSet<string>(
targetGraph.Flattened.Where(e => e.Key.Type == LibraryType.Package)
.Select(e => e.Key.Name),
StringComparer.OrdinalIgnoreCase);
// Package -> PackageInfo
// PackageInfo is kept lazy to avoid hitting the disk for packages
// with no relevant assets.
List<KeyValuePair<LockFileTargetLibrary, Lazy<LocalPackageSourceInfo>>> sortedPackages = new List<KeyValuePair<LockFileTargetLibrary, Lazy<LocalPackageSourceInfo>>>(sortedGraph.Count);
foreach (PackageDependencyInfo sortedPkg in sortedGraph.NoAllocEnumerate())
{
if (packageType.Contains(sortedPkg.Id))
{
foreach (LockFileTargetLibrary assetsPkg in ridlessTarget.Libraries.NoAllocEnumerate())
{
if (sortedPkg.Id.Equals(assetsPkg.Name, StringComparison.OrdinalIgnoreCase) && sortedPkg.Version == assetsPkg.Version)
{
var packageSourceInfo = new Lazy<LocalPackageSourceInfo>(() =>
NuGetv3LocalRepositoryUtility.GetPackage(
repositories,
sortedPkg.Id,
sortedPkg.Version));
sortedPackages.Add(new KeyValuePair<LockFileTargetLibrary, Lazy<LocalPackageSourceInfo>>(assetsPkg, packageSourceInfo));
break;
}
}
}
}
// build/ {packageId}.targets
var buildTargetsGroup = GenerateBuildGroup(repositoryRoot, sortedPackages, TargetsExtension);
targets.AddRange(GenerateGroupsWithConditions(buildTargetsGroup, isMultiTargeting, frameworkConditions));
// props/ {packageId}.props
MSBuildRestoreItemGroup buildPropsGroup = GenerateBuildGroup(repositoryRoot, sortedPackages, PropsExtension);
props.AddRange(GenerateGroupsWithConditions(buildPropsGroup, isMultiTargeting, frameworkConditions));
// Create an empty PropertyGroup for package properties
var packagePathsPropertyGroup = MSBuildRestoreItemGroup.Create("PropertyGroup", 1000);
if (isMultiTargeting)
{
packagePathsPropertyGroup.Conditions.Add(frameworkConditions);
}
IEnumerable<string> packageIdsToCreatePropertiesFor = null;
foreach (var projectGraph in targetGraph.Graphs)
{
HashSet<string> packageSet = null;
foreach (var i in projectGraph.Item.Data.Dependencies)
{
// Packages with GeneratePathProperty=true
if (i.GeneratePathProperty)
{
packageSet ??= new HashSet<string>(StringComparer.OrdinalIgnoreCase);
packageSet.Add(i.Name);
}
}
packageIdsToCreatePropertiesFor = packageSet ?? Enumerable.Empty<string>();
break;
}
// Find the packages with matching IDs in the list of sorted packages, filtering out ones that there was no match for or that don't exist
foreach (var sortedPackage in sortedPackages)
{
var pkg = sortedPackage.Value;
if (pkg?.Value?.Package != null && (packagesWithTools.Contains(pkg.Value.Package.Id) || packageIdsToCreatePropertiesFor.Contains(pkg.Value.Package.Id)) && pkg.Exists())
{
// Get the property
packagePathsPropertyGroup.Items.Add(GeneratePackagePathProperty(pkg.Value.Package));
}
}
// Don't bother adding the PropertyGroup if there were no properties added
if (packagePathsPropertyGroup.Items.Count > 0)
{
props.Add(packagePathsPropertyGroup);
}
if (isMultiTargeting)
{
// buildMultiTargeting/ {packageId}.targets
var buildCrossTargetsGroup = GenerateMultiTargetingGroup(repositoryRoot, sortedPackages, multiTargetingImportsAdded, TargetsExtension);
targets.AddRange(GenerateGroupsWithConditions(buildCrossTargetsGroup, isMultiTargeting, CrossTargetingCondition));
// buildMultiTargeting/ {packageId}.props
var buildCrossPropsGroup = GenerateMultiTargetingGroup(repositoryRoot, sortedPackages, multiTargetingImportsAdded, PropsExtension);
props.AddRange(GenerateGroupsWithConditions(buildCrossPropsGroup, isMultiTargeting, CrossTargetingCondition));
}
// Write out contentFiles only for XPlat PackageReference projects.
if (request.ProjectStyle != ProjectStyle.ProjectJson
&& request.Project.RestoreMetadata?.SkipContentFileWrite != true)
{
// Create a group for every package, with the nearest from each of allLanguages
foreach (var pkg in sortedPackages)
{
var lockContentFiles = new List<LockFileContentFile>(pkg.Key.ContentFiles.Count);
foreach (var contentFile in pkg.Key.ContentFiles.NoAllocEnumerate())
{
if (pkg.Value.Exists())
{
lockContentFiles.Add(contentFile);
}
}
lockContentFiles.Sort(static (x, y) => StringComparer.Ordinal.Compare(x.Path, y.Path));
var currentItems = new List<(LockFileTargetLibrary, LockFileContentFile, string)>(lockContentFiles.Count);
foreach (var e in lockContentFiles)
{
var tuple = ValueTuple.Create(item1: pkg.Key, item2: e, item3: GetPathWithMacros(pkg.Value.GetAbsolutePath(e), repositoryRoot));
currentItems.Add(tuple);
}
foreach (var group in GetLanguageGroups(currentItems))
{
foreach (var item in GenerateGroupsWithConditions(group, isMultiTargeting, frameworkConditions))
{
props.Add(item);
}
}
}
}
}
// Add exclude all condition to all groups
foreach (var group in props)
{
group.Conditions.Add(ExcludeAllCondition);
}
foreach (var target in targets)
{
target.Conditions.Add(ExcludeAllCondition);
}
// Create XML, these may be null if the file should be deleted/not written out.
var propsXML = GenerateMSBuildFile(props, request.ProjectStyle);
var targetsXML = GenerateMSBuildFile(targets, request.ProjectStyle);
// Return all files to write out or delete.
var files = new List<MSBuildOutputFile>(capacity: 2)
{
new MSBuildOutputFile(propsPath, propsXML),
new MSBuildOutputFile(targetsPath, targetsXML)
};
var packageFolders = repositories.Select(e => e.RepositoryRoot);
AddNuGetPropertiesToFirstImport(files, packageFolders, repositoryRoot, request.ProjectStyle, assetsFilePath, restoreSuccess);
return files;
static MSBuildRestoreItemGroup GenerateBuildGroup(string repositoryRoot, List<KeyValuePair<LockFileTargetLibrary, Lazy<LocalPackageSourceInfo>>> sortedPackages, string extension)
{
var buildGroup = new MSBuildRestoreItemGroup();
buildGroup.RootName = MSBuildRestoreItemGroup.ImportGroup;
buildGroup.Position = 2;
foreach (var pkg in sortedPackages)
{
if (pkg.Value.Exists())
{
foreach (LockFileItem lockFileItem in pkg.Key.Build.WithExtension(extension))
{
var absolutePath = pkg.Value.GetAbsolutePath(lockFileItem);
var pathWithMacros = GetPathWithMacros(absolutePath, repositoryRoot);
var import = GenerateImport(pathWithMacros);
buildGroup.Items.Add(import);
}
}
}
return buildGroup;
}
static MSBuildRestoreItemGroup GenerateMultiTargetingGroup(string repositoryRoot, List<KeyValuePair<LockFileTargetLibrary, Lazy<LocalPackageSourceInfo>>> sortedPackages, HashSet<string> multiTargetingImportsAdded, string extension)
{
var buildCrossTargetsGroup = new MSBuildRestoreItemGroup();
buildCrossTargetsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup;
buildCrossTargetsGroup.Position = 0;
foreach (var pkg in sortedPackages)
{
if (pkg.Value.Exists())
{
foreach (var e in pkg.Key.BuildMultiTargeting.WithExtension(extension))
{
var path = pkg.Value.GetAbsolutePath(e);
if (multiTargetingImportsAdded.Add(path))
{
var pathWithMacros = GetPathWithMacros(path, repositoryRoot);
var import = GenerateImport(pathWithMacros);
buildCrossTargetsGroup.Items.Add(import);
}
}
}
}
return buildCrossTargetsGroup;
}
}
private static IEnumerable<string> GetLanguageConditions(string language, SortedSet<string> allLanguages)
{
if (PackagingConstants.AnyCodeLanguage.Equals(language, StringComparison.OrdinalIgnoreCase))
{
// Must not be any of the other package languages.
foreach (var lang in allLanguages)
{
yield return string.Format(CultureInfo.InvariantCulture, NegativeLanguageCondition, GetLanguage(lang));
}
}
else
{
// Must be the language.
yield return string.Format(CultureInfo.InvariantCulture, LanguageCondition, GetLanguage(language));
}
}
public static string GetLanguage(string nugetLanguage)
{
// Translate S -> #
if (StringComparer.OrdinalIgnoreCase.Equals(nugetLanguage, "CS"))
{
return "C#";
}
else if (StringComparer.OrdinalIgnoreCase.Equals(nugetLanguage, "FS"))
{
return "F#";
}
// Return the language as it is
return nugetLanguage.ToUpperInvariant();
}
private static IEnumerable<MSBuildRestoreItemGroup> GetLanguageGroups(
List<ValueTuple<LockFileTargetLibrary, LockFileContentFile, string>> currentItems)
{
if (currentItems.Count == 0)
{
// Noop fast if this does not have content files.
return Enumerable.Empty<MSBuildRestoreItemGroup>();
}
var packageId = currentItems[0].Item1.Name;
var packageVersion = currentItems[0].Item1.Version.ToNormalizedString();
// Find all languages used for the any group condition
var allLanguages = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var e in currentItems)
{
var s = e.Item2.CodeLanguage;
if (!PackagingConstants.AnyCodeLanguage.Equals(s, StringComparison.OrdinalIgnoreCase))
{
allLanguages.Add(s);
}
}
// Convert content file items from a package into an ItemGroup with conditions.
// Remove _._ entries
// Filter empty groups
var groups = currentItems.GroupBy(e => e.Item2.CodeLanguage, StringComparer.OrdinalIgnoreCase)
.Select(group => MSBuildRestoreItemGroup.Create(
rootName: MSBuildRestoreItemGroup.ItemGroup,
position: 1,
conditions: GetLanguageConditions(group.Key, allLanguages),
items: group.Where(e => !e.Item2.Path.EndsWith(PackagingCoreConstants.ForwardSlashEmptyFolder, StringComparison.Ordinal)
&& !e.Item2.Path.EndsWith(".pp", StringComparison.OrdinalIgnoreCase)) // Skip .pp files
.Select(e => GenerateContentFilesItem(e.Item3, e.Item2, packageId, packageVersion))))
.Where(group => group.Items.Count > 0);
return groups;
}
private static IEnumerable<MSBuildRestoreItemGroup> GenerateGroupsWithConditions(
MSBuildRestoreItemGroup original,
bool isCrossTargeting,
params string[] conditions)
{
if (isCrossTargeting)
{
foreach (var condition in conditions)
{
var newConditions = new List<string>(original.Conditions.Count + 1);
foreach (var originalCondition in original.Conditions)
{
newConditions.Add(originalCondition);
}
newConditions.Add(condition);
yield return new MSBuildRestoreItemGroup()
{
RootName = original.RootName,
Position = original.Position,
Items = original.Items,
Conditions = newConditions
};
}
}
else
{
// No changes needed
yield return original;
}
}
private static string GetAbsolutePath(this Lazy<LocalPackageSourceInfo> package, LockFileItem item)
{
return Path.Combine(package.Value.Package.ExpandedPath, LockFileUtils.ToDirectorySeparator(item.Path));
}
private static bool Exists(this Lazy<LocalPackageSourceInfo> package)
{
return (package?.Value != null);
}
private static IEnumerable<LockFileItem> WithExtension(this IList<LockFileItem> items, string extension)
{
if (items == null || items.Count == 0)
{
return Enumerable.Empty<LockFileItem>();
}
return FilterExtensions(items, extension);
static IEnumerable<LockFileItem> FilterExtensions(IList<LockFileItem> items, string extension)
{
for (int i = 0; i < items.Count; ++i)
{
var item = items[i];
if (extension.Equals(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
{
yield return item;
}
}
}
}
private static HashSet<PackageDependencyInfo> ConvertToPackageDependencyInfo(
ISet<GraphItem<RemoteResolveResult>> items)
{
var result = new HashSet<PackageDependencyInfo>(PackageIdentity.Comparer);
foreach (var item in items)
{
IEnumerable<PackageDependency> dependencies;
if (item.Data?.Dependencies == null || item.Data.Dependencies.Count == 0)
{
// If there are no dependencies, pass null.
// The PackageDependencyInfo constructor will convert this to an empty array.
dependencies = null;
}
else
{
List<PackageDependency> newDependencies = new List<PackageDependency>(item.Data.Dependencies.Count);
foreach (var dependency in item.Data.Dependencies)
{
if (dependency.ReferenceType == LibraryDependencyReferenceType.Direct)
{
newDependencies.Add(new PackageDependency(dependency.Name, VersionRange.All));
}
}
// If there are no dependencies, pass null.
// The PackageDependencyInfo constructor will convert this to an empty array.
dependencies = newDependencies.Count == 0 ? null : newDependencies;
}
result.Add(new PackageDependencyInfo(item.Key.Name, item.Key.Version, dependencies));
}
return result;
}
private static XElement GeneratePackagePathProperty(LocalPackageInfo localPackageInfo)
{
#if NETCOREAPP
return GenerateProperty($"Pkg{localPackageInfo.Id.Replace(".", "_", StringComparison.Ordinal)}", localPackageInfo.ExpandedPath);
#else
return GenerateProperty($"Pkg{localPackageInfo.Id.Replace(".", "_")}", localPackageInfo.ExpandedPath);
#endif
}
}
}
|