Exemple #1
0
        /// <summary>
        /// Generate a set of MSIs for the specified platforms using the specified NuGet package.
        /// </summary>
        /// <param name="sourcePackage">The NuGet package to convert into an MSI.</param>
        /// <param name="outputPath">The output path of the generated MSI.</param>
        /// <param name="platforms"></param>
        protected IEnumerable <ITaskItem> Generate(string sourcePackage, string swixPackageId, string outputPath, WorkloadPackKind kind, params string[] platforms)
        {
            NugetPackage    nupkg = new(sourcePackage, Log);
            List <TaskItem> msis  = new();

            // MSI ProductName defaults to the package title and fallback to the package ID with a warning.
            string productName = nupkg.Title;

            if (string.IsNullOrWhiteSpace(nupkg.Title))
            {
                Log?.LogMessage(MessageImportance.High, $"'{sourcePackage}' should have a non-empty title. The MSI ProductName will be set to the package ID instead.");
                productName = nupkg.Id;
            }

            // Extract once, but harvest multiple times because some generated attributes are platform dependent.
            string packageContentsDirectory = Path.Combine(PackageDirectory, $"{nupkg.Identity}");
            IEnumerable <string> exclusions = GetExlusionPatterns();
            string installDir = GetInstallDir(kind);
            string packKind   = kind.ToString().ToLowerInvariant();

            if ((kind != WorkloadPackKind.Library) && (kind != WorkloadPackKind.Template))
            {
                Log.LogMessage(MessageImportance.Low, $"Extracting '{sourcePackage}' to '{packageContentsDirectory}'");
                nupkg.Extract(packageContentsDirectory, exclusions);
            }
            else
            {
                // Library and template packs are not extracted. We want to harvest the nupkg itself,
                // instead of the contents. The package is still copied to a separate folder for harvesting
                // to avoid accidentally pulling in additional files and directories.
                Log.LogMessage(MessageImportance.Low, $"Copying '{sourcePackage}' to '{packageContentsDirectory}'");

                if (Directory.Exists(packageContentsDirectory))
                {
                    Directory.Delete(packageContentsDirectory, recursive: true);
                }
                Directory.CreateDirectory(packageContentsDirectory);

                File.Copy(sourcePackage, Path.Combine(packageContentsDirectory, Path.GetFileName(sourcePackage)));
            }

            System.Threading.Tasks.Parallel.ForEach(platforms, platform =>
            {
                // Extract the MSI template and add it to the list of source files.
                List <string> sourceFiles = new();
                string msiSourcePath      = Path.Combine(MsiDirectory, $"{nupkg.Id}", $"{nupkg.Version}", platform);
                sourceFiles.Add(EmbeddedTemplates.Extract("DependencyProvider.wxs", msiSourcePath));
                sourceFiles.Add(EmbeddedTemplates.Extract("Directories.wxs", msiSourcePath));
                sourceFiles.Add(EmbeddedTemplates.Extract("Product.wxs", msiSourcePath));
                sourceFiles.Add(EmbeddedTemplates.Extract("Registry.wxs", msiSourcePath));

                string EulaRtfPath = Path.Combine(msiSourcePath, "eula.rtf");
                File.WriteAllText(EulaRtfPath, Eula.Replace("__LICENSE_URL__", nupkg.LicenseUrl));
                EmbeddedTemplates.Extract("Variables.wxi", msiSourcePath);

                // Harvest the package contents and add it to the source files we need to compile.
                string packageContentWxs = Path.Combine(msiSourcePath, "PackageContent.wxs");
                sourceFiles.Add(packageContentWxs);

                string directoryReference = (kind == WorkloadPackKind.Library) || (kind == WorkloadPackKind.Template)
                    ? "InstallDir"
                    : PackageContentDirectoryReference;

                HarvestToolTask heat = new(BuildEngine, WixToolsetPath)
                {
                    ComponentGroupName = PackageContentComponentGroupName,
                    DirectoryReference = directoryReference,
                    OutputFile         = packageContentWxs,
                    Platform           = platform,
                    SourceDirectory    = packageContentsDirectory
                };

                if (!heat.Execute())
                {
                    throw new Exception($"Failed to harvest package contents.");
                }

                // Compile the MSI sources
                string candleIntermediateOutputPath = Path.Combine(IntermediateBaseOutputPath, "wixobj",
                                                                   $"{nupkg.Id}", $"{nupkg.Version}", platform);

                CompileToolTask candle = new(BuildEngine, WixToolsetPath)
                {
                    // Candle expects the output path to end with a single '\'
                    OutputPath  = candleIntermediateOutputPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar,
                    SourceFiles = sourceFiles,
                    Arch        = platform
                };

                // Configure preprocessor definitions.
                string manufacturer = "Microsoft Corporation";

                if (!string.IsNullOrWhiteSpace(nupkg.Authors) && (nupkg.Authors.IndexOf("Microsoft", StringComparison.OrdinalIgnoreCase) < 0))
                {
                    manufacturer = nupkg.Authors;
                }
                Log.LogMessage(MessageImportance.Low, $"Setting Manufacturer to '{manufacturer}'");

                candle.PreprocessorDefinitions.AddRange(GetPreprocessorDefinitions(nupkg, platform));
                candle.PreprocessorDefinitions.Add($@"InstallDir={installDir}");
                candle.PreprocessorDefinitions.Add($@"ProductName={productName}");
                candle.PreprocessorDefinitions.Add($@"Platform={platform}");
                candle.PreprocessorDefinitions.Add($@"SourceDir={packageContentsDirectory}");
                candle.PreprocessorDefinitions.Add($@"Manufacturer={manufacturer}");
                candle.PreprocessorDefinitions.Add($@"EulaRtf={EulaRtfPath}");
                candle.PreprocessorDefinitions.Add($@"PackKind={packKind}");

                // Compiler extension to process dependency provider authoring for package reference counting.
                candle.Extensions.Add("WixDependencyExtension");

                if (!candle.Execute())
                {
                    throw new Exception($"Failed to compile MSI.");
                }

                // Link the MSI. The generated filename contains a the semantic version (excluding build metadata) and platform.
                // If the source package already contains a platform, e.g. an aliased package that has a RID, then we don't add
                // the platform again.

                string shortPackageName = Path.GetFileNameWithoutExtension(sourcePackage).Replace(ShortNames);

                string outputFile = sourcePackage.Contains(platform) ?
                                    Path.Combine(OutputPath, shortPackageName + ".msi") :
                                    Path.Combine(OutputPath, shortPackageName + $"-{platform}.msi");

                LinkToolTask light = new(BuildEngine, WixToolsetPath)
                {
                    OutputFile   = Path.Combine(OutputPath, outputFile),
                    SourceFiles  = Directory.EnumerateFiles(candleIntermediateOutputPath, "*.wixobj"),
                    SuppressIces = this.SuppressIces
                };

                // Add WiX extensions
                light.Extensions.Add("WixDependencyExtension");
                light.Extensions.Add("WixUIExtension");

                if (!light.Execute())
                {
                    throw new Exception($"Failed to link MSI.");
                }

                // Generate metadata used for CLI based installations.
                string msiPath         = light.OutputFile;
                MsiProperties msiProps = new MsiProperties
                {
                    InstallSize     = MsiUtils.GetInstallSize(msiPath),
                    Language        = Convert.ToInt32(MsiUtils.GetProperty(msiPath, "ProductLanguage")),
                    Payload         = Path.GetFileName(msiPath),
                    ProductCode     = MsiUtils.GetProperty(msiPath, "ProductCode"),
                    ProductVersion  = MsiUtils.GetProperty(msiPath, "ProductVersion"),
                    ProviderKeyName = $"{nupkg.Id},{nupkg.Version},{platform}",
                    UpgradeCode     = MsiUtils.GetProperty(msiPath, "UpgradeCode"),
                    RelatedProducts = MsiUtils.GetRelatedProducts(msiPath)
                };

                string msiJsonPath = Path.Combine(Path.GetDirectoryName(msiPath), Path.GetFileNameWithoutExtension(msiPath) + ".json");
                File.WriteAllText(msiJsonPath, JsonSerializer.Serialize <MsiProperties>(msiProps));

                TaskItem msi = new(light.OutputFile);
                msi.SetMetadata(Metadata.Platform, platform);
                msi.SetMetadata(Metadata.Version, nupkg.ProductVersion);
                msi.SetMetadata(Metadata.JsonProperties, msiJsonPath);
                msi.SetMetadata(Metadata.WixObj, candleIntermediateOutputPath);

                if (GenerateSwixAuthoring && IsSupportedByVisualStudio(platform))
                {
                    string swixProject = GenerateSwixPackageAuthoring(light.OutputFile,
                                                                      !string.IsNullOrWhiteSpace(swixPackageId) ? swixPackageId :
                                                                      $"{nupkg.Id.Replace(ShortNames)}.{nupkg.Version}", platform);

                    if (!string.IsNullOrWhiteSpace(swixProject))
                    {
                        msi.SetMetadata(Metadata.SwixProject, swixProject);
                    }
                }

                // Generate a .csproj to build a NuGet payload package to carry the MSI and JSON manifest
                msi.SetMetadata(Metadata.PackageProject, GeneratePackageProject(msi.ItemSpec, msiJsonPath, platform, nupkg));

                msis.Add(msi);
            });

            return(msis);
        }