Migrate some tasks from BuildTools
authorDavis Goodin <dagood@microsoft.com>
Fri, 21 Jun 2019 18:17:43 +0000 (13:17 -0500)
committerDavis Goodin <dagood@microsoft.com>
Fri, 21 Jun 2019 20:02:51 +0000 (15:02 -0500)
Commit migrated from https://github.com/dotnet/core-setup/commit/0b9a758eb493539d742c7df99d659955475be96c

tools-local/Directory.Build.props
tools-local/tasks/GenerateCurrentVersion.cs [new file with mode: 0644]
tools-local/tasks/GetTargetMachineInfo.cs [new file with mode: 0644]
tools-local/tasks/ProcessSharedFrameworkDeps.cs
tools-local/tasks/ZipFileCreateFromDirectory.cs [new file with mode: 0644]
tools-local/tasks/ZipFileExtractToDirectory.cs [new file with mode: 0644]
tools-local/tasks/ZipFileGetEntries.cs [new file with mode: 0644]
tools-local/tasks/net46/ProcessSharedFrameworkDeps.net46.cs

index 5d4adb7..cd6ba44 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="..\Directory.Build.props" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory).., Directory.Build.props))\Directory.Build.props" />
 
   <PropertyGroup>
     <BuildCustomTasksForDesktop Condition="'$(MSBuildRuntimeType)' != 'Core'">true</BuildCustomTasksForDesktop>
