From 3b2ceb2072a1d8812b112c6e6750062b8d4d1970 Mon Sep 17 00:00:00 2001 From: Nikola Milosavljevic Date: Fri, 10 Mar 2023 23:06:01 +0000 Subject: [PATCH] Add support for partial tarball extraction --- .../ExtractArchiveToDirectory.cs | 84 ++++++++++++++++--- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/src/core-sdk-tasks/ExtractArchiveToDirectory.cs b/src/core-sdk-tasks/ExtractArchiveToDirectory.cs index d0b9fb15f..e61f21e53 100644 --- a/src/core-sdk-tasks/ExtractArchiveToDirectory.cs +++ b/src/core-sdk-tasks/ExtractArchiveToDirectory.cs @@ -5,8 +5,12 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System; +#if !NETFRAMEWORK +using System.Formats.Tar; +#endif using System.IO; using System.IO.Compression; +using System.Linq; namespace Microsoft.DotNet.Build.Tasks { @@ -33,7 +37,7 @@ namespace Microsoft.DotNet.Build.Tasks public bool CleanDestination { get; set; } /// - /// A list of directories, semicolon deliminated, relative to the root of the archive to include. If empty all directories will be copied. + /// A list of directories, relative to the root of the archive to include. If empty all directories will be copied. /// public ITaskItem[] DirectoriesToCopy { get; set; } @@ -71,22 +75,26 @@ namespace Microsoft.DotNet.Build.Tasks public override bool Execute() { bool retVal = true; + bool isZipArchive = Path.GetExtension(SourceArchive).Equals(".zip", StringComparison.OrdinalIgnoreCase); + bool isTarballArchive = SourceArchive.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase); - // Inherits from ToolTask in order to shell out to tar. + // Inherits from ToolTask in order to shell out to tar for complete extraction // If the file is a .zip, then don't call the base Execute method, just run as a normal task - if (Path.GetExtension(SourceArchive).Equals(".zip", StringComparison.OrdinalIgnoreCase)) + // If the file is a .tar.gz, and DirectoriesToCopy isn't empty, also run a normal task. + if (isZipArchive || isTarballArchive) { if (ValidateParameters()) { if (DirectoriesToCopy != null && DirectoriesToCopy.Length != 0) { - var zip = new ZipArchive(File.OpenRead(SourceArchive)); - string loc = DestinationDirectory; - foreach (var entry in zip.Entries) + // Partial archive extraction + if (isZipArchive) { - foreach (var directory in DirectoriesToCopy) + var zip = new ZipArchive(File.OpenRead(SourceArchive)); + string loc = DestinationDirectory; + foreach (var entry in zip.Entries) { - if (entry.FullName.StartsWith(directory.ItemSpec)) + if (ShouldExtractItem(entry.FullName)) { if (!Directory.Exists(Path.Combine(DestinationDirectory, Path.GetDirectoryName(entry.FullName)))) { @@ -98,16 +106,57 @@ namespace Microsoft.DotNet.Build.Tasks } } } + else + { +#if NETFRAMEWORK + // Run the base tool, which uses external 'tar' command + retVal = base.Execute(); +#else + // Decompress GZip content + using FileStream compressedFileStream = File.Open(SourceArchive, FileMode.Open); + using var decompressor = new GZipStream(compressedFileStream, CompressionMode.Decompress); + using var decompressedStream = new MemoryStream(); + decompressor.CopyTo(decompressedStream); + decompressedStream.Seek(0, SeekOrigin.Begin); + + // Extract Tar content + using TarReader tr = new TarReader(decompressedStream); + while (tr.GetNextEntry() is TarEntry tarEntry) + { + if (tarEntry.EntryType != TarEntryType.Directory) + { + string entryName = tarEntry.Name; + entryName = entryName.StartsWith("./") ? entryName[2..] : entryName; + if (ShouldExtractItem(entryName)) + { + Log.LogMessage(entryName); + string destinationPath = Path.Combine(DestinationDirectory, entryName); + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + tarEntry.ExtractToFile(destinationPath, overwrite: true); + } + } + } +#endif + } } else - { + { + // Complete archive extraction + if (isZipArchive) + { #if NETFRAMEWORK - // .NET Framework doesn't have overload to overwrite files - ZipFile.ExtractToDirectory(SourceArchive, DestinationDirectory); + // .NET Framework doesn't have overload to overwrite files + ZipFile.ExtractToDirectory(SourceArchive, DestinationDirectory); #else - ZipFile.ExtractToDirectory(SourceArchive, DestinationDirectory, overwriteFiles: true); + ZipFile.ExtractToDirectory(SourceArchive, DestinationDirectory, overwriteFiles: true); #endif + } + else + { + // Run the base tool, which uses external 'tar' command + retVal = base.Execute(); + } } } else @@ -129,6 +178,17 @@ namespace Microsoft.DotNet.Build.Tasks return retVal; } + private bool ShouldExtractItem(string path) + { + if (DirectoriesToCopy != null) + { + return DirectoriesToCopy.Any(p => path.StartsWith(p.ItemSpec)); + + } + + return false; + } + protected override string ToolName { get { return "tar"; }