kateinoigakukun/swift-tar
A library for reading and writing TAR archives in Swift.
Features
- Read & write - iterate over archive entries or build new archives
- Foundation-free - works without Foundation or any system framework
- Cross-platform - macOS, Linux, Windows, WebAssembly, Embedded Swift
- GNU & PAX extensions - transparent handling of long paths, long link names, and PAX extended headers
Installation
Add swift-tar as a dependency in your Package.swift:
swift package add-dependency https://github.com/kateinoigakukun/swift-tar --up-to-next-minor-from 0.1.0
swift package add-target-dependency Tar <your-package-target-name> --package swift-tarQuick Start
Reading an Archive
import Tar
// `archiveBytes` is a [UInt8] containing the tar data
let archive = Archive(data: archiveBytes)
for entry in archive {
let path = entry.fields.path()
let size = entry.fields.size
print("\(path) (\(size) bytes)")
if entry.fields.effectiveEntryType().isFile {
let contents = entry.data // ArraySlice<UInt8>
// process file contents...
}
}Streaming Reader
import Tar
var reader = TarReader()
for chunk in chunks {
let events = try reader.append(chunk)
for event in events {
switch event {
case .entryStart(let fields):
print("\(fields.path()) (\(fields.size) bytes)")
case .data(let data):
// process streamed file contents...
print("received \(data.count) bytes")
case .entryEnd:
print("entry complete")
}
}
}
for event in try reader.finish() {
print(event)
}Extracting an Archive
import Tar
let archive = Archive(data: archiveBytes)
let result = try TarExtractor().extract(archive, to: "output-directory")
print("Extracted \(result.extractedEntries) entries")
print("Skipped \(result.skippedEntries) entries")Streaming Extraction
import Tar
var reader = TarReader()
var extractor = try TarExtractor().streamingExtractor(to: "output-directory")
for chunk in chunks {
let events = try reader.append(chunk)
try extractor.consume(events)
}
try extractor.consume(reader.finish())
let result = try extractor.finish()
print("Extracted \(result.extractedEntries) entries")
print("Skipped \(result.skippedEntries) entries")Creating an Archive
import Tar
var writer = TarWriter(mode: .deterministic)
// Add a file
let fileData: [UInt8] = Array("Hello, world!\n".utf8)
var header = Header(asGnu: ())
header.entryType = .regular
header.setSize(UInt64(fileData.count))
header.setMode(0o644)
writer.appendData(header: header, path: "hello.txt", data: fileData)
// Add a directory
writer.appendDir(path: "subdir/")
// Finalize and get the bytes
let archiveBytes = writer.finish()Performance
Extraction benchmark of the 3.5 GB swift-6.3-RELEASE-ubuntu24.04.tar (from swift.org) archive on MacBook Pro with Apple M4 Max, 16 cores, 64 GB RAM, macOS 26.4 (25E246) with Swift 6.3. The archive was read once before timing to warm the OS file cache. Measured with hyperfine --warmup 2 --runs 7. Each run extracted into a fresh temporary directory.
| Implementation | Command | Mean | Std. dev. | Median | | --- | --- | ---: | ---: | ---: | | swift-tar | Examples/.build/release/ExtractArchive | 2.918s | 0.107s | 2.896s | | libarchive | bsdtar -xf | 3.067s | 0.054s | 3.067s |
Running the Tests
The libarchive interoperability tests require test fixture files that are not stored in the repository. Fetch them before running swift test:
python3 Vendor/checkout-dependency libarchive
swift testLicense
This project is licensed under MIT License
Package Metadata
Repository: kateinoigakukun/swift-tar
Default branch: main
README: README.md