diff --git a/tools-local/tasks/GenerateCurrentVersion.cs b/tools-local/tasks/GenerateCurrentVersion.cs
new file mode 100644 (file)
index 0000000..7068a2a
--- /dev/null
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System.Globalization;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.DotNet.Build.Tasks
+{
+    public sealed class GenerateCurrentVersion : BuildTask
+    {
+        /// <summary>
+        /// The passed in date that will be used to generate a version. (yyyy-MM-dd format)
+        /// </summary>
+        [Required]
+        public string SeedDate { get; set; }
+
+        /// <summary>
+        /// Optional parameter containing the Official Build Id. We'll use this to get the revision number out and use it as BuildNumberMinor.
+        /// </summary>
+        public string OfficialBuildId { get; set; }
+
+        /// <summary>
+        /// Optional parameter that sets the Padding for the version number. Must be 5 or bigger.
+        /// </summary>
+        public int Padding { get; set; }
+
+        /// <summary>
+        /// If basing off of internal builds version format is not required, this optional parameter lets you pass in a comparison date.
+        /// </summary>
+        public string ComparisonDate { get; set; }
+
+        /// <summary>
+        /// The Major Version that will be produced given a SeedDate.
+        /// </summary>
+        [Output]
+        public string GeneratedVersion { get; set; }
+
+        /// <summary>
+        /// The Revision number that will be produced from the BuildNumber.
+        /// </summary>
+        [Output]
+        public string GeneratedRevision { get; set; }
+
+        private const string DateFormat = "yyyy-MM-dd";
+        private const string LastModifiedTimeDateFormat = "yyyy-MM-dd HH:mm:ss.FFFFFFF";
+        private CultureInfo enUS = new CultureInfo("en-US");
+
+        public override bool Execute()
+        {
+            // If OfficialBuildId is passed in, then use that to calculate the version and revision.
+            if (string.IsNullOrEmpty(OfficialBuildId))
+            {
+                GeneratedRevision = "0";
+            }
+            else
+            {
+                bool success = SetVersionAndRevisionFromBuildId(OfficialBuildId);
+                return success;
+            }
+
+            // Calculating GeneratedVersion
+            if (Padding == 0)
+            {
+                Padding = 5;
+            }
+            else if (Padding < 5)
+            {
+                Log.LogWarning("The specified Padding '{0}' has to be equal to or greater than 5. Using 5 as a default now.", Padding);
+                Padding = 5;
+            }
+            DateTime date;
+            GeneratedVersion = string.Empty;
+            if (!(DateTime.TryParseExact(SeedDate, DateFormat, enUS, DateTimeStyles.AssumeLocal, out date)))
+            {
+                // Check if the timestamp matches the LastModifiedTimeDateFormat
+                if (!(DateTime.TryParseExact(SeedDate, LastModifiedTimeDateFormat, enUS, DateTimeStyles.AssumeLocal, out date)))
+                {
+                    Log.LogError("The seed date '{0}' is not valid. Please specify a date in the short format.({1})", SeedDate, DateFormat);
+                    return false;
+                }
+            }
+            //Convert Date to UTC to converge
+            date = date.ToUniversalTime();
+            GeneratedVersion = GetCurrentVersionForDate(date, ComparisonDate);
+            if (string.IsNullOrEmpty(GeneratedVersion))
+            {
+                Log.LogError("The date '{0}' is not valid. Please pass in a date after {1}.", SeedDate, ComparisonDate);
+                return false;
+            }
+            return true;
+        }
+
+        public bool SetVersionAndRevisionFromBuildId(string buildId)
+        {
+            Regex regex = new Regex(@"(\d{8})[\-\.](\d+)$");
+            string dateFormat = "yyyyMMdd";
+            Match match = regex.Match(buildId);
+            if (match.Success && match.Groups.Count > 2)
+            {
+                DateTime buildIdDate;
+                if (!DateTime.TryParseExact(match.Groups[1].Value, dateFormat, enUS, DateTimeStyles.AssumeLocal, out buildIdDate))
+                {
+                    Log.LogError("The OfficialBuildId doesn't follow the expected({0}.rr) format: '{1}'", dateFormat, match.Groups[1].Value);
+                    return false;
+                }
+                buildIdDate = buildIdDate.ToUniversalTime();
+                GeneratedVersion = GetCurrentVersionForDate(buildIdDate, ComparisonDate);
+                GeneratedRevision = match.Groups[2].Value;
+                return true;
+            }
+            Log.LogError("Error: Invalid OfficialBuildId was passed: '{0}'", buildId);
+            return false;
+        }
+
+        public string GetCurrentVersionForDate(DateTime seedDate, string comparisonDate)
+        {
+            DateTime compareDate;
+            if (string.IsNullOrEmpty(comparisonDate))
+            {
+                /*
+                 * We need to ensure that our build numbers are higher that what we used to ship internal builds so this date
+                 * will make that possible.
+                 */
+                compareDate = new DateTime(1996, 4, 1, 0, 0, 0, DateTimeKind.Utc);
+            }
+            else
+            {
+                bool isValidDate = DateTime.TryParseExact(comparisonDate, DateFormat, enUS, DateTimeStyles.AssumeLocal, out compareDate);
+                if (!isValidDate)
+                {
+                    Log.LogError("The comparison date '{0}' is not valid. Please specify a date in the short format.({1})", comparisonDate, DateFormat);
+                }
+                //Convert to UTC to converge
+                compareDate = compareDate.ToUniversalTime();
+            }
+            int months = (seedDate.Year - compareDate.Year) * 12 + seedDate.Month - compareDate.Month;
+            if (months > 0) //only allow dates after comparedate
+            {
+                return string.Format("{0}{1}", months.ToString("D" + (Padding - 2)), seedDate.Day.ToString("D2"));
+            }
+            return string.Empty;
+        }
+
+    }
+}
diff --git a/tools-local/tasks/GetTargetMachineInfo.cs b/tools-local/tasks/GetTargetMachineInfo.cs
new file mode 100644 (file)
index 0000000..93cf854
--- /dev/null
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System.Runtime.InteropServices;
+using Microsoft.DotNet.PlatformAbstractions;
+
+namespace Microsoft.DotNet.Build.Tasks
+{
+    public class GetTargetMachineInfo : BuildTask
+    {
+        [Output]
+        public string TargetOS { get; set; }
+
+        [Output]
+        public string TargetArch { get; set; }
+
+        [Output]
+        public string RuntimeIdentifier { get; set; }
+
+        public override bool Execute()
+        {
+            switch (RuntimeInformation.OSArchitecture)
+            {
+                case Architecture.X64:
+                    TargetArch = "x64";
+                    break;
+                case Architecture.X86:
+                    TargetArch = "x86";
+                    break;
+                case Architecture.Arm:
+                    TargetArch = "arm";
+                    break;
+                case Architecture.Arm64:
+                    TargetArch = "arm64";
+                    break;
+            }
+
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                TargetOS = "Windows_NT";
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                TargetOS = "Linux";
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+                TargetOS = "OSX";
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")))
+                TargetOS = "FreeBSD";
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD")))
+                TargetOS = "NetBSD";
+
+            RuntimeIdentifier = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier();
+
+            if (TargetArch == null)
+            {
+                Log.LogError("{0} is null", nameof(TargetArch));
+                return false;
+            }
+
+            if (TargetOS == null)
+            {
+                Log.LogError("{0} is null", nameof(TargetOS));
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
index 5fdfe26..b5e9642 100644 (file)
@@ -24,11 +24,11 @@ namespace Microsoft.DotNet.Build.Tasks
         public string Runtime { get; set; }
 
         [Required]
-        public string BuildToolsTaskDir { get; set; }
+        public string BuildTasksAssemblyPath { get; set; }
 
         public override bool Execute()
         {
-            EnsureInitialized(BuildToolsTaskDir);
+            EnsureInitialized(BuildTasksAssemblyPath);
 
             ExecuteCore();
 
@@ -74,6 +74,6 @@ namespace Microsoft.DotNet.Build.Tasks
             }
         }
 
-        partial void EnsureInitialized(string buildToolsTaskDir);
+        partial void EnsureInitialized(string buildTasksAssemblyPath);
     }
 }
diff --git a/tools-local/tasks/ZipFileCreateFromDirectory.cs b/tools-local/tasks/ZipFileCreateFromDirectory.cs
new file mode 100644 (file)
index 0000000..472c52b
--- /dev/null
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Text.RegularExpressions;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.DotNet.Build.Tasks
+{
+    public sealed class ZipFileCreateFromDirectory : Task
+    {
+        /// <summary>
+        /// The path to the directory to be archived.
+        /// </summary>
+        [Required]
+        public string SourceDirectory { get; set; }
+
+        /// <summary>
+        /// The path of the archive to be created.
+        /// </summary>
+        [Required]
+        public string DestinationArchive { get; set; }
+
+        /// <summary>
+        /// Indicates if the destination archive should be overwritten if it already exists.
+        /// </summary>
+        public bool OverwriteDestination { get; set; }
+
+        /// <summary>
+        /// If zipping an entire folder without exclusion patterns, whether to include the folder in the archive.
+        /// </summary>
+        public bool IncludeBaseDirectory { get; set; }
+
+        /// <summary>
+        /// An item group of regular expressions for content to exclude from the archive.
+        /// </summary>
+        public ITaskItem[] ExcludePatterns { get; set; }
+
+        public override bool Execute()
+        {
+            try
+            {
+                if (File.Exists(DestinationArchive))
+                {
+                    if (OverwriteDestination == true)
+                    {
+                        Log.LogMessage(MessageImportance.Low, "{0} already existed, deleting before zipping...", DestinationArchive);
+                        File.Delete(DestinationArchive);
+                    }
+                    else
+                    {
+                        Log.LogWarning("'{0}' already exists. Did you forget to set '{1}' to true?", DestinationArchive, nameof(OverwriteDestination));
+                    }
+                }
+
+                Log.LogMessage(MessageImportance.High, "Compressing {0} into {1}...", SourceDirectory, DestinationArchive);
+                string destinationDirectory = Path.GetDirectoryName(DestinationArchive);
+                if (!Directory.Exists(destinationDirectory) && !string.IsNullOrEmpty(destinationDirectory))
+                {
+                    Directory.CreateDirectory(destinationDirectory);
+                }
+
+                if (ExcludePatterns == null)
+                {
+                    ZipFile.CreateFromDirectory(SourceDirectory, DestinationArchive, CompressionLevel.Optimal, IncludeBaseDirectory);
+                }
+                else
+                {
+                    // convert to regular expressions
+                    Regex[] regexes = new Regex[ExcludePatterns.Length];
+                    for (int i = 0; i < ExcludePatterns.Length; ++i)
+                        regexes[i] = new Regex(ExcludePatterns[i].ItemSpec, RegexOptions.IgnoreCase);
+
+                    using (FileStream writer = new FileStream(DestinationArchive, FileMode.CreateNew))
+                    {
+                        using (ZipArchive zipFile = new ZipArchive(writer, ZipArchiveMode.Create))
+                        {
+                            var files = Directory.GetFiles(SourceDirectory, "*", SearchOption.AllDirectories);
+
+                            foreach (var file in files)
+                            {
+                                // look for a match
+                                bool foundMatch = false;
+                                foreach (var regex in regexes)
+                                {
+                                    if (regex.IsMatch(file))
+                                    {
+                                        foundMatch = true;
+                                        break;
+                                    }
+                                }
+
+                                if (foundMatch)
+                                {
+                                    Log.LogMessage(MessageImportance.Low, "Excluding {0} from archive.", file);
+                                    continue;
+                                }
+
+                                var relativePath = MakeRelativePath(SourceDirectory, file);
+                                zipFile.CreateEntryFromFile(file, relativePath, CompressionLevel.Optimal);
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                // We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
+                Log.LogError("An exception has occurred while trying to compress '{0}' into '{1}'.", SourceDirectory, DestinationArchive);
+                Log.LogErrorFromException(e, /*show stack=*/ true, /*show detail=*/ true, DestinationArchive);
+                return false;
+            }
+
+            return true;
+        }
+
+        private string MakeRelativePath(string root, string subdirectory)
+        {
+            if (!subdirectory.StartsWith(root))
+                throw new Exception(string.Format("'{0}' is not a subdirectory of '{1}'.", subdirectory, root));
+
+            // returned string should not start with a directory separator
+            int chop = root.Length;
+            if (subdirectory[chop] == Path.DirectorySeparatorChar)
+                ++chop;
+
+            return subdirectory.Substring(chop);
+        }
+    }
+}
diff --git a/tools-local/tasks/ZipFileExtractToDirectory.cs b/tools-local/tasks/ZipFileExtractToDirectory.cs
new file mode 100644 (file)
index 0000000..049ca12
--- /dev/null
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+using System.IO;
+using System.IO.Compression;
+
+namespace Microsoft.DotNet.Build.Tasks
+{
+    public sealed class ZipFileExtractToDirectory : BuildTask
+    {
+        /// <summary>
+        /// The path to the archive to be extracted.
+        /// </summary>
+        [Required]
+        public string SourceArchive { get; set; }
+
+        /// <summary>
+        /// The path of the directory to extract into.
+        /// </summary>
+        [Required]
+        public string DestinationDirectory { get; set; }
+
+        /// <summary>
+        /// Indicates if the destination directory should be overwritten if it already exists.
+        /// </summary>
+        public bool OverwriteDestination { get; set; }
+
+        /// <summary>
+        /// File entries to include in the extraction. Entries are relative
+        /// paths inside the archive. If null or empty, all files are extracted.
+        /// </summary>
+        public ITaskItem[] Include { get; set; }
+
+        public override bool Execute()
+        {
+            try
+            {
+                if (Directory.Exists(DestinationDirectory))
+                {
+                    if (OverwriteDestination)
+                    {
+                        Log.LogMessage(MessageImportance.Low, $"'{DestinationDirectory}' already exists, trying to delete before unzipping...");
+                        Directory.Delete(DestinationDirectory, recursive: true);
+                    }
+                    else
+                    {
+                        Log.LogWarning($"'{DestinationDirectory}' already exists. Did you forget to set '{nameof(OverwriteDestination)}' to true?");
+                    }
+                }
+
+                Log.LogMessage(MessageImportance.High, "Decompressing '{0}' into '{1}'...", SourceArchive, DestinationDirectory);
+                Directory.CreateDirectory(Path.GetDirectoryName(DestinationDirectory));
+
+                using (ZipArchive archive = ZipFile.OpenRead(SourceArchive))
+                {
+                    if (Include?.Length > 0)
+                    {
+                        foreach (ITaskItem entryItem in Include)
+                        {
+                            ZipArchiveEntry entry = archive.GetEntry(entryItem.ItemSpec);
+                            string destinationPath = Path.Combine(DestinationDirectory, entryItem.ItemSpec);
+
+                            Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
+                            entry.ExtractToFile(destinationPath, overwrite: false);
+                        }
+                    }
+                    else
+                    {
+                        archive.ExtractToDirectory(DestinationDirectory);
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                // We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
+                Log.LogError("An exception has occurred while trying to decompress '{0}' into '{1}'.", SourceArchive, DestinationDirectory);
+                Log.LogErrorFromException(e, /*show stack=*/ true, /*show detail=*/ true, DestinationDirectory);
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/tools-local/tasks/ZipFileGetEntries.cs b/tools-local/tasks/ZipFileGetEntries.cs
new file mode 100644 (file)
index 0000000..56b1e38
--- /dev/null
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+using System.IO.Compression;
+using System.Linq;
+
+namespace Microsoft.DotNet.Build.Tasks
+{
+    public sealed class ZipFileGetEntries : BuildTask
+    {
+        /// <summary>
+        /// The path to the archive.
+        /// </summary>
+        [Required]
+        public string TargetArchive { get; set; }
+
+        /// <summary>
+        /// Generated items where each ItemSpec is the relative location of a
+        /// file entry in the zip archive.
+        /// </summary>
+        [Output]
+        public ITaskItem[] Entries { get; set; }
+
+        public override bool Execute()
+        {
+            try
+            {
+                using (ZipArchive archive = ZipFile.OpenRead(TargetArchive))
+                {
+                    Entries = archive.Entries
+                        // Escape '%' so encoded '+' in the nupkg stays encoded through MSBuild.
+                        .Select(e => new TaskItem(e.FullName.Replace("%", "%25")))
+                        .ToArray();
+                }
+            }
+            catch (Exception e)
+            {
+                // We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
+                Log.LogError($"An exception has occurred while trying to read entries from '{TargetArchive}'.");
+                Log.LogErrorFromException(e, /*show stack=*/ true, /*show detail=*/ true, TargetArchive);
+                return false;
+            }
+            return true;
+        }
+    }
+}
index e9ab209..475a212 100644 (file)
@@ -1,23 +1,19 @@
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
-using Microsoft.Extensions.DependencyModel;
-using NuGet.Common;
-using NuGet.ProjectModel;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
 using System;
-using System.IO;
-using System.Linq;
 using System.Reflection;
 
 namespace Microsoft.DotNet.Build.Tasks
 {
     public partial class ProcessSharedFrameworkDeps
     {
-        partial void EnsureInitialized(string buildToolsTaskDir)
+        partial void EnsureInitialized(string buildTasksAssemblyPath)
         {
-            // ensure the build tools AssemblyResolver is enabled, so we get the correct assembly unification
-            // even if the build tools assembly hasn't been loaded yet.
-            string buildTasksPath = Path.Combine(buildToolsTaskDir, "Microsoft.DotNet.Build.Tasks.dll");
-            Assembly buildTasksAssembly = Assembly.Load(AssemblyName.GetAssemblyName(buildTasksPath));
+            // Ensure the Arcade AssemblyResolver is enabled, so we get the correct assembly
+            // unification even if an Arcade assembly hasn't been loaded yet.
+            Assembly buildTasksAssembly = Assembly.Load(AssemblyName.GetAssemblyName(buildTasksAssemblyPath));
             Type assemblyResolver = buildTasksAssembly.GetType("Microsoft.DotNet.Build.Common.Desktop.AssemblyResolver");
             assemblyResolver.GetMethod("Enable").Invoke(null, new object[] { });
         }