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);
     }
 }
Example #2
0
        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);
        }