// 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 { /// /// The path to the directory to be archived. /// [Required] public string SourceDirectory { get; set; } /// /// The path of the archive to be created. /// [Required] public string DestinationArchive { get; set; } /// /// Indicates if the destination archive should be overwritten if it already exists. /// public bool OverwriteDestination { get; set; } /// /// If zipping an entire folder without exclusion patterns, whether to include the folder in the archive. /// public bool IncludeBaseDirectory { get; set; } /// /// An item group of regular expressions for content to exclude from the archive. /// 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); if (!Directory.Exists(Path.GetDirectoryName(DestinationArchive))) Directory.CreateDirectory(Path.GetDirectoryName(DestinationArchive)); 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 occured while trying to compress '{0}' into '{1}'.", SourceDirectory, DestinationArchive); Log.LogMessage(MessageImportance.Low, e.ToString()); 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); } } }