private static IEnumerable <MSBuildRestoreItemGroup> GenerateGroupsWithConditions( MSBuildRestoreItemGroup original, bool isCrossTargeting, params string[] conditions) { if (isCrossTargeting) { foreach (var condition in conditions) { yield return(new MSBuildRestoreItemGroup() { RootName = original.RootName, Position = original.Position, Items = original.Items, Conditions = original.Conditions.Concat(new[] { condition }).ToList() }); } } else { // No changes needed yield return(original); } }
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, request, "targets"); var propsPath = GetMSBuildFilePath(project, request, "props"); // 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.First().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; // ItemGroups for each file. var props = new List <MSBuildRestoreItemGroup>(); var targets = new List <MSBuildRestoreItemGroup>(); // MultiTargeting imports are shared between TFMs, to avoid // duplicate import warnings only add each once. var multiTargetingImportsAdded = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // Skip runtime graphs, msbuild targets may not come from RID specific packages. var ridlessTargets = assetsFile.Targets .Where(e => string.IsNullOrEmpty(e.RuntimeIdentifier)); var packagesWithTools = new HashSet <string>(assetsFile.Libraries.Where(i => i.HasTools).Select(i => i.Name), StringComparer.OrdinalIgnoreCase); foreach (var ridlessTarget in ridlessTargets) { // There could be multiple string matches from the MSBuild project. var frameworkConditions = GetMatchingFrameworkStrings(project, ridlessTarget.TargetFramework) .Select(match => string.Format(CultureInfo.InvariantCulture, TargetFrameworkCondition, match)) .ToArray(); // Find matching target in the original target graphs. var targetGraph = targetGraphs.FirstOrDefault(e => string.IsNullOrEmpty(e.RuntimeIdentifier) && ridlessTarget.TargetFramework == e.Framework); // 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. var sortedPackages = sortedGraph.Where(e => packageType.Contains(e.Id)) .Select(sortedPkg => new KeyValuePair <LockFileTargetLibrary, Lazy <LocalPackageSourceInfo> >( key: ridlessTarget.Libraries.FirstOrDefault(assetsPkg => sortedPkg.Version == assetsPkg.Version && sortedPkg.Id.Equals(assetsPkg.Name, StringComparison.OrdinalIgnoreCase)), value: new Lazy <LocalPackageSourceInfo>(() => NuGetv3LocalRepositoryUtility.GetPackage( repositories, sortedPkg.Id, sortedPkg.Version)))) .Where(e => e.Key != null) .ToArray(); // build/ {packageId}.targets var buildTargetsGroup = new MSBuildRestoreItemGroup(); buildTargetsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildTargetsGroup.Position = 2; buildTargetsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.Build.WithExtension(TargetsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); targets.AddRange(GenerateGroupsWithConditions(buildTargetsGroup, isMultiTargeting, frameworkConditions)); // props/ {packageId}.props var buildPropsGroup = new MSBuildRestoreItemGroup(); buildPropsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildPropsGroup.Position = 2; buildPropsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.Build.WithExtension(PropsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); props.AddRange(GenerateGroupsWithConditions(buildPropsGroup, isMultiTargeting, frameworkConditions)); // Create an empty PropertyGroup for package properties var packagePathsPropertyGroup = MSBuildRestoreItemGroup.Create("PropertyGroup", Enumerable.Empty <XElement>(), 1000, isMultiTargeting ? frameworkConditions : Enumerable.Empty <string>()); var projectGraph = targetGraph.Graphs.FirstOrDefault(); // Packages with GeneratePathProperty=true var packageIdsToCreatePropertiesFor = new HashSet <string>(projectGraph.Item.Data.Dependencies.Where(i => i.GeneratePathProperty).Select(i => i.Name), StringComparer.OrdinalIgnoreCase); var localPackages = sortedPackages.Select(e => e.Value); // 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 var packagePathProperties = localPackages .Where(pkg => pkg?.Value?.Package != null && (packagesWithTools.Contains(pkg.Value.Package.Id) || packageIdsToCreatePropertiesFor.Contains(pkg.Value.Package.Id)) && pkg.Exists()) .Select(pkg => pkg.Value.Package) // Get the property .Select(GeneratePackagePathProperty); packagePathsPropertyGroup.Items.AddRange(packagePathProperties); // Don't bother adding the PropertyGroup if there were no properties added if (packagePathsPropertyGroup.Items.Any()) { props.Add(packagePathsPropertyGroup); } if (isMultiTargeting) { // buildMultiTargeting/ {packageId}.targets var buildCrossTargetsGroup = new MSBuildRestoreItemGroup(); buildCrossTargetsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildCrossTargetsGroup.Position = 0; buildCrossTargetsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.BuildMultiTargeting.WithExtension(TargetsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Where(path => multiTargetingImportsAdded.Add(path)) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); targets.AddRange(GenerateGroupsWithConditions(buildCrossTargetsGroup, isMultiTargeting, CrossTargetingCondition)); // buildMultiTargeting/ {packageId}.props var buildCrossPropsGroup = new MSBuildRestoreItemGroup(); buildCrossPropsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildCrossPropsGroup.Position = 0; buildCrossPropsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.BuildMultiTargeting.WithExtension(PropsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Where(path => multiTargetingImportsAdded.Add(path)) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); 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 props.AddRange(sortedPackages.Select(pkg => pkg.Key.ContentFiles .Where(e => pkg.Value.Exists()) .OrderBy(e => e.Path, StringComparer.Ordinal) .Select(e => Tuple.Create( item1: pkg.Key, item2: e, item3: GetPathWithMacros(pkg.Value.GetAbsolutePath(e), repositoryRoot)))) .SelectMany(e => GetLanguageGroups(e)) .SelectMany(group => GenerateGroupsWithConditions(group, isMultiTargeting, frameworkConditions))); } } // Add exclude all condition to all groups foreach (var group in props.Concat(targets)) { group.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> { 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); }