Skip to content

Add TarDirectory and Untar tasks for creating and extracting tar archives#14451

Open
ViktorHofer with Copilot wants to merge 28 commits into
mainfrom
copilot/create-tar-files-task
Open

Add TarDirectory and Untar tasks for creating and extracting tar archives#14451
ViktorHofer with Copilot wants to merge 28 commits into
mainfrom
copilot/create-tar-files-task

Conversation

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Adds a TarDirectory inbox task that produces .tar (and optionally .tar.gz) archives from a directory, mirroring the existing ZipDirectory task. Since System.Formats.Tar requires .NET, the task is authored as .NET-only and is unavailable in Visual Studio / MSBuild.exe.

Context

The existing ZipDirectory/Unzip tasks have no tar equivalent. The maintainer discussion settled on shipping this as the first .NET (core)-only inbox task, provided the UsingTask is authored to only bind on the .NET runtime.

Changes Made

  • TarDirectory task (src/Tasks/TarDirectory.cs): creates a tar from SourceDirectory to DestinationFile via System.Formats.Tar. Parameters mirror ZipDirectory: Overwrite, FailIfNotIncremental, plus an optional Compression (None default, or GZip).
    • Per review feedback, an existing destination is not silently deleted — it errors (MSB4322) unless Overwrite="true".
    • Invalid Compression values warn (MSB4324) and fall back to an uncompressed tar. zstd was omitted as it isn't in the BCL.
  • .NET-only gating: TarDirectory.cs compiles only for .NETCoreApp; the UsingTask in Microsoft.Common.tasks is registered with Runtime="NET" and Condition="'$(MSBuildRuntimeType)' == 'Core'".
  • Resources: new strings MSB4321MSB4330 in Strings.resx, synced to all .xlf files.
  • Tests: TarDirectory_Tests.cs (net-only) covering plain/GZip creation, overwrite, and the error/warning paths.
  • Docs: documented the FailIfNotIncremental behavior in documentation/specs/question.md.
<TarDirectory SourceDirectory="$(OutDir)" DestinationFile="package.tar.gz" Compression="GZip" Overwrite="true" />

Testing

Unit tests in TarDirectory_Tests.cs; also verified end-to-end via the bootstrap MSBuild that emitted .tar and .tar.gz archives readable by the system tar utility.

Notes

