<?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>
--- /dev/null
+// 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;
+ }
+
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
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();
}
}
- partial void EnsureInitialized(string buildToolsTaskDir);
+ partial void EnsureInitialized(string buildTasksAssemblyPath);
}
}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
-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[] { });
}