in src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/BuildAssetsUtils.cs [434:718]
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,
GetMatchingFrameworkStrings(project, ridlessTarget.TargetFramework));
// 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)
{
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;
}
}