Skip to content

Commit

Permalink
Added Include/Exclude filtering capability to Unzip Task (#5169) (#6018)
Browse files Browse the repository at this point in the history
Added Include/Exclude filtering capability to Unzip Task via globs
  • Loading branch information
IvanLieckens committed Feb 6, 2021
1 parent 13de7ba commit 70f6767
Show file tree
Hide file tree
Showing 20 changed files with 564 additions and 33 deletions.
Expand Up @@ -1235,6 +1235,8 @@ public sealed partial class Unzip : Microsoft.Build.Tasks.TaskExtension, Microso
public Unzip() { }
[Microsoft.Build.Framework.RequiredAttribute]
public Microsoft.Build.Framework.ITaskItem DestinationFolder { get { throw null; } set { } }
public string Exclude { get { throw null; } set { } }
public string Include { get { throw null; } set { } }
public bool OverwriteReadOnlyFiles { get { throw null; } set { } }
public bool SkipUnchangedFiles { get { throw null; } set { } }
[Microsoft.Build.Framework.RequiredAttribute]
Expand Down
Expand Up @@ -912,6 +912,8 @@ public sealed partial class Unzip : Microsoft.Build.Tasks.TaskExtension, Microso
public Unzip() { }
[Microsoft.Build.Framework.RequiredAttribute]
public Microsoft.Build.Framework.ITaskItem DestinationFolder { get { throw null; } set { } }
public string Exclude { get { throw null; } set { } }
public string Include { get { throw null; } set { } }
public bool OverwriteReadOnlyFiles { get { throw null; } set { } }
public bool SkipUnchangedFiles { get { throw null; } set { } }
[Microsoft.Build.Framework.RequiredAttribute]
Expand Down
25 changes: 20 additions & 5 deletions src/Shared/FileMatcher.cs
Expand Up @@ -31,6 +31,8 @@ internal class FileMatcher
private static readonly char[] s_wildcardCharacters = { '*', '?' };
private static readonly char[] s_wildcardAndSemicolonCharacters = { '*', '?', ';' };

private static readonly string[] s_propertyAndItemReferences = { "$(", "@(" };

// on OSX both System.IO.Path separators are '/', so we have to use the literals
internal static readonly char[] directorySeparatorCharacters = { '/', '\\' };
internal static readonly string[] directorySeparatorStrings = directorySeparatorCharacters.Select(c => c.ToString()).ToArray();
Expand Down Expand Up @@ -166,8 +168,6 @@ internal static void ClearFileEnumerationsCache()
/// <summary>
/// Determines whether the given path has any wild card characters.
/// </summary>
/// <param name="filespec"></param>
/// <returns></returns>
internal static bool HasWildcards(string filespec)
{
// Perf Note: Doing a [Last]IndexOfAny(...) is much faster than compiling a
Expand All @@ -180,18 +180,33 @@ internal static bool HasWildcards(string filespec)
}

/// <summary>
/// Determines whether the given path has any wild card characters or any semicolons.
/// Determines whether the given path has any wild card characters or semicolons.
/// </summary>
internal static bool HasWildcardsOrSemicolon(string filespec)
{
return -1 != filespec.LastIndexOfAny(s_wildcardAndSemicolonCharacters);
}

/// <summary>
/// Determines whether the given path has any wild card characters, any semicolons or any property references.
/// </summary>
internal static bool HasWildcardsSemicolonItemOrPropertyReferences(string filespec)
{
return

(-1 != filespec.IndexOfAny(s_wildcardAndSemicolonCharacters)) ||
filespec.Contains("$(") ||
filespec.Contains("@(")
HasPropertyOrItemReferences(filespec)
;
}

/// <summary>
/// Determines whether the given path has any property references.
/// </summary>
internal static bool HasPropertyOrItemReferences(string filespec)
{
return s_propertyAndItemReferences.Any(filespec.Contains);
}

/// <summary>
/// Get the files and\or folders specified by the given path and pattern.
/// </summary>
Expand Down
207 changes: 207 additions & 0 deletions src/Tasks.UnitTests/Unzip_Tests.cs
Expand Up @@ -214,5 +214,212 @@ public void LogsErrorIfSourceFileDoesNotExist()
_mockEngine.Log.ShouldContain("MSB3932", () => _mockEngine.Log);
}
}

[Fact]
public void CanUnzip_WithIncludeFilter()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "BE78A17D30144B549D21F71D5C633F7D.txt"
};

unzip.Execute().ShouldBeTrue(() => _mockEngine.Log);

_mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "BE78A17D30144B549D21F71D5C633F7D.txt"), () => _mockEngine.Log);
_mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "A04FF4B88DF14860B7C73A8E75A4FB76.txt"), () => _mockEngine.Log);
}
}

[Fact]
public void CanUnzip_WithExcludeFilter()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Exclude = "BE78A17D30144B549D21F71D5C633F7D.txt"
};

unzip.Execute().ShouldBeTrue(() => _mockEngine.Log);

_mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "BE78A17D30144B549D21F71D5C633F7D.txt"), () => _mockEngine.Log);
_mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "A04FF4B88DF14860B7C73A8E75A4FB76.txt"), () => _mockEngine.Log);
}
}

[Fact]
public void CanUnzip_WithIncludeAndExcludeFilter()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
TransientTestFolder sub = source.CreateDirectory("sub");
testEnvironment.CreateFile(source, "file1.js", "file1");
testEnvironment.CreateFile(source, "file1.js.map", "file2");
testEnvironment.CreateFile(source, "file2.js", "file3");
testEnvironment.CreateFile(source, "readme.txt", "file4");
testEnvironment.CreateFile(sub, "subfile.js", "File5");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "*.js",
Exclude = "*.js.map;sub\\*.js"
};

unzip.Execute().ShouldBeTrue(() => _mockEngine.Log);

_mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "file1.js"), () => _mockEngine.Log);
_mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "file1.js.map"), () => _mockEngine.Log);
_mockEngine.Log.ShouldContain(Path.Combine(destination.Path, "file2.js"), () => _mockEngine.Log);
_mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "readme.txt"), () => _mockEngine.Log);
_mockEngine.Log.ShouldNotContain(Path.Combine(destination.Path, "sub", "subfile.js"), () => _mockEngine.Log);
}
}

[Fact]
public void LogsErrorIfIncludeContainsInvalidPathCharacters()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "<BE78A17D30144B|549D21F71D5C633F7D/.txt"
};

unzip.Execute().ShouldBeFalse(() => _mockEngine.Log);

_mockEngine.Log.ShouldContain("MSB3937", () => _mockEngine.Log);
}
}

[Fact]
public void LogsErrorIfIncludeContainsPropertyReferences()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Include = "$(Include)"
};

unzip.Execute().ShouldBeFalse(() => _mockEngine.Log);

_mockEngine.Log.ShouldContain("MSB3938", () => _mockEngine.Log);
}
}

[Fact]
public void LogsErrorIfExcludeContainsInvalidPathCharacters()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Exclude = "<BE78A17D30144B|549D21F71D5C633F7D/.txt"
};

unzip.Execute().ShouldBeFalse(() => _mockEngine.Log);

_mockEngine.Log.ShouldContain("MSB3937", () => _mockEngine.Log);
}
}

[Fact]
public void LogsErrorIfExcludeContainsPropertyReferences()
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
TransientTestFolder source = testEnvironment.CreateFolder(createFolder: true);
TransientTestFolder destination = testEnvironment.CreateFolder(createFolder: false);
testEnvironment.CreateFile(source, "BE78A17D30144B549D21F71D5C633F7D.txt", "file1");
testEnvironment.CreateFile(source, "A04FF4B88DF14860B7C73A8E75A4FB76.txt", "file2");

TransientZipArchive zipArchive = TransientZipArchive.Create(source, testEnvironment.CreateFolder(createFolder: true));

Unzip unzip = new Unzip
{
BuildEngine = _mockEngine,
DestinationFolder = new TaskItem(destination.Path),
OverwriteReadOnlyFiles = true,
SkipUnchangedFiles = false,
SourceFiles = new ITaskItem[] { new TaskItem(zipArchive.Path) },
Exclude = "$(Include)"
};

unzip.Execute().ShouldBeFalse(() => _mockEngine.Log);

_mockEngine.Log.ShouldContain("MSB3938", () => _mockEngine.Log);
}
}
}
}
11 changes: 11 additions & 0 deletions src/Tasks/Resources/Strings.resx
Expand Up @@ -2789,9 +2789,20 @@
<value>MSB3936: Failed to open unzip file "{0}" to "{1}". {2}</value>
<comment>{StrBegin="MSB3936: "}</comment>
</data>
<data name="Unzip.ErrorParsingPatternInvalidPath">
<value>MSB3937: Failed to parse pattern "{0}" because it contains an invalid path character.</value>
<comment>{StrBegin="MSB3937: "}</comment>
</data>
<data name="Unzip.ErrorParsingPatternPropertyReferences">
<value>MSB3938: Failed to parse pattern "{0}" because it contains a property reference which isn't supported.</value>
<comment>{StrBegin="MSB3938: "}</comment>
</data>
<data name="Unzip.DidNotUnzipBecauseOfFileMatch">
<value>Did not unzip from file "{0}" to file "{1}" because the "{2}" parameter was set to "{3}" in the project and the files' sizes and timestamps match.</value>
</data>
<data name="Unzip.DidNotUnzipBecauseOfFilter">
<value>Did not unzip file "{0}" because it didn't match the include filter or because it matched the exclude filter.</value>
</data>
<data name="Unzip.FileComment">
<value>Unzipping file "{0}" to "{1}".</value>
</data>
Expand Down
15 changes: 15 additions & 0 deletions src/Tasks/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 70f6767

Please sign in to comment.