File: WorkloadPackageBase.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.Workloads\src\Microsoft.DotNet.Build.Tasks.Workloads.csproj (Microsoft.DotNet.Build.Tasks.Workloads)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Versioning;
 
namespace Microsoft.DotNet.Build.Tasks.Workloads
{
    /// <summary>
    /// Serves as a base class for implementing different types of workload packages. The class captures some common
    /// elements related to the underlying NuGet package.
    /// </summary>
    public abstract class WorkloadPackageBase
    {
        /// <summary>
        /// The package authors.
        /// </summary>
        public string Authors
        {
            get;
        }
 
        public string Copyright
        {
            get;
        }
 
        public string Description
        {
            get;
        }
 
        /// <summary>
        /// Determines on the contents of the package is managed.
        /// </summary>
        public abstract PackageExtractionMethod ExtractionMethod
        {
            get;
        }
 
        /// <summary>
        /// The NuGet package identifier.
        /// </summary>
        public string Id => Identity.Id;
 
        /// <summary>
        /// The identity of the NuGet package.
        /// </summary>
        public PackageIdentity Identity
        {
            get;
        }
 
        /// <summary>
        /// Gets whether the package has been extracted.
        /// </summary>
        public bool HasBeenExtracted
        {
            get;
            private set;
        }
 
        public LicenseMetadata LicenseData
        {
            get;
        }
 
        public string LicenseUrl
        {
            get;
        }
 
        /// <summary>
        /// Gets the version to use for the generated MSI's ProductVersion property.
        /// </summary>
        public abstract Version MsiVersion
        {
            get;
        }
 
        public string PackagePath
        {
            get;
        }
 
        public string PackageFileName
        {
            get;
        }
 
        public string ShortName
        {
            get;
        }
 
        /// <summary>
        /// A string containing the major, minor and patch version of the package.
        /// </summary>
        public string ProductVersion => $"{PackageVersion.Major}.{PackageVersion.Minor}.{PackageVersion.Patch}";
 
        public string Title
        {
            get;
        }
 
        public string ProjectUrl
        {
            get;
        }
 
        /// <summary>
        /// The version of the NuGet package.
        /// </summary>
        public NuGetVersion PackageVersion => Identity.Version;
 
        public ITaskItem[]? ShortNames
        {
            get;
        }
 
        /// <summary>
        /// The SWIX identifier for the package in VS.
        /// </summary>
        public string SwixPackageId
        {
            get;
            protected set;
        }
 
        /// <summary>
        /// Gets an instance of a <see cref="TaskLoggingHelper"/> class containing task logging methods.
        /// </summary>
        protected TaskLoggingHelper? Log
        {
            get;
        }
 
        /// <summary>
        /// A <see cref="System.Version"/> containing the major, minor, and patch version of the underlying NuGet package.
        /// </summary>
        public Version Version => Identity.Version.Version;
 
        /// <summary>
        /// The destination directory where the package will be extracted.
        /// </summary>
        public string DestinationDirectory
        {
            get;
        }
 
        /// <summary>
        /// Creates a new instance of a <see cref="WorkloadPackageBase"/> class.
        /// </summary>
        /// <param name="packagePath">The path of the NuGet package.</param>
        /// <param name="destinationBaseDirectory">The root directory where packages will be extracted.</param>
        /// <param name="shortNames">A set of items used to shorten the names and identifiers of setup packages.</param>
        /// <param name="log">A <see cref="TaskLoggingHelper"/> class containing task logging methods.</param>
        public WorkloadPackageBase(string packagePath, string destinationBaseDirectory, ITaskItem[]? shortNames = null, TaskLoggingHelper? log = null)
        {
            // Very important: If the underlying stream isn't closed, it will cause
            // sharing violations when the package content is being extracted later.
            using FileStream fs = new(packagePath, FileMode.Open);
            using PackageArchiveReader reader = new(fs);
            NuspecReader nuspec = reader.NuspecReader;
 
            Authors = nuspec.GetAuthors();
            Copyright = nuspec.GetCopyright();
            Description = nuspec.GetDescription();
            Identity = nuspec.GetIdentity();
            LicenseData = nuspec.GetLicenseMetadata();
            LicenseUrl = nuspec.GetLicenseUrl();
            ProjectUrl = nuspec.GetProjectUrl();
            Title = nuspec.GetTitle();
 
            PackagePath = packagePath;
            DestinationDirectory = Path.Combine(destinationBaseDirectory, $"{Identity}");
            ShortNames = shortNames;
 
            PackageFileName = Path.GetFileNameWithoutExtension(packagePath);
            ShortName = PackageFileName.Replace(shortNames);
            SwixPackageId = $"{Id.Replace(shortNames)}.{Identity.Version}";
            Log = log;
        }
 
        /// <summary>
        /// Extracts the contents of the package based on
        /// </summary>
        public void Extract()
        {
            Extract(Enumerable.Empty<string>());
        }
 
        /// <summary>
        /// Extract the contents of the package and optionally delete files that match
        /// the set of exclusions.
        /// </summary>
        /// <param name="exclusionPatterns">A set of regular expression patterns used to determine if a
        /// file should be excluded. Excluded files will be deleted after the package has been extracted.</param>
        public virtual void Extract(IEnumerable<string> exclusionPatterns)
        {
            if (HasBeenExtracted)
            {
                return;
            }
 
            Utils.DeleteDirectory(DestinationDirectory);
            Directory.CreateDirectory(DestinationDirectory);
 
            if (ExtractionMethod == PackageExtractionMethod.Copy)
            {
                File.Copy(PackagePath, Path.Combine(DestinationDirectory, Path.GetFileName(PackagePath)), overwrite: true);
                HasBeenExtracted = true;
            }
            else if (ExtractionMethod == PackageExtractionMethod.Unzip)
            {
                ZipFile.ExtractToDirectory(PackagePath, DestinationDirectory);
 
                // Remove unnecessary files and directories that we never want to ship. These are always present in a NuGet package.
                Utils.DeleteDirectory(Path.Combine(DestinationDirectory, "_rels"));
                Utils.DeleteDirectory(Path.Combine(DestinationDirectory, "package"));
 
                Utils.DeleteFile(Path.Combine(DestinationDirectory, ".signature.p7s"));
                Utils.DeleteFile(Path.Combine(DestinationDirectory, "[Content_Types].xml"));
                Utils.DeleteFile(Path.Combine(DestinationDirectory, $"{Id}.nuspec"));
 
                if (exclusionPatterns.Any())
                {
                    foreach (string file in Directory.EnumerateFiles(DestinationDirectory, "*.*", SearchOption.AllDirectories))
                    {
                        if (exclusionPatterns.Any(pattern => Regex.IsMatch(file, pattern)))
                        {
                            Log?.LogMessage(MessageImportance.Low, string.Format(Strings.WorkloadPackageDeleteExclusion, file));
                            File.Delete(file);
                        }
                    }
                }
 
                HasBeenExtracted = true;
            }
        }
    }
}
 
#nullable disable