Two Code Review comments were intentionally left to stay byte-for-byte consistent with ZipDirectory: the "Question" doc-comment terminology, and reusing the Comment resource for question-mode output. Open to diverging if reviewers prefer.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hello @copilot, I noticed that you’re changing an .swr file or any file under src/Package/MSBuild.VSSetup.. Please make sure to validate this change by an experimental VS insertion. This is accomplished by pushing to an exp/* branch, which requires write permissions to this repo.

Copilot AI linked an issue Jul 20, 2026 that may be closed by this pull request
Copilot AI and others added 3 commits July 20, 2026 07:55
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
…rammar

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Copilot AI changed the title [WIP] Add MSBuild task for creating Tar files Add TarDirectory task for creating tar archives Jul 20, 2026
Copilot AI requested a review from ViktorHofer July 20, 2026 08:02
@ViktorHofer

Copy link
Copy Markdown
Member

@copilot looks like the current code falls back to not use gzip when a compression type is passed in that's unrecognized. Given that we will expand this matrix, I would throw an error if an unrecognized compression type is passed in instead of silently falling back to not use gzip.

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot looks like the current code falls back to not use gzip when a compression type is passed in that's unrecognized. Given that we will expand this matrix, I would throw an error ...

Done — an unrecognized Compression value now fails the task with error MSB4324 instead of warning and falling back to an uncompressed tar. Updated in the latest commit.

Copilot AI and others added 3 commits July 20, 2026 10:07
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
… in TrimStart

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
The stage1 and stage2 CI builds both default their binary log to Build.binlog, so publishing them to the same artifacts location collides. Declare ci/binaryLog/excludeCIBinarylog as first-class parameters, forward them to both stage builds, and pass -binaryLogName Build.stage2.binlog to stage2 when a binlog will actually be produced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2a6b38b1-0da7-488f-9938-3ac1051e15e2
Moving the stage 1 artifacts aside relocated its Build.binlog out of the published artifacts/log directory, so CI only published the stage 2 binlog. Copy the stage 1 binlog back as Build.stage1.binlog right after the move (best-effort; skipped when no binlog was produced).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2a6b38b1-0da7-488f-9938-3ac1051e15e2
Instead of copying just the stage 1 binlog, copy the whole stage 1 log directory back into artifacts/log after the move, so CI publishes all stage 1 logs alongside the stage 2 ones. Runs before the stage 2 build, so it won't clobber stage 2 output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2a6b38b1-0da7-488f-9938-3ac1051e15e2
… enum

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
Comment thread src/Tasks/TarDirectory.cs Outdated
@ViktorHofer

Copy link
Copy Markdown
Member

@copilot add an Untar task as well that mimics the Unzip task but goes hand in hand with new TarDirectory task. Add tests and update the PR description.

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>

Copilot AI commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot add an Untar task as well that mimics the Unzip task but goes hand in hand with new TarDirectory task. Add tests and update the PR description.

Added the Untar task in the latest commit. It mirrors Unzip: extracts one or more .tar archives to DestinationFolder, with Include/Exclude globs, SkipUnchangedFiles, OverwriteReadOnlyFiles, FailIfNotIncremental, cancellation, tar-slip path protection, and Unix permission restore. Compression (GZip/ZStandard) is auto-detected from the archive's magic bytes, so it handles .tar, .tar.gz, and .tar.zst without a Compression parameter. Like TarDirectory it's .NET-only (Runtime="NET", Condition="'$(MSBuildRuntimeType)' == 'Core'"). Added Untar_Tests.cs (12 tests, all passing) and new resources MSB4331MSB4338 synced to all .xlf files. PR description checklist updated.

@ViktorHofer ViktorHofer changed the title Add TarDirectory task for creating tar archives Add TarDirectory and Untar tasks for creating and extracting tar archives Jul 21, 2026
Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>
@ViktorHofer

Copy link
Copy Markdown
Member

@copilot fix the build failures

Co-authored-by: ViktorHofer <7412651+ViktorHofer@users.noreply.github.com>

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix the build failures

Fixed the build failure. The CI break was error IDE0052 under -warnaserror: the _enumOutput field in TaskBuilderTestTask was assigned in the EnumParam setter but never read (the tests validate via ParameterSet, and there's already an unrelated EnumOutput property). Removed the unused field and its assignment. Verified src/Build.UnitTests now builds clean with -warnaserror and the TestSetEnumParam* tests pass.

@ViktorHofer

ViktorHofer commented Jul 22, 2026

Copy link
Copy Markdown
Member

@baronfel if you have some time would you taking a look? The tasks are now using the new task types that you mentioned. Not a high priority at all at the moment.

@ViktorHofer
ViktorHofer marked this pull request as ready for review July 24, 2026 17:54
@ViktorHofer
ViktorHofer requested review from baronfel and Copilot July 24, 2026 17:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new .NET-only inbox tasks to create and extract tar archives (parity with existing ZipDirectory/Unzip tasks), plus supporting resource strings, task registration, and tests. It also extends engine parameter binding to support enum task parameters.

Changes:

  • Introduces TarDirectory and Untar tasks built on System.Formats.Tar, gated to MSBuild-on-.NET via UsingTask and .NETCoreApp-only compilation.
  • Adds new localized resources (MSB4321–MSB4340 bucket) and unit tests validating tar creation/extraction paths.
  • Updates task parameter binding (ValueTypeParser) and adds engine tests to validate enum parameter parsing.
Show a summary per file
File Description
src/Tasks/Untar.cs New task for extracting tar archives with include/exclude filtering, incremental-skip behavior, and path traversal protection.
src/Tasks/TarDirectory.cs New task for creating tar archives from directories, with overwrite and format/compression options.
src/Tasks/Resources/xlf/Strings.zh-Hant.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.zh-Hans.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.tr.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.ru.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.pt-BR.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.pl.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.ko.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.ja.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.it.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.fr.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.es.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.de.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/xlf/Strings.cs.xlf Localization entries for new TarDirectory/Untar strings.
src/Tasks/Resources/Strings.resx Adds TarDirectory/Untar resource strings and assigns MSB code ranges.
src/Tasks/Microsoft.Common.tasks Registers the new tasks as .NET-only UsingTask entries (Core runtime only).
src/Tasks/Microsoft.Build.Tasks.csproj Includes new task sources only when targeting .NETCoreApp.
src/Tasks.UnitTests/Untar_Tests.cs New unit tests for Untar behavior (filters, skipping unchanged, error paths).
src/Tasks.UnitTests/TarDirectory_Tests.cs New unit tests for TarDirectory (compression/format/overwrite/error paths).
src/Framework/ValueTypeParser.cs Adds enum parsing support for task parameter binding.
src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs Adds tests validating enum parameter binding behavior in the engine.
src/Build.UnitTests/BackEnd/TaskBuilderTestTask.cs Adds a test enum + parameter setter used by engine binding tests.
documentation/specs/question.md Documents TarDirectory “question/incremental” behavior (contains a correctness issue).

Copilot's findings

  • Files reviewed: 24/24 changed files
  • Comments generated: 6

Comment thread src/Tasks/TarDirectory.cs
Comment on lines +116 to +121
using Stream? compressionStream = Compression switch
{
TarCompression.GZip => new GZipStream(destinationStream, CompressionLevel.Optimal),
TarCompression.ZStandard => new ZstandardStream(destinationStream, CompressionLevel.Optimal),
_ => null,
};
Comment thread src/Tasks/Untar.cs
Comment on lines +205 to +209
// ZStandard magic number: 0x28 0xB5 0x2F 0xFD.
if (read >= 4 && magic[0] == 0x28 && magic[1] == 0xB5 && magic[2] == 0x2F && magic[3] == 0xFD)
{
return new ZstandardStream(stream, CompressionMode.Decompress, leaveOpen: true);
}
Comment on lines +209 to +215
Stream? decompressionStream = compression switch
{
TarDirectory.TarCompression.None => null,
TarDirectory.TarCompression.GZip => new GZipStream(stream, CompressionMode.Decompress),
TarDirectory.TarCompression.ZStandard => new ZstandardStream(stream, CompressionMode.Decompress),
_ => throw new ArgumentException($"Unexpected compression '{compression}'.", nameof(compression)),
};
Comment on lines +97 to +108
// Enums bind by their (case-insensitive) member name, and values that do not map to a
// defined member are rejected so the engine surfaces a clear parameter-binding error.
if (targetType.IsEnum)
{
object result = Enum.Parse(targetType, value, ignoreCase: true);
if (!Enum.IsDefined(targetType, result))
{
throw new ArgumentException($"'{value}' is not a defined value of enum type {targetType.Name}.", nameof(value));
}

return result;
}
Comment on lines +81 to +82
`TarDirectory`
Error if the destination tar file doesn't exist.
Comment thread src/Tasks/TarDirectory.cs
Comment on lines +47 to +53
/// <summary>
/// Gets or sets the compression to apply to the tar archive.
/// The default is <see cref="TarCompression.None"/>.
/// This parameter is optional.
/// </summary>
public TarCompression Compression { get; set; } = TarCompression.None;

@baronfel baronfel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but left a few comments for your consideration.

Comment thread src/Tasks/TarDirectory.cs
public TarEntryFormat Format { get; set; } = TarEntryFormat.Pax;

/// <inheritdoc />
public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this member required at all now? It's unused in the rest of the implementation.

Comment thread src/Tasks/TarDirectory.cs
return false;
}

BuildEngine3.Yield();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we yield so early? feels like we could push the yield down do line 105 or so and not deal with pre-emptive yielding for cases where the preconditions fail for this Task.

Comment thread src/Tasks/Untar.cs
/// Gets or sets a <see cref="ITaskItem"/> with a destination folder path to untar the files to.
/// </summary>
[Required]
public ITaskItem DestinationFolder { get; set; } = null!;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use typed binding here

Comment thread src/Tasks/Untar.cs
/// The compression (none, GZip, or ZStandard) is detected automatically from the archive contents.
/// </summary>
[Required]
public ITaskItem[] SourceFiles { get; set; } = null!;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typed bindings here too?

Comment thread src/Tasks/Untar.cs
/// <summary>
/// Gets or sets an MSBuild glob expression that specifies which files to include being untarred from the archive.
/// </summary>
public string? Include { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now you're making me want to support binding directly to glob expressions too :D

Comment thread src/Tasks/Untar.cs
public bool FailIfNotIncremental { get; set; }

/// <inheritdoc />
public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar question - if you use the typed bindings, using the FileInfo/DirectoryInfo APIs are safe so you may not need this member anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MSBuild task for creating Tar files

4 participants