private static IEnumerable <MSBuildRestoreItemGroup> GetLanguageGroups( IEnumerable <Tuple <LockFileTargetLibrary, LockFileContentFile, string> > items) { var currentItems = items.ToArray(); if (currentItems.Length == 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>( currentItems.Select(e => e.Item2.CodeLanguage) .Where(s => !PackagingConstants.AnyCodeLanguage.Equals(s, StringComparison.OrdinalIgnoreCase)), StringComparer.OrdinalIgnoreCase); // 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) && !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); }
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); }