// 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);
}
}
}