From e4338d4df51a68a3f8f86c5d9e7b2d58fb8ca870 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 13:31:16 +0300 Subject: [PATCH 001/106] Drop support for Swift 5.3 and 5.4 Mainly because the macOS 11 images, required for these versions of Swift, will soon be deprecated in CI. --- Package.swift | 2 +- README.md | 2 +- azure-pipelines.yml | 23 ----------------------- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/Package.swift b/Package.swift index 2e2020db..1839e960 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.5 import PackageDescription let package = Package( diff --git a/README.md b/README.md index ce180397..426f40bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SWCompression -[![Swift 5.3+](https://img.shields.io/badge/Swift-5.3+-blue.svg)](https://developer.apple.com/swift/) +[![Swift 5.5+](https://img.shields.io/badge/Swift-5.5+-blue.svg)](https://developer.apple.com/swift/) [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/tsolomko/SWCompression/master/LICENSE) [![Build Status](https://dev.azure.com/tsolomko/SWCompression/_apis/build/status/tsolomko.SWCompression?branchName=develop)](https://dev.azure.com/tsolomko/SWCompression/_build/latest?definitionId=3&branchName=develop) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3d5dc588..e1dabecf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,18 +15,6 @@ stages: - job: macos strategy: matrix: - macosSwift53: - imageName: 'macOS-11' - DEVELOPER_DIR: '/Applications/Xcode_12.4.app' - IOS_SIMULATOR: 'iPhone 8' - WATCHOS_ACTIONS: 'clean build' - WATCHOS_SIMULATOR: 'Apple Watch Series 4 - 44mm' - macosSwift54: - imageName: 'macOS-11' - DEVELOPER_DIR: '/Applications/Xcode_12.5.1.app' - IOS_SIMULATOR: 'iPhone 8' - WATCHOS_ACTIONS: 'clean test' - WATCHOS_SIMULATOR: 'Apple Watch Series 6 - 44mm' macosSwift55: imageName: 'macOS-12' DEVELOPER_DIR: '/Applications/Xcode_13.2.1.app' @@ -99,12 +87,6 @@ stages: - job: linux strategy: matrix: - linuxSwift53: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.3.3-focal' - linuxSwift54: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.4.3-focal' linuxSwift55: imageName: 'ubuntu-20.04' containerImage: 'swift:5.5.3-focal' @@ -136,11 +118,6 @@ stages: - job: windows strategy: matrix: - windowsSwift54: - imageName: 'windows-2019' - SWIFT_VERSION: '5.4.3' - ICU_PATH: 'C:\Library\icu-67\usr\bin' - SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin' windowsSwift55: imageName: 'windows-2019' SWIFT_VERSION: '5.5.3' From ed03f6441d9c0d585cef50befe58c5196eee6d42 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 13:33:03 +0300 Subject: [PATCH 002/106] [CI] Remove a redundant flag from macos builds --- azure-pipelines.yml | 6 ------ utils.py | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e1dabecf..08d4eb5c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,38 +19,32 @@ stages: imageName: 'macOS-12' DEVELOPER_DIR: '/Applications/Xcode_13.2.1.app' IOS_SIMULATOR: 'iPhone 8' - WATCHOS_ACTIONS: 'clean test' WATCHOS_SIMULATOR: 'Apple Watch Series 6 - 44mm' macosSwift56: imageName: 'macOS-12' DEVELOPER_DIR: '/Applications/Xcode_13.4.1.app' IOS_SIMULATOR: 'iPhone 8' - WATCHOS_ACTIONS: 'clean test' WATCHOS_SIMULATOR: 'Apple Watch Series 6 - 44mm' macosSwift57: imageName: 'macOS-13' DEVELOPER_DIR: '/Applications/Xcode_14.2.app' IOS_SIMULATOR: 'iPhone 14' - WATCHOS_ACTIONS: 'clean test' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' macosSwift58: imageName: 'macOS-13' DEVELOPER_DIR: '/Applications/Xcode_14.3.1.app' IOS_SIMULATOR: 'iPhone 14' - WATCHOS_ACTIONS: 'clean test' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' macosSwift59: imageName: 'macOS-13' DEVELOPER_DIR: '/Applications/Xcode_15.2.app' IOS_SIMULATOR: 'iPhone 14' - WATCHOS_ACTIONS: 'clean test' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' # TODO: Enable when AZP adds macOS 14 images. # macosSwift510: # imageName: 'macOS-14' # DEVELOPER_DIR: '/Applications/Xcode_15.3.app' # IOS_SIMULATOR: 'iPhone 14' - # WATCHOS_ACTIONS: 'clean test' # WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' pool: vmImage: $(imageName) diff --git a/utils.py b/utils.py index aef6e96d..70b0fb0f 100755 --- a/utils.py +++ b/utils.py @@ -25,7 +25,7 @@ def _ci_script_macos(): xcodebuild_command_parts = ["xcodebuild", "-quiet", "-project", "SWCompression.xcodeproj", "-scheme", "SWCompression"] destinations_actions = [(["-destination 'platform=OS X'"], ["clean", "test"]), (["-destination 'platform=iOS Simulator,name=" + os.environ["IOS_SIMULATOR"] + "'"], ["clean", "test"]), - (["-destination 'platform=watchOS Simulator,name=" + os.environ["WATCHOS_SIMULATOR"] + "'"], [os.environ["WATCHOS_ACTIONS"]]), + (["-destination 'platform=watchOS Simulator,name=" + os.environ["WATCHOS_SIMULATOR"] + "'"], ["clean", "test"]), (["-destination 'platform=tvOS Simulator,name=Apple TV'"], ["clean", "test"])] for destination, actions in destinations_actions: From 7427a29d6d84a2d72411f39ae491de238937dc46 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 13:45:19 +0300 Subject: [PATCH 003/106] Fix podspec settings for the PrivacyInfo file --- SWCompression.podspec | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/SWCompression.podspec b/SWCompression.podspec index 562de57b..5519d2a3 100644 --- a/SWCompression.podspec +++ b/SWCompression.podspec @@ -25,70 +25,61 @@ Pod::Spec.new do |s| s.dependency "BitByteData", "~> 2.0" + s.resource_bundles = {"SWCompression" => ["Sources/PrivacyInfo.xcprivacy"]} + s.subspec "Deflate" do |sp| sp.source_files = "Sources/{Deflate/*,Common/*,Common/CodingTree/*}.swift" - sp.resource_bundles = {"SWCompression/Deflate" => ["Sources/PrivacyInfo.xcprivacy"]} sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_DEFLATE" } end s.subspec "GZip" do |sp| sp.dependency "SWCompression/Deflate" - sp.resource_bundles = {"SWCompression/GZip" => ["Sources/PrivacyInfo.xcprivacy"]} sp.source_files = "Sources/{GZip/*,Common/*}.swift" end s.subspec "Zlib" do |sp| sp.dependency "SWCompression/Deflate" - sp.resource_bundles = {"SWCompression/Zlib" => ["Sources/PrivacyInfo.xcprivacy"]} sp.source_files = "Sources/{Zlib/*,Common/*}.swift" end s.subspec "BZip2" do |sp| sp.source_files = "Sources/{BZip2/*,Common/*,Common/CodingTree/*}.swift" - sp.resource_bundles = {"SWCompression/BZip2" => ["Sources/PrivacyInfo.xcprivacy"]} sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_BZ2" } end s.subspec "LZMA" do |sp| sp.source_files = "Sources/{LZMA/*,Common/*}.swift" - sp.resource_bundles = {"SWCompression/LZMA" => ["Sources/PrivacyInfo.xcprivacy"]} sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_LZMA" } end s.subspec "LZMA2" do |sp| sp.dependency "SWCompression/LZMA" sp.source_files = "Sources/{LZMA2/*,Common/*}.swift" - sp.resource_bundles = {"SWCompression/LZMA2" => ["Sources/PrivacyInfo.xcprivacy"]} end s.subspec "LZ4" do |sp| sp.source_files = "Sources/{LZ4/*,Common/*}.swift" - sp.resource_bundles = {"SWCompression/LZ4" => ["Sources/PrivacyInfo.xcprivacy"]} sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_LZ4" } end s.subspec "XZ" do |sp| sp.dependency "SWCompression/LZMA2" sp.source_files = "Sources/{XZ/*,Common/*}.swift" - sp.resource_bundles = {"SWCompression/XZ" => ["Sources/PrivacyInfo.xcprivacy"]} end s.subspec "ZIP" do |sp| sp.dependency "SWCompression/Deflate" sp.source_files = "Sources/{Zip/*,Common/*,Common/Container/*}.swift" - sp.resource_bundles = {"SWCompression/ZIP" => ["Sources/PrivacyInfo.xcprivacy"]} sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_ZIP" } end s.subspec "TAR" do |sp| sp.source_files = "Sources/{TAR/*,Common/*,Common/Container/*}.swift" - sp.resource_bundles = {"SWCompression/TAR" => ["Sources/PrivacyInfo.xcprivacy"]} end s.subspec "SevenZip" do |sp| sp.dependency "SWCompression/LZMA2" sp.source_files = "Sources/{7-Zip/*,Common/*,Common/Container/*}.swift" - sp.resource_bundles = {"SWCompression/SevenZip" => ["Sources/PrivacyInfo.xcprivacy"]} sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_SEVENZIP" } end From 891848f9398bf29ca24461b4ce8227f5b5b18020 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 13:45:44 +0300 Subject: [PATCH 004/106] [swcomp] Remove redundant compiler check --- .../swcomp/Extensions/FileHandle+CloseCompat.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift b/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift index 941930f8..93e95e86 100644 --- a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift +++ b/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift @@ -8,15 +8,11 @@ import Foundation extension FileHandle { func closeCompat() throws { - #if compiler(<5.2) + if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { + try self.close() + } else { self.closeFile() - #else - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - try self.close() - } else { - self.closeFile() - } - #endif + } } } From ffa0ba30d686cbc39741547bae426cf31df8e012 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 13:45:58 +0300 Subject: [PATCH 005/106] Upgrade Xcode project format --- SWCompression.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 3e4b7fc1..f285838a 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 55; objects = { /* Begin PBXBuildFile section */ @@ -1179,7 +1179,7 @@ }; }; buildConfigurationList = 06BE1AC21DB410F100EE0F59 /* Build configuration list for PBXProject "SWCompression" */; - compatibilityVersion = "Xcode 11.4"; + compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( From 2927e6ecf3ce8440b8ca72af432168a87ec5b805 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 17:17:01 +0300 Subject: [PATCH 006/106] Increase minimum deployment targets on Apple platforms --- Package.swift | 8 ++++---- SWCompression.podspec | 8 ++++---- SWCompression.xcodeproj/project.pbxproj | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Package.swift b/Package.swift index 1839e960..323d637d 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription let package = Package( name: "SWCompression", platforms: [ - .macOS(.v10_13), - .iOS(.v11), - .tvOS(.v11), - .watchOS(.v4), + .macOS(.v11), + .iOS(.v14), + .tvOS(.v14), + .watchOS(.v7), // TODO: Enable after upgrading to Swift 5.9. // .visionOS(.v1) ], diff --git a/SWCompression.podspec b/SWCompression.podspec index 5519d2a3..2094cd8f 100644 --- a/SWCompression.podspec +++ b/SWCompression.podspec @@ -15,10 +15,10 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/tsolomko/SWCompression.git", :tag => "#{s.version}" } - s.ios.deployment_target = "11.0" - s.osx.deployment_target = "10.13" - s.tvos.deployment_target = "11.0" - s.watchos.deployment_target = "4.0" + s.ios.deployment_target = "14.0" + s.osx.deployment_target = "11.0" + s.tvos.deployment_target = "14.0" + s.watchos.deployment_target = "7.0" # s.visionos.deployment_target = "1.0" s.swift_versions = ["5"] diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index f285838a..ff7666c5 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1575,8 +1575,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.13; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; ONLY_ACTIVE_ARCH = YES; OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_LDFLAGS = ( @@ -1587,9 +1587,9 @@ SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 14.0; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 4.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; XROS_DEPLOYMENT_TARGET = 1.0; }; name = Debug; @@ -1643,8 +1643,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.13; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_LDFLAGS = ( "-framework", @@ -1654,9 +1654,9 @@ SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 14.0; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 4.0; + WATCHOS_DEPLOYMENT_TARGET = 7.0; XROS_DEPLOYMENT_TARGET = 1.0; }; name = Release; From fa4af5803a799a6e3df07dbe5732ebb5f5f8cb86 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 17:18:53 +0300 Subject: [PATCH 007/106] [TAR] Remove compatibility functions for FileHandle --- SWCompression.xcodeproj/project.pbxproj | 4 -- Sources/TAR/TarReader.swift | 60 ++++++------------- Sources/TAR/TarWriter.swift | 15 +---- Sources/swcomp/Benchmarks/Benchmarks.swift | 4 +- Sources/swcomp/Containers/TarCommand.swift | 6 +- .../Extensions/FileHandle+CloseCompat.swift | 18 ------ Tests/FileHandle+CloseCompat.swift | 18 ------ Tests/TarReaderTests.swift | 30 +++++----- Tests/TarWriterTests.swift | 2 +- 9 files changed, 40 insertions(+), 117 deletions(-) delete mode 100644 Sources/swcomp/Extensions/FileHandle+CloseCompat.swift delete mode 100644 Tests/FileHandle+CloseCompat.swift diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index ff7666c5..c9e94da2 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -229,7 +229,6 @@ E64F71BC26AAFD6A008BB308 /* Data+Tar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64F71BB26AAFD6A008BB308 /* Data+Tar.swift */; }; E6522FB42789AF9600026B56 /* TarReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6522FB32789AF9600026B56 /* TarReaderTests.swift */; }; E6522FB62789AFA600026B56 /* TarWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6522FB52789AFA600026B56 /* TarWriterTests.swift */; }; - E6522FBD2789C4A300026B56 /* FileHandle+CloseCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */; }; E652D8F3263D670900FC229B /* test_nonstandard_runlength.bz2 in Resources */ = {isa = PBXBuildFile; fileRef = E652D8F2263D670900FC229B /* test_nonstandard_runlength.bz2 */; }; E652D8F5263D678000FC229B /* test_nonstandard_runlength.answer in Resources */ = {isa = PBXBuildFile; fileRef = E652D8F4263D678000FC229B /* test_nonstandard_runlength.answer */; }; E652FECA27007E79006BC312 /* test3.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEC127007E78006BC312 /* test3.lz4 */; }; @@ -531,7 +530,6 @@ E64F71BB26AAFD6A008BB308 /* Data+Tar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Tar.swift"; sourceTree = ""; }; E6522FB32789AF9600026B56 /* TarReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TarReaderTests.swift; sourceTree = ""; }; E6522FB52789AFA600026B56 /* TarWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TarWriterTests.swift; sourceTree = ""; }; - E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+CloseCompat.swift"; sourceTree = ""; }; E652D8F2263D670900FC229B /* test_nonstandard_runlength.bz2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_nonstandard_runlength.bz2; sourceTree = ""; }; E652D8F4263D678000FC229B /* test_nonstandard_runlength.answer */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_nonstandard_runlength.answer; sourceTree = ""; }; E652FEC127007E78006BC312 /* test3.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test3.lz4; sourceTree = ""; }; @@ -844,7 +842,6 @@ E6522FB52789AFA600026B56 /* TarWriterTests.swift */, 06F065A61FFB763300312A82 /* TarTests.swift */, E6522FB32789AF9600026B56 /* TarReaderTests.swift */, - E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */, 06F065A91FFB763300312A82 /* XzTests.swift */, 06F065A41FFB763300312A82 /* ZipTests.swift */, 06D42DA62067B71000C1A98B /* TestZipExtraField.swift */, @@ -1506,7 +1503,6 @@ E6D86D2F26FE35C50032CFFA /* XxHash32Tests.swift in Sources */, 06F0661B1FFB763300312A82 /* XzTests.swift in Sources */, 06F0661E1FFB763300312A82 /* BZip2CompressionTests.swift in Sources */, - E6522FBD2789C4A300026B56 /* FileHandle+CloseCompat.swift in Sources */, 06F066131FFB763300312A82 /* Constants.swift in Sources */, 06F0661C1FFB763300312A82 /* SevenZipTests.swift in Sources */, 06F066161FFB763300312A82 /* ZipTests.swift in Sources */, diff --git a/Sources/TAR/TarReader.swift b/Sources/TAR/TarReader.swift index 565ce4b7..e7603a27 100644 --- a/Sources/TAR/TarReader.swift +++ b/Sources/TAR/TarReader.swift @@ -23,12 +23,6 @@ import BitByteData try handle.close() ``` Note that closing the `FileHandle` remains the responsibility of the caller. - - - Important: Due to the API availability limitations of Foundation's `FileHandle`, on certain platforms errors in - `FileHandle` operations may result in unrecoverable runtime failures due to unhandled Objective-C exceptions (which are - impossible to correctly handle in Swift code). As such, it is not recommended to use `TarReader` on those platforms. - The following platforms are _unaffected_ by this issue: macOS 10.15.4+, iOS 13.4+, watchOS 6.2+, tvOS 13.4+, and any - other platforms without Objective-C runtime. */ public struct TarReader { @@ -86,7 +80,7 @@ public struct TarReader { return nil } else if headerData == Data(count: 512) { // EOF marker case. - let offset = try getOffset() + let offset = try handle.offset() if try getData(size: 512) == Data(count: 512) { return nil } else { @@ -94,7 +88,7 @@ public struct TarReader { // match the EOF marker signature. In practice, this indicates a malformed TAR container, since a // zero-filled block is not a valid TAR header (and in fact the end result is an error being thrown in // TarHeader initializer later down the line). - try set(offset: offset) + try handle.seek(toOffset: offset) } } else if headerData.count < 512 { throw DataError.truncated @@ -106,7 +100,7 @@ public struct TarReader { // at most 512 bytes. // Check, just in case, since we use blockStartIndex = -1 when creating TAR containers. assert(header.blockStartIndex >= 0) - let dataStartOffset = try getOffset() + let dataStartOffset = try handle.offset() let entryData = try getData(size: header.size) guard entryData.count == header.size @@ -125,11 +119,11 @@ public struct TarReader { case .longName: longName = LittleEndianByteReader(data: entryData).tarCString(maxLength: header.size) } - try set(offset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512())) + try handle.seek(toOffset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512())) return try read() } else { let info = TarEntryInfo(header, lastGlobalExtendedHeader, lastLocalExtendedHeader, longName, longLinkName) - try set(offset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512())) + try handle.seek(toOffset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512())) lastLocalExtendedHeader = nil longName = nil longLinkName = nil @@ -144,40 +138,20 @@ public struct TarReader { } } - private func getOffset() throws -> UInt64 { - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - return try handle.offset() - } else { - return handle.offsetInFile - } - } - - private func set(offset: UInt64) throws { - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - try handle.seek(toOffset: offset) - } else { - handle.seek(toFileOffset: offset) - } - } - + @inline(__always) private func getData(size: Int) throws -> Data { assert(size >= 0, "TarReader.getData(size:): negative size.") - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - // The documentation for FileHandle.read(upToCount:) is a bit misleading. This method does "return the data - // obtained by reading length bytes starting at the current file pointer" even if the requested amount is - // larger than the available data. What is not clear is when the method returns nil. Apparently, there are - // (at least) two cases when it happens: - // - the file pointer is at the EOF regardless of the argument value, - // - the argument is zero. - // It is also unclear what happens when the argument is negative (it seems that it reads everything until - // the EOF), but the assertion above takes care of this. In any case, instead of returning nil we return - // empty data since both of these situations logically seem equivalent for our purposes. This also allows us - // to eliminate additional guard-check for the size parameter. - return try handle.read(upToCount: size) ?? Data() - } else { - // Technically, this can throw NSException, but since it is ObjC exception we cannot handle it in Swift. - return handle.readData(ofLength: size) - } + // The documentation for FileHandle.read(upToCount:) is a bit misleading. This method does "return the data + // obtained by reading length bytes starting at the current file pointer" even if the requested amount is + // larger than the available data. What is not clear is when the method returns nil. Apparently, there are + // (at least) two cases when it happens: + // - the file pointer is at the EOF regardless of the argument value, + // - the argument is zero. + // It is also unclear what happens when the argument is negative (it seems that it reads everything until + // the EOF), but the assertion above takes care of this. In any case, instead of returning nil we return + // empty data since both of these situations logically seem equivalent for our purposes. This also allows us + // to eliminate additional guard-check for the size parameter. + return try handle.read(upToCount: size) ?? Data() } } diff --git a/Sources/TAR/TarWriter.swift b/Sources/TAR/TarWriter.swift index b19d9746..5ffb8416 100644 --- a/Sources/TAR/TarWriter.swift +++ b/Sources/TAR/TarWriter.swift @@ -26,12 +26,6 @@ import Foundation ``` Note that `TarWriter.finalize()` must be called after finishing appending entries to the container. In addition, closing the `FileHandle` remains the responsibility of the caller. - - - Important: Due to the API availability limitations of Foundation's `FileHandle`, on certain platforms errors in - `FileHandle` operations may result in unrecoverable runtime failures due to unhandled Objective-C exceptions (which are - impossible to correctly handle in Swift code). As such, it is not recommended to use `TarWriter` on those platforms. - The following platforms are _unaffected_ by this issue: macOS 10.15.4+, iOS 13.4+, watchOS 6.2+, tvOS 13.4+, and any - other platforms without Objective-C runtime. */ public struct TarWriter { @@ -132,13 +126,8 @@ public struct TarWriter { } private func write(_ data: Data) throws { - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - try handle.write(contentsOf: data) - try handle.synchronize() - } else { - handle.write(data) - handle.synchronizeFile() - } + try handle.write(contentsOf: data) + try handle.synchronize() } } diff --git a/Sources/swcomp/Benchmarks/Benchmarks.swift b/Sources/swcomp/Benchmarks/Benchmarks.swift index 98ab8f51..44f3fc06 100644 --- a/Sources/swcomp/Benchmarks/Benchmarks.swift +++ b/Sources/swcomp/Benchmarks/Benchmarks.swift @@ -594,7 +594,7 @@ struct ReaderTar: Benchmark { } } let timeElapsed = Double(DispatchTime.now().uptimeNanoseconds - startTime) / 1_000_000_000 - try handle.closeCompat() + try handle.close() return self.size / timeElapsed } catch let error { swcompExit(.benchmarkCannotMeasure(Self.self, error)) @@ -629,7 +629,7 @@ struct WriterTar: Benchmark { } try writer.finalize() let timeElapsed = Double(DispatchTime.now().uptimeNanoseconds - startTime) / 1_000_000_000 - try handle.closeCompat() + try handle.close() try FileManager.default.removeItem(at: url) return self.size / timeElapsed } catch let error { diff --git a/Sources/swcomp/Containers/TarCommand.swift b/Sources/swcomp/Containers/TarCommand.swift index a01b6645..e58b8f44 100644 --- a/Sources/swcomp/Containers/TarCommand.swift +++ b/Sources/swcomp/Containers/TarCommand.swift @@ -119,7 +119,7 @@ final class TarCommand: Command { return false } } - try handle.closeCompat() + try handle.close() } } else if let outputPath = self.extract { guard try isValidOutputDirectory(outputPath, create: true) @@ -167,7 +167,7 @@ final class TarCommand: Command { return false } } - try handle.closeCompat() + try handle.close() for tuple in directoryAttributes { try fileManager.setAttributes(tuple.attributes, ofItemAtPath: tuple.path) @@ -222,7 +222,7 @@ final class TarCommand: Command { var writer = TarWriter(fileHandle: handle, force: self.useFormat ?? .pax) try TarEntry.generateEntries(&writer, inputPath, verbose) try writer.finalize() - try handle.closeCompat() + try handle.close() } } } diff --git a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift b/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift deleted file mode 100644 index 93e95e86..00000000 --- a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2024 Timofey Solomko -// Licensed under MIT License -// -// See LICENSE for license informations - -import Foundation - -extension FileHandle { - - func closeCompat() throws { - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - try self.close() - } else { - self.closeFile() - } - } - -} diff --git a/Tests/FileHandle+CloseCompat.swift b/Tests/FileHandle+CloseCompat.swift deleted file mode 100644 index 93e95e86..00000000 --- a/Tests/FileHandle+CloseCompat.swift +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2024 Timofey Solomko -// Licensed under MIT License -// -// See LICENSE for license informations - -import Foundation - -extension FileHandle { - - func closeCompat() throws { - if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - try self.close() - } else { - self.closeFile() - } - } - -} diff --git a/Tests/TarReaderTests.swift b/Tests/TarReaderTests.swift index 2bf525cd..be422acd 100644 --- a/Tests/TarReaderTests.swift +++ b/Tests/TarReaderTests.swift @@ -16,7 +16,7 @@ class TarReaderTests: XCTestCase { let testHandle = try Constants.handle(forTest: "test7", withType: "answer") var reader = TarReader(fileHandle: testHandle) XCTAssertThrowsError(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func test() throws { @@ -44,7 +44,7 @@ class TarReaderTests: XCTestCase { } } XCTAssertEqual(entriesCount, 1) - try testHandle.closeCompat() + try testHandle.close() } @@ -77,7 +77,7 @@ class TarReaderTests: XCTestCase { } } XCTAssertEqual(entriesCount, 5) - try testHandle.closeCompat() + try testHandle.close() } func testFormats() throws { @@ -104,7 +104,7 @@ class TarReaderTests: XCTestCase { } XCTAssertEqual(entriesCount, 1) XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } } @@ -126,7 +126,7 @@ class TarReaderTests: XCTestCase { } XCTAssertEqual(entriesCount, 6) XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } } @@ -160,7 +160,7 @@ class TarReaderTests: XCTestCase { XCTAssertEqual(entry!.data, "Hello, Windows!".data(using: .utf8)) } XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testEmptyFile() throws { @@ -181,7 +181,7 @@ class TarReaderTests: XCTestCase { } XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testEmptyDirectory() throws { @@ -201,7 +201,7 @@ class TarReaderTests: XCTestCase { XCTAssertNil(entry!.data) } XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testOnlyDirectoryHeader() throws { @@ -223,21 +223,21 @@ class TarReaderTests: XCTestCase { XCTAssertNil(entry!.data) } XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testEmptyContainer() throws { let testHandle = try Constants.handle(forTest: "test_empty_cont", withType: TarReaderTests.testType) var reader = TarReader(fileHandle: testHandle) XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testBigContainer() throws { let testHandle = try Constants.handle(forTest: "SWCompressionSourceCode", withType: TarReaderTests.testType) var reader = TarReader(fileHandle: testHandle) while try reader.read() != nil { } - try testHandle.closeCompat() + try testHandle.close() } func testUnicodeUstar() throws { @@ -258,7 +258,7 @@ class TarReaderTests: XCTestCase { } XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testUnicodePax() throws { @@ -279,7 +279,7 @@ class TarReaderTests: XCTestCase { } XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } func testGnuIncrementalFormat() throws { @@ -304,7 +304,7 @@ class TarReaderTests: XCTestCase { } XCTAssertEqual(entriesCount, 3) XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } // This test is impossible to implement using TarReader since the test file doesn't contain actual entry data. @@ -328,7 +328,7 @@ class TarReaderTests: XCTestCase { } // Test that reading after reaching EOF returns nil. XCTAssertNil(try reader.read()) - try testHandle.closeCompat() + try testHandle.close() } } diff --git a/Tests/TarWriterTests.swift b/Tests/TarWriterTests.swift index b19de81a..a31f0626 100644 --- a/Tests/TarWriterTests.swift +++ b/Tests/TarWriterTests.swift @@ -36,7 +36,7 @@ class TarWriterTests: XCTestCase { try writer.append(entry) } try writer.finalize() - try handle.closeCompat() + try handle.close() return try Data(contentsOf: tempFileUrl) } From 60b76e64908f26eaa68a88fc7f8f1d2a004c2edd Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 21 Apr 2024 17:24:19 +0300 Subject: [PATCH 008/106] [swcomp] Enable swcomp in the SPM manifest only on macOS Mainly, because SwiftCLI is incompatible with Linux and Windows, and we both don't need swcomp on these platforms (at the moment), and I am too busy to try to make a fork of SwiftCLI which fixes these issues. --- Package.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 323d637d..87d7d641 100644 --- a/Package.swift +++ b/Package.swift @@ -1,7 +1,7 @@ // swift-tools-version:5.5 import PackageDescription -let package = Package( +var package = Package( name: "SWCompression", platforms: [ .macOS(.v11), @@ -19,8 +19,6 @@ let package = Package( dependencies: [ .package(name: "BitByteData", url: "https://github.com/tsolomko/BitByteData", from: "2.0.0"), - .package(name: "SwiftCLI", url: "https://github.com/jakeheis/SwiftCLI", - from: "6.0.0"), ], targets: [ .target( @@ -30,12 +28,13 @@ let package = Package( exclude: ["swcomp"], sources: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib"], resources: [.copy("PrivacyInfo.xcprivacy")]), - .target( - name: "swcomp", - dependencies: ["SWCompression", "SwiftCLI"], - path: "Sources", - exclude: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib", "PrivacyInfo.xcprivacy"], - sources: ["swcomp"]), ], swiftLanguageVersions: [.v5] ) + +#if os(macOS) +package.dependencies.append(.package(name: "SwiftCLI", url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.0")) +package.targets.append(.executableTarget(name: "swcomp", dependencies: ["SWCompression", "SwiftCLI"], path: "Sources", + exclude: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib", "PrivacyInfo.xcprivacy"], + sources: ["swcomp"])) +#endif From 81cfa82f92eaedd5b28a131409e3713f7d5606ac Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 14 Dec 2024 17:43:57 +0200 Subject: [PATCH 009/106] Drop support for Swift 5.5 and 5.6 This is mainly driven by some CI configurations being deprecated. Additionally add builds for Swift 5.10 and Swift 6.0 in CI. --- Package.swift | 2 +- README.md | 2 +- SWCompression.xcodeproj/project.pbxproj | 6 +- .../xcschemes/SWCompression.xcscheme | 2 +- azure-pipelines.yml | 90 +++++++++---------- 5 files changed, 46 insertions(+), 56 deletions(-) diff --git a/Package.swift b/Package.swift index 87d7d641..af6070d3 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription var package = Package( diff --git a/README.md b/README.md index 426f40bf..cf343ba4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SWCompression -[![Swift 5.5+](https://img.shields.io/badge/Swift-5.5+-blue.svg)](https://developer.apple.com/swift/) +[![Swift 5.7+](https://img.shields.io/badge/Swift-5.7+-blue.svg)](https://developer.apple.com/swift/) [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/tsolomko/SWCompression/master/LICENSE) [![Build Status](https://dev.azure.com/tsolomko/SWCompression/_apis/build/status/tsolomko.SWCompression?branchName=develop)](https://dev.azure.com/tsolomko/SWCompression/_build/latest?definitionId=3&branchName=develop) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index c9e94da2..03dc30da 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -1162,7 +1162,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 1620; ORGANIZATIONNAME = "Timofey Solomko"; TargetAttributes = { 06BE1AC71DB410F100EE0F59 = { @@ -1176,7 +1176,7 @@ }; }; buildConfigurationList = 06BE1AC21DB410F100EE0F59 /* Build configuration list for PBXProject "SWCompression" */; - compatibilityVersion = "Xcode 13.0"; + compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( diff --git a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme index 7232fa36..f186312b 100644 --- a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme +++ b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme @@ -1,6 +1,6 @@ Date: Sat, 14 Dec 2024 17:44:51 +0200 Subject: [PATCH 010/106] Remove mention of xcode 12 workaround for testing from README --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cf343ba4..d9c770ec 100644 --- a/README.md +++ b/README.md @@ -187,14 +187,8 @@ git lfs pull git lfs checkout ``` -The first command will download the BitByteData dependency, which requires having Carthage installed. When using Xcode -12 or later, you should also pass the `--xcf` option, or use the -[xconfig workaround](https://github.com/Carthage/Carthage/blob/master/Documentation/Xcode12Workaround.md). Please note -that when building SWCompression's Xcode project you may see ld warnings about a directory not being found. These are -expected and harmless. Finally, you should keep in mind that support for non-xcframework method of installing -dependencies is likely to be dropped in the future major update. - -The remaining commands will download the files used in tests. These files are stored in a +The first command will download the BitByteData dependency, which requires having Carthage installed. The remaining +commands will download the files used in tests. These files are stored in a [separate repository](https://github.com/tsolomko/SWCompression-Test-Files), using Git LFS. There are two reasons for this complicated setup. Firstly, some of these files can be quite big, and it would be unfortunate if the users of SWCompression had to download them during the installation. Secondly, Swift Package Manager and contemporary versions of From ce5967585656f2b75bed86a7316a8846b8546e77 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 14 Dec 2024 17:45:04 +0200 Subject: [PATCH 011/106] Mention that swcomp is only available on macos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9c770ec..93207103 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Every function or type of SWCompression's public API is documented. This documen ### Sophisticated example There is a small command-line program, "swcomp", which is included in this repository in "Sources/swcomp". It can be -built using Swift Package Manager. +built using Swift Package Manager (only available on macOS). __IMPORTANT:__ The "swcomp" command-line tool is NOT intended for general use. From c0b07cfd4cc087f206782a66689f0860f78575e6 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 15 Dec 2024 11:23:04 +0200 Subject: [PATCH 012/106] Use non-deprecated methods in SPM manifest --- Package.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index af6070d3..6fba78b0 100644 --- a/Package.swift +++ b/Package.swift @@ -17,8 +17,7 @@ var package = Package( targets: ["SWCompression"]), ], dependencies: [ - .package(name: "BitByteData", url: "https://github.com/tsolomko/BitByteData", - from: "2.0.0"), + .package(url: "https://github.com/tsolomko/BitByteData", from: "2.0.0"), ], targets: [ .target( @@ -33,7 +32,7 @@ var package = Package( ) #if os(macOS) -package.dependencies.append(.package(name: "SwiftCLI", url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.0")) +package.dependencies.append(.package(url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.0")) package.targets.append(.executableTarget(name: "swcomp", dependencies: ["SWCompression", "SwiftCLI"], path: "Sources", exclude: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib", "PrivacyInfo.xcprivacy"], sources: ["swcomp"])) From 5ff7317a2f978b0686cf0148623bcfd69dc0e977 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 15 Dec 2024 11:24:55 +0200 Subject: [PATCH 013/106] [Tests] Fix modifying data in memory crashes on Swift 5.9+ The option mappedIfSafe used in loading test data seems to be actually mapping data into memory, which makes modifying data in place cause crashes. --- Tests/Constants.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Constants.swift b/Tests/Constants.swift index 12d89bf7..fcd42794 100644 --- a/Tests/Constants.swift +++ b/Tests/Constants.swift @@ -21,7 +21,7 @@ class Constants { static func data(forTest name: String, withType ext: String) throws -> Data { let url = Constants.url(forTest: name, withType: ext) - return try Data(contentsOf: url, options: .mappedIfSafe) + return try Data(contentsOf: url) } static func handle(forTest name: String, withType ext: String) throws -> FileHandle { @@ -35,7 +35,7 @@ class Constants { static func data(forAnswer name: String) throws -> Data { let url = Constants.url(forAnswer: name) - return try Data(contentsOf: url, options: .mappedIfSafe) + return try Data(contentsOf: url) } private static func url(forAnswer name: String) -> URL { From 17c0a1e52c533edc962d3584aa045eafc79406e4 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 15 Dec 2024 11:25:47 +0200 Subject: [PATCH 014/106] [CI] Update ios simulators for Swift 5.9 and 5.10 --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 73ac5769..2db0880e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,12 +28,12 @@ stages: macosSwift59: imageName: 'macOS-14' DEVELOPER_DIR: '/Applications/Xcode_15.2.app' - IOS_SIMULATOR: 'iPhone 14' + IOS_SIMULATOR: 'iPhone 15' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' macosSwift510: imageName: 'macOS-15' DEVELOPER_DIR: '/Applications/Xcode_15.4.app' - IOS_SIMULATOR: 'iPhone 14' + IOS_SIMULATOR: 'iPhone 15' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' macosSwift60: imageName: 'macOS-15' From e71c7d88f350b90365395c3fd6af61082344ee18 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 15 Dec 2024 19:35:13 +0200 Subject: [PATCH 015/106] [CI] Use docker for broken windows builds --- azure-pipelines.yml | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2db0880e..a075d1f1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -121,18 +121,6 @@ stages: ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' XCTEST_LIB_PATH: '\x86_64' - windowsSwift510: - imageName: 'windows-2019' - SWIFT_VERSION: '5.10.1' - ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' - SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' - XCTEST_LIB_PATH: '\x86_64' - windowsSwift60: - imageName: 'windows-2019' - SWIFT_VERSION: '6.0.3' - ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' - SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' - XCTEST_LIB_PATH: '\x86_64' pool: vmImage: $(imageName) variables: @@ -151,8 +139,26 @@ stages: - script: | set PATH=C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin;%SWIFT_DEV_PATH%;%ICU_PATH%;%LocalAppData%\Programs\Swift;%PATH% swift.exe --version - swift.exe build --target SWCompression %SWIFTFLAGS% - swift.exe build -c release --target SWCompression %SWIFTFLAGS% + swift.exe build %SWIFTFLAGS% + swift.exe build -c release %SWIFTFLAGS% + displayName: 'Build SPM Debug & Release' + - job: windowsDocker + strategy: + matrix: + windowsSwift510: + imageName: 'windows-2022' + containerImage: 'swift:5.10.1-windowsservercore-ltsc2022' + windowsSwift60: + imageName: 'windows-2022' + containerImage: 'swift:6.0.3-windowsservercore-ltsc2022' + pool: + vmImage: $(imageName) + container: $[ variables['containerImage'] ] + steps: + - script: | + swift.exe --version + swift.exe build + swift.exe build -c release displayName: 'Build SPM Debug & Release' - stage: deploy displayName: Deploy From ea9aaccfb9804e6821fd7310fdeab61ba112c546 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 15:30:24 +0800 Subject: [PATCH 016/106] Update copyright year to 2026 --- .jazzy.yaml | 2 +- LICENSE | 2 +- SWCompression.xcodeproj/SWCompression.plist | 2 +- Sources/7-Zip/7zCoder+Equatable.swift | 2 +- Sources/7-Zip/7zCoder.swift | 2 +- Sources/7-Zip/7zCoderInfo.swift | 2 +- Sources/7-Zip/7zContainer.swift | 2 +- Sources/7-Zip/7zEntry.swift | 2 +- Sources/7-Zip/7zEntryInfo.swift | 2 +- Sources/7-Zip/7zError.swift | 2 +- Sources/7-Zip/7zFileInfo.swift | 2 +- Sources/7-Zip/7zFolder.swift | 2 +- Sources/7-Zip/7zHeader.swift | 2 +- Sources/7-Zip/7zPackInfo.swift | 2 +- Sources/7-Zip/7zProperty.swift | 2 +- Sources/7-Zip/7zStreamInfo.swift | 2 +- Sources/7-Zip/7zSubstreamInfo.swift | 2 +- Sources/7-Zip/CompressionMethod+7z.swift | 2 +- Sources/7-Zip/MsbBitReader+7z.swift | 2 +- Sources/BZip2/BZip2+BlockSize.swift | 2 +- Sources/BZip2/BZip2+Compress.swift | 2 +- Sources/BZip2/BZip2+Lengths.swift | 2 +- Sources/BZip2/BZip2.swift | 2 +- Sources/BZip2/BZip2Error.swift | 2 +- Sources/BZip2/BurrowsWheeler.swift | 2 +- Sources/BZip2/SuffixArray.swift | 2 +- Sources/Common/Archive.swift | 2 +- Sources/Common/CheckSums.swift | 2 +- Sources/Common/CodingTree/Code.swift | 2 +- Sources/Common/CodingTree/CodeLength.swift | 2 +- Sources/Common/CodingTree/DecodingTree.swift | 2 +- Sources/Common/CodingTree/EncodingTree.swift | 2 +- Sources/Common/CompressionAlgorithm.swift | 2 +- Sources/Common/CompressionMethod.swift | 2 +- Sources/Common/Container/Container.swift | 2 +- Sources/Common/Container/ContainerEntry.swift | 2 +- Sources/Common/Container/ContainerEntryInfo.swift | 2 +- Sources/Common/Container/ContainerEntryType.swift | 2 +- Sources/Common/Container/DosAttributes.swift | 2 +- Sources/Common/Container/Permissions.swift | 2 +- Sources/Common/DataError.swift | 2 +- Sources/Common/DecompressionAlgorithm.swift | 2 +- Sources/Common/DeltaFilter.swift | 2 +- Sources/Common/Extensions.swift | 2 +- Sources/Common/FileSystemType.swift | 2 +- Sources/Deflate/Deflate+Compress.swift | 2 +- Sources/Deflate/Deflate+Constants.swift | 2 +- Sources/Deflate/Deflate+Lengths.swift | 2 +- Sources/Deflate/Deflate.swift | 2 +- Sources/Deflate/DeflateError.swift | 2 +- Sources/GZip/FileSystemType+Gzip.swift | 2 +- Sources/GZip/GzipArchive.swift | 2 +- Sources/GZip/GzipError.swift | 2 +- Sources/GZip/GzipHeader+ExtraField.swift | 2 +- Sources/GZip/GzipHeader.swift | 2 +- Sources/LZ4/LZ4+Compress.swift | 2 +- Sources/LZ4/LZ4.swift | 2 +- Sources/LZ4/XxHash32.swift | 2 +- Sources/LZMA/LZMA.swift | 2 +- Sources/LZMA/LZMABitTreeDecoder.swift | 2 +- Sources/LZMA/LZMAConstants.swift | 2 +- Sources/LZMA/LZMADecoder.swift | 2 +- Sources/LZMA/LZMAError.swift | 2 +- Sources/LZMA/LZMALenDecoder.swift | 2 +- Sources/LZMA/LZMAProperties.swift | 2 +- Sources/LZMA/LZMARangeDecoder.swift | 2 +- Sources/LZMA2/LZMA2.swift | 2 +- Sources/LZMA2/LZMA2Decoder.swift | 2 +- Sources/LZMA2/LZMA2Error.swift | 2 +- Sources/TAR/ContainerEntryType+Tar.swift | 2 +- Sources/TAR/Data+Tar.swift | 2 +- Sources/TAR/LittleEndianByteReader+Tar.swift | 2 +- Sources/TAR/TarContainer.swift | 2 +- Sources/TAR/TarCreateError.swift | 2 +- Sources/TAR/TarEntry.swift | 2 +- Sources/TAR/TarEntryInfo.swift | 2 +- Sources/TAR/TarError.swift | 2 +- Sources/TAR/TarExtendedHeader.swift | 2 +- Sources/TAR/TarHeader.swift | 2 +- Sources/TAR/TarParser.swift | 2 +- Sources/TAR/TarReader.swift | 2 +- Sources/TAR/TarWriter.swift | 2 +- Sources/XZ/ByteReader+XZ.swift | 2 +- Sources/XZ/Sha256.swift | 2 +- Sources/XZ/XZArchive.swift | 2 +- Sources/XZ/XZBlock.swift | 2 +- Sources/XZ/XZError.swift | 2 +- Sources/XZ/XZStreamHeader.swift | 2 +- Sources/ZIP/BuiltinExtraFields.swift | 2 +- Sources/ZIP/CompressionMethod+Zip.swift | 2 +- Sources/ZIP/FileSystemType+Zip.swift | 2 +- Sources/ZIP/LittleEndianByteReader+Zip.swift | 2 +- Sources/ZIP/ZipCentralDirectoryEntry.swift | 2 +- Sources/ZIP/ZipContainer.swift | 2 +- Sources/ZIP/ZipEndOfCentralDirectory.swift | 2 +- Sources/ZIP/ZipEntry.swift | 2 +- Sources/ZIP/ZipEntryInfo.swift | 2 +- Sources/ZIP/ZipEntryInfoHelper.swift | 2 +- Sources/ZIP/ZipError.swift | 2 +- Sources/ZIP/ZipExtraField.swift | 2 +- Sources/ZIP/ZipLocalHeader.swift | 2 +- Sources/Zlib/ZlibArchive.swift | 2 +- Sources/Zlib/ZlibError.swift | 2 +- Sources/Zlib/ZlibHeader.swift | 2 +- Sources/swcomp/Archives/BZip2Command.swift | 2 +- Sources/swcomp/Archives/GZipCommand.swift | 2 +- Sources/swcomp/Archives/LZ4Command.swift | 2 +- Sources/swcomp/Archives/LZMACommand.swift | 2 +- Sources/swcomp/Archives/XZCommand.swift | 2 +- Sources/swcomp/Benchmarks/Benchmark.swift | 2 +- Sources/swcomp/Benchmarks/BenchmarkGroup.swift | 2 +- Sources/swcomp/Benchmarks/BenchmarkMetadata.swift | 2 +- Sources/swcomp/Benchmarks/BenchmarkResult.swift | 2 +- Sources/swcomp/Benchmarks/Benchmarks.swift | 2 +- Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift | 2 +- Sources/swcomp/Benchmarks/SaveFile.swift | 2 +- Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 2 +- Sources/swcomp/Benchmarks/SpeedFormatter.swift | 2 +- Sources/swcomp/Containers/7ZipCommand.swift | 2 +- Sources/swcomp/Containers/CommonFunctions.swift | 2 +- Sources/swcomp/Containers/ContainerCommand.swift | 2 +- Sources/swcomp/Containers/TarCommand.swift | 2 +- Sources/swcomp/Containers/ZipCommand.swift | 2 +- .../Extensions/CompressionMethod+CustomStringConvertible.swift | 2 +- .../Extensions/ContainerEntryInfo+CustomStringConvertible.swift | 2 +- .../Extensions/FileSystemType+CustomStringConvertible.swift | 2 +- .../swcomp/Extensions/GzipHeader+CustomStringConvertible.swift | 2 +- Sources/swcomp/Extensions/TarEntry+Create.swift | 2 +- Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift | 2 +- Sources/swcomp/SwcompError.swift | 2 +- Sources/swcomp/main.swift | 2 +- Tests/BZip2CompressionTests.swift | 2 +- Tests/BZip2Tests.swift | 2 +- Tests/Constants.swift | 2 +- Tests/DeflateCompressionTests.swift | 2 +- Tests/GzipTests.swift | 2 +- Tests/LZ4CompressionTests.swift | 2 +- Tests/LZ4Tests.swift | 2 +- Tests/LzmaTests.swift | 2 +- Tests/SevenZipTests.swift | 2 +- Tests/Sha256Tests.swift | 2 +- Tests/TarCreateTests.swift | 2 +- Tests/TarReaderTests.swift | 2 +- Tests/TarTests.swift | 2 +- Tests/TarWriterTests.swift | 2 +- Tests/TestZipExtraField.swift | 2 +- Tests/XxHash32Tests.swift | 2 +- Tests/XzTests.swift | 2 +- Tests/ZipTests.swift | 2 +- Tests/ZlibTests.swift | 2 +- 150 files changed, 150 insertions(+), 150 deletions(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 23bfd367..d6975103 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -4,7 +4,7 @@ clean: false author: Timofey Solomko module: SWCompression module_version: 4.8.6 -copyright: '© 2024 Timofey Solomko' +copyright: '© 2026 Timofey Solomko' readme: README.md github_url: https://github.com/tsolomko/SWCompression github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.8.6 diff --git a/LICENSE b/LICENSE index 3dc58e8e..af3170d2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Timofey Solomko +Copyright (c) 2026 Timofey Solomko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/SWCompression.xcodeproj/SWCompression.plist b/SWCompression.xcodeproj/SWCompression.plist index 3d1f3e26..ea15ef36 100644 --- a/SWCompression.xcodeproj/SWCompression.plist +++ b/SWCompression.xcodeproj/SWCompression.plist @@ -19,6 +19,6 @@ CFBundleVersion 91 NSHumanReadableCopyright - Copyright © 2024 Timofey Solomko + Copyright © 2026 Timofey Solomko diff --git a/Sources/7-Zip/7zCoder+Equatable.swift b/Sources/7-Zip/7zCoder+Equatable.swift index 466713f1..96ed9f68 100644 --- a/Sources/7-Zip/7zCoder+Equatable.swift +++ b/Sources/7-Zip/7zCoder+Equatable.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zCoder.swift b/Sources/7-Zip/7zCoder.swift index 19b76995..fc41c480 100644 --- a/Sources/7-Zip/7zCoder.swift +++ b/Sources/7-Zip/7zCoder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zCoderInfo.swift b/Sources/7-Zip/7zCoderInfo.swift index 44255eb1..98ad29c9 100644 --- a/Sources/7-Zip/7zCoderInfo.swift +++ b/Sources/7-Zip/7zCoderInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zContainer.swift b/Sources/7-Zip/7zContainer.swift index 0f251711..685d58d5 100644 --- a/Sources/7-Zip/7zContainer.swift +++ b/Sources/7-Zip/7zContainer.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zEntry.swift b/Sources/7-Zip/7zEntry.swift index f6a5e394..46139a20 100644 --- a/Sources/7-Zip/7zEntry.swift +++ b/Sources/7-Zip/7zEntry.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zEntryInfo.swift b/Sources/7-Zip/7zEntryInfo.swift index ddfb6d93..31b63fea 100644 --- a/Sources/7-Zip/7zEntryInfo.swift +++ b/Sources/7-Zip/7zEntryInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zError.swift b/Sources/7-Zip/7zError.swift index 03fff69a..4b3492a0 100644 --- a/Sources/7-Zip/7zError.swift +++ b/Sources/7-Zip/7zError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zFileInfo.swift b/Sources/7-Zip/7zFileInfo.swift index 03241362..6f45fc5f 100644 --- a/Sources/7-Zip/7zFileInfo.swift +++ b/Sources/7-Zip/7zFileInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zFolder.swift b/Sources/7-Zip/7zFolder.swift index 3cc5ddec..d917a12e 100644 --- a/Sources/7-Zip/7zFolder.swift +++ b/Sources/7-Zip/7zFolder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zHeader.swift b/Sources/7-Zip/7zHeader.swift index 1a50cdba..301e0e78 100644 --- a/Sources/7-Zip/7zHeader.swift +++ b/Sources/7-Zip/7zHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zPackInfo.swift b/Sources/7-Zip/7zPackInfo.swift index 9998bcd6..fe8bb595 100644 --- a/Sources/7-Zip/7zPackInfo.swift +++ b/Sources/7-Zip/7zPackInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zProperty.swift b/Sources/7-Zip/7zProperty.swift index b817adc4..e5751078 100644 --- a/Sources/7-Zip/7zProperty.swift +++ b/Sources/7-Zip/7zProperty.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zStreamInfo.swift b/Sources/7-Zip/7zStreamInfo.swift index f8bca63b..b7e82a19 100644 --- a/Sources/7-Zip/7zStreamInfo.swift +++ b/Sources/7-Zip/7zStreamInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/7zSubstreamInfo.swift b/Sources/7-Zip/7zSubstreamInfo.swift index 1bcc715d..442ea2b0 100644 --- a/Sources/7-Zip/7zSubstreamInfo.swift +++ b/Sources/7-Zip/7zSubstreamInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/CompressionMethod+7z.swift b/Sources/7-Zip/CompressionMethod+7z.swift index 103775ad..f5168bc9 100644 --- a/Sources/7-Zip/CompressionMethod+7z.swift +++ b/Sources/7-Zip/CompressionMethod+7z.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/7-Zip/MsbBitReader+7z.swift b/Sources/7-Zip/MsbBitReader+7z.swift index 55d4bfbf..70044388 100644 --- a/Sources/7-Zip/MsbBitReader+7z.swift +++ b/Sources/7-Zip/MsbBitReader+7z.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/BZip2+BlockSize.swift b/Sources/BZip2/BZip2+BlockSize.swift index 63b310aa..215edbc0 100644 --- a/Sources/BZip2/BZip2+BlockSize.swift +++ b/Sources/BZip2/BZip2+BlockSize.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index 676e971e..ed4cf7dc 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/BZip2+Lengths.swift b/Sources/BZip2/BZip2+Lengths.swift index 824307d6..799507f8 100644 --- a/Sources/BZip2/BZip2+Lengths.swift +++ b/Sources/BZip2/BZip2+Lengths.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index 2bc26727..efbb844b 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/BZip2Error.swift b/Sources/BZip2/BZip2Error.swift index e5ee5579..c58b90c9 100644 --- a/Sources/BZip2/BZip2Error.swift +++ b/Sources/BZip2/BZip2Error.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/BurrowsWheeler.swift b/Sources/BZip2/BurrowsWheeler.swift index 59ff64e5..80cae086 100644 --- a/Sources/BZip2/BurrowsWheeler.swift +++ b/Sources/BZip2/BurrowsWheeler.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/BZip2/SuffixArray.swift b/Sources/BZip2/SuffixArray.swift index 5b1c0f26..6c7018d1 100644 --- a/Sources/BZip2/SuffixArray.swift +++ b/Sources/BZip2/SuffixArray.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Archive.swift b/Sources/Common/Archive.swift index 1daca0d5..a0930705 100644 --- a/Sources/Common/Archive.swift +++ b/Sources/Common/Archive.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CheckSums.swift b/Sources/Common/CheckSums.swift index 5b8c7ddc..047cbe7a 100644 --- a/Sources/Common/CheckSums.swift +++ b/Sources/Common/CheckSums.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CodingTree/Code.swift b/Sources/Common/CodingTree/Code.swift index 8992126f..fcbe7999 100644 --- a/Sources/Common/CodingTree/Code.swift +++ b/Sources/Common/CodingTree/Code.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CodingTree/CodeLength.swift b/Sources/Common/CodingTree/CodeLength.swift index eabfa41a..21bf5ff6 100644 --- a/Sources/Common/CodingTree/CodeLength.swift +++ b/Sources/Common/CodingTree/CodeLength.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CodingTree/DecodingTree.swift b/Sources/Common/CodingTree/DecodingTree.swift index a502baa1..8149f3e9 100644 --- a/Sources/Common/CodingTree/DecodingTree.swift +++ b/Sources/Common/CodingTree/DecodingTree.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CodingTree/EncodingTree.swift b/Sources/Common/CodingTree/EncodingTree.swift index 3fb51133..9c5125d4 100644 --- a/Sources/Common/CodingTree/EncodingTree.swift +++ b/Sources/Common/CodingTree/EncodingTree.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CompressionAlgorithm.swift b/Sources/Common/CompressionAlgorithm.swift index 1ae63323..e3dc9a77 100644 --- a/Sources/Common/CompressionAlgorithm.swift +++ b/Sources/Common/CompressionAlgorithm.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/CompressionMethod.swift b/Sources/Common/CompressionMethod.swift index 1b796ff4..cede6f79 100644 --- a/Sources/Common/CompressionMethod.swift +++ b/Sources/Common/CompressionMethod.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Container/Container.swift b/Sources/Common/Container/Container.swift index f700655e..a2ef214b 100644 --- a/Sources/Common/Container/Container.swift +++ b/Sources/Common/Container/Container.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Container/ContainerEntry.swift b/Sources/Common/Container/ContainerEntry.swift index def4a7e0..f0e58c25 100644 --- a/Sources/Common/Container/ContainerEntry.swift +++ b/Sources/Common/Container/ContainerEntry.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Container/ContainerEntryInfo.swift b/Sources/Common/Container/ContainerEntryInfo.swift index 14d5a8dd..7b8c653f 100644 --- a/Sources/Common/Container/ContainerEntryInfo.swift +++ b/Sources/Common/Container/ContainerEntryInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Container/ContainerEntryType.swift b/Sources/Common/Container/ContainerEntryType.swift index 09578c22..441de008 100644 --- a/Sources/Common/Container/ContainerEntryType.swift +++ b/Sources/Common/Container/ContainerEntryType.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Container/DosAttributes.swift b/Sources/Common/Container/DosAttributes.swift index 48d7cd7e..fad1f7b1 100644 --- a/Sources/Common/Container/DosAttributes.swift +++ b/Sources/Common/Container/DosAttributes.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Container/Permissions.swift b/Sources/Common/Container/Permissions.swift index 1286a095..d476395f 100644 --- a/Sources/Common/Container/Permissions.swift +++ b/Sources/Common/Container/Permissions.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/DataError.swift b/Sources/Common/DataError.swift index f02e111c..bcccd831 100644 --- a/Sources/Common/DataError.swift +++ b/Sources/Common/DataError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/DecompressionAlgorithm.swift b/Sources/Common/DecompressionAlgorithm.swift index 254f03ba..9fa30b5c 100644 --- a/Sources/Common/DecompressionAlgorithm.swift +++ b/Sources/Common/DecompressionAlgorithm.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/DeltaFilter.swift b/Sources/Common/DeltaFilter.swift index e75bc14f..795a3fe6 100644 --- a/Sources/Common/DeltaFilter.swift +++ b/Sources/Common/DeltaFilter.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/Extensions.swift b/Sources/Common/Extensions.swift index bb602e29..ea8ba358 100644 --- a/Sources/Common/Extensions.swift +++ b/Sources/Common/Extensions.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Common/FileSystemType.swift b/Sources/Common/FileSystemType.swift index 97a2dc55..7763f5ce 100644 --- a/Sources/Common/FileSystemType.swift +++ b/Sources/Common/FileSystemType.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Deflate/Deflate+Compress.swift b/Sources/Deflate/Deflate+Compress.swift index f7842601..1bb39043 100644 --- a/Sources/Deflate/Deflate+Compress.swift +++ b/Sources/Deflate/Deflate+Compress.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Deflate/Deflate+Constants.swift b/Sources/Deflate/Deflate+Constants.swift index 0c4c9ff3..ca7a2cea 100644 --- a/Sources/Deflate/Deflate+Constants.swift +++ b/Sources/Deflate/Deflate+Constants.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Deflate/Deflate+Lengths.swift b/Sources/Deflate/Deflate+Lengths.swift index dd265c6b..815d7f5d 100644 --- a/Sources/Deflate/Deflate+Lengths.swift +++ b/Sources/Deflate/Deflate+Lengths.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index 56b6cf4c..c49a3504 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Deflate/DeflateError.swift b/Sources/Deflate/DeflateError.swift index 802e5c30..439e39aa 100644 --- a/Sources/Deflate/DeflateError.swift +++ b/Sources/Deflate/DeflateError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/GZip/FileSystemType+Gzip.swift b/Sources/GZip/FileSystemType+Gzip.swift index 6bdbf6b3..d9a98280 100644 --- a/Sources/GZip/FileSystemType+Gzip.swift +++ b/Sources/GZip/FileSystemType+Gzip.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/GZip/GzipArchive.swift b/Sources/GZip/GzipArchive.swift index 32bd9701..8b09dd0a 100644 --- a/Sources/GZip/GzipArchive.swift +++ b/Sources/GZip/GzipArchive.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/GZip/GzipError.swift b/Sources/GZip/GzipError.swift index 6e037c30..2622dc4f 100644 --- a/Sources/GZip/GzipError.swift +++ b/Sources/GZip/GzipError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/GZip/GzipHeader+ExtraField.swift b/Sources/GZip/GzipHeader+ExtraField.swift index b897c0b1..338c0cb8 100644 --- a/Sources/GZip/GzipHeader+ExtraField.swift +++ b/Sources/GZip/GzipHeader+ExtraField.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/GZip/GzipHeader.swift b/Sources/GZip/GzipHeader.swift index 03b7098b..e3ea37fc 100644 --- a/Sources/GZip/GzipHeader.swift +++ b/Sources/GZip/GzipHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZ4/LZ4+Compress.swift b/Sources/LZ4/LZ4+Compress.swift index dfb57743..b0edcb4e 100644 --- a/Sources/LZ4/LZ4+Compress.swift +++ b/Sources/LZ4/LZ4+Compress.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZ4/LZ4.swift b/Sources/LZ4/LZ4.swift index aeb1c7c1..6d4aef2c 100644 --- a/Sources/LZ4/LZ4.swift +++ b/Sources/LZ4/LZ4.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZ4/XxHash32.swift b/Sources/LZ4/XxHash32.swift index 768c9b11..260b68ff 100644 --- a/Sources/LZ4/XxHash32.swift +++ b/Sources/LZ4/XxHash32.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMA.swift b/Sources/LZMA/LZMA.swift index 6d7bbe7a..a75a6444 100644 --- a/Sources/LZMA/LZMA.swift +++ b/Sources/LZMA/LZMA.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMABitTreeDecoder.swift b/Sources/LZMA/LZMABitTreeDecoder.swift index 8bd30f99..65ac172a 100644 --- a/Sources/LZMA/LZMABitTreeDecoder.swift +++ b/Sources/LZMA/LZMABitTreeDecoder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMAConstants.swift b/Sources/LZMA/LZMAConstants.swift index ebea1b77..e686c5a4 100644 --- a/Sources/LZMA/LZMAConstants.swift +++ b/Sources/LZMA/LZMAConstants.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMADecoder.swift b/Sources/LZMA/LZMADecoder.swift index 2564fd5f..095c1e14 100644 --- a/Sources/LZMA/LZMADecoder.swift +++ b/Sources/LZMA/LZMADecoder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMAError.swift b/Sources/LZMA/LZMAError.swift index 95f151ca..10175b84 100644 --- a/Sources/LZMA/LZMAError.swift +++ b/Sources/LZMA/LZMAError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMALenDecoder.swift b/Sources/LZMA/LZMALenDecoder.swift index 271d9c68..6c79a6ae 100644 --- a/Sources/LZMA/LZMALenDecoder.swift +++ b/Sources/LZMA/LZMALenDecoder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMAProperties.swift b/Sources/LZMA/LZMAProperties.swift index 0a81083b..340a586f 100644 --- a/Sources/LZMA/LZMAProperties.swift +++ b/Sources/LZMA/LZMAProperties.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA/LZMARangeDecoder.swift b/Sources/LZMA/LZMARangeDecoder.swift index e43e21a5..91b896b8 100644 --- a/Sources/LZMA/LZMARangeDecoder.swift +++ b/Sources/LZMA/LZMARangeDecoder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA2/LZMA2.swift b/Sources/LZMA2/LZMA2.swift index 46cbe674..501396d9 100644 --- a/Sources/LZMA2/LZMA2.swift +++ b/Sources/LZMA2/LZMA2.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA2/LZMA2Decoder.swift b/Sources/LZMA2/LZMA2Decoder.swift index d467d3f8..416271c8 100644 --- a/Sources/LZMA2/LZMA2Decoder.swift +++ b/Sources/LZMA2/LZMA2Decoder.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/LZMA2/LZMA2Error.swift b/Sources/LZMA2/LZMA2Error.swift index 636b513c..af0c3472 100644 --- a/Sources/LZMA2/LZMA2Error.swift +++ b/Sources/LZMA2/LZMA2Error.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/ContainerEntryType+Tar.swift b/Sources/TAR/ContainerEntryType+Tar.swift index 1602d3be..3d1e5b56 100644 --- a/Sources/TAR/ContainerEntryType+Tar.swift +++ b/Sources/TAR/ContainerEntryType+Tar.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/Data+Tar.swift b/Sources/TAR/Data+Tar.swift index 984c6324..8dbfdd3e 100644 --- a/Sources/TAR/Data+Tar.swift +++ b/Sources/TAR/Data+Tar.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/LittleEndianByteReader+Tar.swift b/Sources/TAR/LittleEndianByteReader+Tar.swift index 5f22a051..4ef2a45a 100644 --- a/Sources/TAR/LittleEndianByteReader+Tar.swift +++ b/Sources/TAR/LittleEndianByteReader+Tar.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarContainer.swift b/Sources/TAR/TarContainer.swift index 8c2db113..dc9ddaeb 100644 --- a/Sources/TAR/TarContainer.swift +++ b/Sources/TAR/TarContainer.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarCreateError.swift b/Sources/TAR/TarCreateError.swift index 61ead2bc..072f7acf 100644 --- a/Sources/TAR/TarCreateError.swift +++ b/Sources/TAR/TarCreateError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarEntry.swift b/Sources/TAR/TarEntry.swift index 06625758..ea685eb6 100644 --- a/Sources/TAR/TarEntry.swift +++ b/Sources/TAR/TarEntry.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarEntryInfo.swift b/Sources/TAR/TarEntryInfo.swift index 18b3ed24..0f4842e6 100644 --- a/Sources/TAR/TarEntryInfo.swift +++ b/Sources/TAR/TarEntryInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarError.swift b/Sources/TAR/TarError.swift index 2b9fe91e..0dcce194 100644 --- a/Sources/TAR/TarError.swift +++ b/Sources/TAR/TarError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarExtendedHeader.swift b/Sources/TAR/TarExtendedHeader.swift index aa052c10..1ff9becb 100644 --- a/Sources/TAR/TarExtendedHeader.swift +++ b/Sources/TAR/TarExtendedHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarHeader.swift b/Sources/TAR/TarHeader.swift index fb4b660c..95f1f950 100644 --- a/Sources/TAR/TarHeader.swift +++ b/Sources/TAR/TarHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarParser.swift b/Sources/TAR/TarParser.swift index 327d3db0..a7bc1b23 100644 --- a/Sources/TAR/TarParser.swift +++ b/Sources/TAR/TarParser.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarReader.swift b/Sources/TAR/TarReader.swift index e7603a27..f961e458 100644 --- a/Sources/TAR/TarReader.swift +++ b/Sources/TAR/TarReader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/TAR/TarWriter.swift b/Sources/TAR/TarWriter.swift index 5ffb8416..8003a83f 100644 --- a/Sources/TAR/TarWriter.swift +++ b/Sources/TAR/TarWriter.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/XZ/ByteReader+XZ.swift b/Sources/XZ/ByteReader+XZ.swift index b8de7d96..94bd8890 100644 --- a/Sources/XZ/ByteReader+XZ.swift +++ b/Sources/XZ/ByteReader+XZ.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/XZ/Sha256.swift b/Sources/XZ/Sha256.swift index cca5894a..dc15f39d 100644 --- a/Sources/XZ/Sha256.swift +++ b/Sources/XZ/Sha256.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/XZ/XZArchive.swift b/Sources/XZ/XZArchive.swift index bfc275ac..48ccbd97 100644 --- a/Sources/XZ/XZArchive.swift +++ b/Sources/XZ/XZArchive.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/XZ/XZBlock.swift b/Sources/XZ/XZBlock.swift index 0c984df7..3fe2f3ce 100644 --- a/Sources/XZ/XZBlock.swift +++ b/Sources/XZ/XZBlock.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/XZ/XZError.swift b/Sources/XZ/XZError.swift index 5a6b52a2..8a301a6b 100644 --- a/Sources/XZ/XZError.swift +++ b/Sources/XZ/XZError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/XZ/XZStreamHeader.swift b/Sources/XZ/XZStreamHeader.swift index be5b581a..333f78f7 100644 --- a/Sources/XZ/XZStreamHeader.swift +++ b/Sources/XZ/XZStreamHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/BuiltinExtraFields.swift b/Sources/ZIP/BuiltinExtraFields.swift index 345af697..70293b27 100644 --- a/Sources/ZIP/BuiltinExtraFields.swift +++ b/Sources/ZIP/BuiltinExtraFields.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/CompressionMethod+Zip.swift b/Sources/ZIP/CompressionMethod+Zip.swift index bf91ba89..c467859e 100644 --- a/Sources/ZIP/CompressionMethod+Zip.swift +++ b/Sources/ZIP/CompressionMethod+Zip.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/FileSystemType+Zip.swift b/Sources/ZIP/FileSystemType+Zip.swift index a0e93e83..3ecb572f 100644 --- a/Sources/ZIP/FileSystemType+Zip.swift +++ b/Sources/ZIP/FileSystemType+Zip.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/LittleEndianByteReader+Zip.swift b/Sources/ZIP/LittleEndianByteReader+Zip.swift index b70fb66a..e5f8b1da 100644 --- a/Sources/ZIP/LittleEndianByteReader+Zip.swift +++ b/Sources/ZIP/LittleEndianByteReader+Zip.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipCentralDirectoryEntry.swift b/Sources/ZIP/ZipCentralDirectoryEntry.swift index 0a996ed2..b6ff0bd8 100644 --- a/Sources/ZIP/ZipCentralDirectoryEntry.swift +++ b/Sources/ZIP/ZipCentralDirectoryEntry.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift index 8d555b30..97c14ffd 100644 --- a/Sources/ZIP/ZipContainer.swift +++ b/Sources/ZIP/ZipContainer.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipEndOfCentralDirectory.swift b/Sources/ZIP/ZipEndOfCentralDirectory.swift index 4a7c135b..36da217e 100644 --- a/Sources/ZIP/ZipEndOfCentralDirectory.swift +++ b/Sources/ZIP/ZipEndOfCentralDirectory.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipEntry.swift b/Sources/ZIP/ZipEntry.swift index b29cb215..05cb9953 100644 --- a/Sources/ZIP/ZipEntry.swift +++ b/Sources/ZIP/ZipEntry.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipEntryInfo.swift b/Sources/ZIP/ZipEntryInfo.swift index 7499f1b3..927db60b 100644 --- a/Sources/ZIP/ZipEntryInfo.swift +++ b/Sources/ZIP/ZipEntryInfo.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipEntryInfoHelper.swift b/Sources/ZIP/ZipEntryInfoHelper.swift index 9d61185d..4ebf00ea 100644 --- a/Sources/ZIP/ZipEntryInfoHelper.swift +++ b/Sources/ZIP/ZipEntryInfoHelper.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipError.swift b/Sources/ZIP/ZipError.swift index be19107f..b6610b2b 100644 --- a/Sources/ZIP/ZipError.swift +++ b/Sources/ZIP/ZipError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipExtraField.swift b/Sources/ZIP/ZipExtraField.swift index b9b4b5cc..ad04bc99 100644 --- a/Sources/ZIP/ZipExtraField.swift +++ b/Sources/ZIP/ZipExtraField.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/ZIP/ZipLocalHeader.swift b/Sources/ZIP/ZipLocalHeader.swift index fe93f56e..37366f7d 100644 --- a/Sources/ZIP/ZipLocalHeader.swift +++ b/Sources/ZIP/ZipLocalHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Zlib/ZlibArchive.swift b/Sources/Zlib/ZlibArchive.swift index 043629be..f4b6822e 100644 --- a/Sources/Zlib/ZlibArchive.swift +++ b/Sources/Zlib/ZlibArchive.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Zlib/ZlibError.swift b/Sources/Zlib/ZlibError.swift index 1f0f5fad..55681a30 100644 --- a/Sources/Zlib/ZlibError.swift +++ b/Sources/Zlib/ZlibError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/Zlib/ZlibHeader.swift b/Sources/Zlib/ZlibHeader.swift index 000a03f8..4de8fe35 100644 --- a/Sources/Zlib/ZlibHeader.swift +++ b/Sources/Zlib/ZlibHeader.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Archives/BZip2Command.swift b/Sources/swcomp/Archives/BZip2Command.swift index ab69d86f..dd17dbd3 100644 --- a/Sources/swcomp/Archives/BZip2Command.swift +++ b/Sources/swcomp/Archives/BZip2Command.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Archives/GZipCommand.swift b/Sources/swcomp/Archives/GZipCommand.swift index 0fbbdf03..ad8d0a25 100644 --- a/Sources/swcomp/Archives/GZipCommand.swift +++ b/Sources/swcomp/Archives/GZipCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Archives/LZ4Command.swift b/Sources/swcomp/Archives/LZ4Command.swift index 6f0c6abb..e9c1f272 100644 --- a/Sources/swcomp/Archives/LZ4Command.swift +++ b/Sources/swcomp/Archives/LZ4Command.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Archives/LZMACommand.swift b/Sources/swcomp/Archives/LZMACommand.swift index 08d7821a..22c00170 100644 --- a/Sources/swcomp/Archives/LZMACommand.swift +++ b/Sources/swcomp/Archives/LZMACommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Archives/XZCommand.swift b/Sources/swcomp/Archives/XZCommand.swift index e0cd6a15..b330cf5c 100644 --- a/Sources/swcomp/Archives/XZCommand.swift +++ b/Sources/swcomp/Archives/XZCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/Benchmark.swift b/Sources/swcomp/Benchmarks/Benchmark.swift index 6b4c2c3a..3f353e97 100644 --- a/Sources/swcomp/Benchmarks/Benchmark.swift +++ b/Sources/swcomp/Benchmarks/Benchmark.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift index 4e9707fd..2852c053 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift b/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift index c035a361..4f42f057 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/BenchmarkResult.swift b/Sources/swcomp/Benchmarks/BenchmarkResult.swift index 99d347e0..e32563dc 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkResult.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkResult.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/Benchmarks.swift b/Sources/swcomp/Benchmarks/Benchmarks.swift index 44f3fc06..448ab359 100644 --- a/Sources/swcomp/Benchmarks/Benchmarks.swift +++ b/Sources/swcomp/Benchmarks/Benchmarks.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index ae9c95f5..8b6bed42 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift index 6dd7a434..ac3b859f 100644 --- a/Sources/swcomp/Benchmarks/SaveFile.swift +++ b/Sources/swcomp/Benchmarks/SaveFile.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index bf023699..6072869b 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Benchmarks/SpeedFormatter.swift b/Sources/swcomp/Benchmarks/SpeedFormatter.swift index 8a2e9ad7..3f496601 100644 --- a/Sources/swcomp/Benchmarks/SpeedFormatter.swift +++ b/Sources/swcomp/Benchmarks/SpeedFormatter.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Containers/7ZipCommand.swift b/Sources/swcomp/Containers/7ZipCommand.swift index 3fdb8875..b4015615 100644 --- a/Sources/swcomp/Containers/7ZipCommand.swift +++ b/Sources/swcomp/Containers/7ZipCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Containers/CommonFunctions.swift b/Sources/swcomp/Containers/CommonFunctions.swift index a5b9bd8c..b41c8216 100644 --- a/Sources/swcomp/Containers/CommonFunctions.swift +++ b/Sources/swcomp/Containers/CommonFunctions.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Containers/ContainerCommand.swift b/Sources/swcomp/Containers/ContainerCommand.swift index d2a074a7..3fc09788 100644 --- a/Sources/swcomp/Containers/ContainerCommand.swift +++ b/Sources/swcomp/Containers/ContainerCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Containers/TarCommand.swift b/Sources/swcomp/Containers/TarCommand.swift index e58b8f44..b706c83c 100644 --- a/Sources/swcomp/Containers/TarCommand.swift +++ b/Sources/swcomp/Containers/TarCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Containers/ZipCommand.swift b/Sources/swcomp/Containers/ZipCommand.swift index ddf19124..7d685ca6 100644 --- a/Sources/swcomp/Containers/ZipCommand.swift +++ b/Sources/swcomp/Containers/ZipCommand.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift index d0c01536..67a49b00 100644 --- a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift index 345f27b0..891c5c3f 100644 --- a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift index 4a2cb0d0..98503229 100644 --- a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift index a4631594..25011661 100644 --- a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Extensions/TarEntry+Create.swift b/Sources/swcomp/Extensions/TarEntry+Create.swift index 664725d7..5d0effa2 100644 --- a/Sources/swcomp/Extensions/TarEntry+Create.swift +++ b/Sources/swcomp/Extensions/TarEntry+Create.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift index 436361f2..7d0231a1 100644 --- a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift +++ b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/SwcompError.swift b/Sources/swcomp/SwcompError.swift index 5a88f023..50196d20 100644 --- a/Sources/swcomp/SwcompError.swift +++ b/Sources/swcomp/SwcompError.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Sources/swcomp/main.swift b/Sources/swcomp/main.swift index 4adf98ae..11a11150 100644 --- a/Sources/swcomp/main.swift +++ b/Sources/swcomp/main.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/BZip2CompressionTests.swift b/Tests/BZip2CompressionTests.swift index 4b127a01..41a8fe4d 100644 --- a/Tests/BZip2CompressionTests.swift +++ b/Tests/BZip2CompressionTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/BZip2Tests.swift b/Tests/BZip2Tests.swift index c03fdd2c..68bed62e 100644 --- a/Tests/BZip2Tests.swift +++ b/Tests/BZip2Tests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/Constants.swift b/Tests/Constants.swift index fcd42794..e05702bb 100644 --- a/Tests/Constants.swift +++ b/Tests/Constants.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/DeflateCompressionTests.swift b/Tests/DeflateCompressionTests.swift index de9e1016..1b143d33 100644 --- a/Tests/DeflateCompressionTests.swift +++ b/Tests/DeflateCompressionTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/GzipTests.swift b/Tests/GzipTests.swift index 5dd5e40e..daa49ee7 100644 --- a/Tests/GzipTests.swift +++ b/Tests/GzipTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/LZ4CompressionTests.swift b/Tests/LZ4CompressionTests.swift index 9e381bfe..544b6c06 100644 --- a/Tests/LZ4CompressionTests.swift +++ b/Tests/LZ4CompressionTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/LZ4Tests.swift b/Tests/LZ4Tests.swift index 16ab52f2..4a591f14 100644 --- a/Tests/LZ4Tests.swift +++ b/Tests/LZ4Tests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/LzmaTests.swift b/Tests/LzmaTests.swift index c3cbaab9..2f17a8b4 100644 --- a/Tests/LzmaTests.swift +++ b/Tests/LzmaTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/SevenZipTests.swift b/Tests/SevenZipTests.swift index d9aed7c6..41fa5e89 100644 --- a/Tests/SevenZipTests.swift +++ b/Tests/SevenZipTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/Sha256Tests.swift b/Tests/Sha256Tests.swift index cb78202b..83468bc8 100644 --- a/Tests/Sha256Tests.swift +++ b/Tests/Sha256Tests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/TarCreateTests.swift b/Tests/TarCreateTests.swift index ae0c4cd0..76e55514 100644 --- a/Tests/TarCreateTests.swift +++ b/Tests/TarCreateTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/TarReaderTests.swift b/Tests/TarReaderTests.swift index be422acd..4ad4a866 100644 --- a/Tests/TarReaderTests.swift +++ b/Tests/TarReaderTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/TarTests.swift b/Tests/TarTests.swift index b31aedd5..015397f1 100644 --- a/Tests/TarTests.swift +++ b/Tests/TarTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/TarWriterTests.swift b/Tests/TarWriterTests.swift index a31f0626..261ff50d 100644 --- a/Tests/TarWriterTests.swift +++ b/Tests/TarWriterTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/TestZipExtraField.swift b/Tests/TestZipExtraField.swift index 45676511..084a1501 100644 --- a/Tests/TestZipExtraField.swift +++ b/Tests/TestZipExtraField.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/XxHash32Tests.swift b/Tests/XxHash32Tests.swift index d773af4e..87e15276 100644 --- a/Tests/XxHash32Tests.swift +++ b/Tests/XxHash32Tests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/XzTests.swift b/Tests/XzTests.swift index d243bdf9..708bf111 100644 --- a/Tests/XzTests.swift +++ b/Tests/XzTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/ZipTests.swift b/Tests/ZipTests.swift index 5aea5218..d2bfa497 100644 --- a/Tests/ZipTests.swift +++ b/Tests/ZipTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information diff --git a/Tests/ZlibTests.swift b/Tests/ZlibTests.swift index d6aa7c55..2f9d81d1 100644 --- a/Tests/ZlibTests.swift +++ b/Tests/ZlibTests.swift @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information From ec180c3d063480a879cf148a59777887ea842dba Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:01:03 +0800 Subject: [PATCH 017/106] Add a note to README about CocoaPods deprecation --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 93207103..ce28cf45 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ Also, SWCompression is _written with Swift only._ SWCompression can be integrated into your project using Swift Package Manager, CocoaPods, or Carthage. +__Note:__ Due to [upcoming](https://blog.cocoapods.org/CocoaPods-Specs-Repo/) deprecation of CocoaPods no new releases +of SWCompression will be published using CocoaPods. The last release of SWCompression made available via CocoaPods was +4.8.7. Consider switching to Swift Package Manager instead. + ### Swift Package Manager To install using SPM, add SWCompression to you package dependencies and specify it as a dependency for your target, e.g.: From 0a4edd58966b743d1f37dbcfc680962090d12288 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:04:47 +0800 Subject: [PATCH 018/106] Update xcode project --- SWCompression.xcodeproj/project.pbxproj | 4 +++- .../xcshareddata/xcschemes/SWCompression.xcscheme | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 03dc30da..05c3c142 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1162,7 +1162,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1620; + LastUpgradeCheck = 2620; ORGANIZATIONNAME = "Timofey Solomko"; TargetAttributes = { 06BE1AC71DB410F100EE0F59 = { @@ -1580,6 +1580,7 @@ BitByteData, ); SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -1647,6 +1648,7 @@ BitByteData, ); SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_VERSION = 5.0; diff --git a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme index f186312b..1b98aff8 100644 --- a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme +++ b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme @@ -1,6 +1,6 @@ Date: Sat, 31 Jan 2026 20:06:42 +0800 Subject: [PATCH 019/106] Drop support for Swift 5.7 and 5.8 --- Package.swift | 5 ++--- README.md | 2 +- SWCompression.xcodeproj/project.pbxproj | 4 ++-- azure-pipelines.yml | 28 ------------------------- 4 files changed, 5 insertions(+), 34 deletions(-) diff --git a/Package.swift b/Package.swift index 6fba78b0..0bf40d03 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 import PackageDescription var package = Package( @@ -8,8 +8,7 @@ var package = Package( .iOS(.v14), .tvOS(.v14), .watchOS(.v7), - // TODO: Enable after upgrading to Swift 5.9. - // .visionOS(.v1) + .visionOS(.v1) ], products: [ .library( diff --git a/README.md b/README.md index ce28cf45..10fc6be2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SWCompression -[![Swift 5.7+](https://img.shields.io/badge/Swift-5.7+-blue.svg)](https://developer.apple.com/swift/) +[![Swift 5.9+](https://img.shields.io/badge/Swift-5.9+-blue.svg)](https://developer.apple.com/swift/) [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/tsolomko/SWCompression/master/LICENSE) [![Build Status](https://dev.azure.com/tsolomko/SWCompression/_apis/build/status/tsolomko.SWCompression?branchName=develop)](https://dev.azure.com/tsolomko/SWCompression/_build/latest?definitionId=3&branchName=develop) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 05c3c142..e881802f 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -1176,7 +1176,7 @@ }; }; buildConfigurationList = 06BE1AC21DB410F100EE0F59 /* Build configuration list for PBXProject "SWCompression" */; - compatibilityVersion = "Xcode 14.0"; + compatibilityVersion = "Xcode 15.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a075d1f1..cb2369b0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,16 +15,6 @@ stages: - job: macos strategy: matrix: - macosSwift57: - imageName: 'macOS-13' - DEVELOPER_DIR: '/Applications/Xcode_14.2.app' - IOS_SIMULATOR: 'iPhone 14' - WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' - macosSwift58: - imageName: 'macOS-13' - DEVELOPER_DIR: '/Applications/Xcode_14.3.1.app' - IOS_SIMULATOR: 'iPhone 14' - WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' macosSwift59: imageName: 'macOS-14' DEVELOPER_DIR: '/Applications/Xcode_15.2.app' @@ -75,12 +65,6 @@ stages: - job: linux strategy: matrix: - linuxSwift57: - imageName: 'ubuntu-22.04' - containerImage: 'swift:5.7.3-jammy' - linuxSwift58: - imageName: 'ubuntu-22.04' - containerImage: 'swift:5.8.1-jammy' linuxSwift59: imageName: 'ubuntu-22.04' containerImage: 'swift:5.9.2-jammy' @@ -103,18 +87,6 @@ stages: - job: windows strategy: matrix: - windowsSwift57: - imageName: 'windows-2019' - SWIFT_VERSION: '5.7.3' - ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' - SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' - XCTEST_LIB_PATH: '\x86_64' - windowsSwift58: - imageName: 'windows-2019' - SWIFT_VERSION: '5.8.1' - ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' - SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' - XCTEST_LIB_PATH: '\x86_64' windowsSwift59: imageName: 'windows-2019' SWIFT_VERSION: '5.9.2' From 33e5716c3bf660fe22fb5afe66df782937c2269a Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:09:19 +0800 Subject: [PATCH 020/106] Increase minimum deployment targets on Apple platforms --- Package.swift | 8 ++++---- SWCompression.podspec | 10 +++++----- SWCompression.xcodeproj/project.pbxproj | 20 ++++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Package.swift b/Package.swift index 0bf40d03..27e2c996 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription var package = Package( name: "SWCompression", platforms: [ - .macOS(.v11), - .iOS(.v14), - .tvOS(.v14), - .watchOS(.v7), + .macOS(.v14), + .iOS(.v17), + .tvOS(.v17), + .watchOS(.v10), .visionOS(.v1) ], products: [ diff --git a/SWCompression.podspec b/SWCompression.podspec index 2094cd8f..e6216032 100644 --- a/SWCompression.podspec +++ b/SWCompression.podspec @@ -15,11 +15,11 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/tsolomko/SWCompression.git", :tag => "#{s.version}" } - s.ios.deployment_target = "14.0" - s.osx.deployment_target = "11.0" - s.tvos.deployment_target = "14.0" - s.watchos.deployment_target = "7.0" - # s.visionos.deployment_target = "1.0" + s.ios.deployment_target = "17.0" + s.osx.deployment_target = "14.0" + s.tvos.deployment_target = "17.0" + s.watchos.deployment_target = "10.0" + s.visionos.deployment_target = "1.0" s.swift_versions = ["5"] diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index e881802f..50117dfe 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1571,8 +1571,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MACOSX_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; + MACOSX_DEPLOYMENT_TARGET = 14.6; ONLY_ACTIVE_ARCH = YES; OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_LDFLAGS = ( @@ -1584,10 +1584,10 @@ SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 14.0; + TVOS_DEPLOYMENT_TARGET = 17.6; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 7.0; - XROS_DEPLOYMENT_TARGET = 1.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Debug; }; @@ -1640,8 +1640,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MACOSX_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; + MACOSX_DEPLOYMENT_TARGET = 14.6; OTHER_CODE_SIGN_FLAGS = "--deep"; OTHER_LDFLAGS = ( "-framework", @@ -1652,10 +1652,10 @@ SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 14.0; + TVOS_DEPLOYMENT_TARGET = 17.6; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 7.0; - XROS_DEPLOYMENT_TARGET = 1.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Release; }; From 9c22e4353e2370e5686e28ea7d6771c905752a08 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:10:36 +0800 Subject: [PATCH 021/106] [CI] Add builds on Swift 6.1 and 6.2 --- azure-pipelines.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cb2369b0..42ce83ed 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,6 +30,16 @@ stages: DEVELOPER_DIR: '/Applications/Xcode_16.2.app' IOS_SIMULATOR: 'iPhone 16' WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' + macosSwift61: + imageName: 'macOS-15' + DEVELOPER_DIR: '/Applications/Xcode_16.4.app' + IOS_SIMULATOR: 'iPhone 16' + WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' + macosSwift62: + imageName: 'macOS-15' + DEVELOPER_DIR: '/Applications/Xcode_26.2.app' + IOS_SIMULATOR: 'iPhone 16' + WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' pool: vmImage: $(imageName) variables: @@ -74,6 +84,12 @@ stages: linuxSwift60: imageName: 'ubuntu-24.04' containerImage: 'swift:6.0.3-noble' + linuxSwift61: + imageName: 'ubuntu-24.04' + containerImage: 'swift:6.1.3-noble' + linuxSwift62: + imageName: 'ubuntu-24.04' + containerImage: 'swift:6.2.3-noble' pool: vmImage: $(imageName) container: $[ variables['containerImage'] ] @@ -123,6 +139,12 @@ stages: windowsSwift60: imageName: 'windows-2022' containerImage: 'swift:6.0.3-windowsservercore-ltsc2022' + windowsSwift61: + imageName: 'windows-2022' + containerImage: 'swift:6.1.3-windowsservercore-ltsc2022' + windowsSwift62: + imageName: 'windows-2022' + containerImage: 'swift:6.2.3-windowsservercore-ltsc2022' pool: vmImage: $(imageName) container: $[ variables['containerImage'] ] From f498c08da991e2f36aa92ea23323a85e1b37a0ad Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:11:54 +0800 Subject: [PATCH 022/106] [CI] Change Swift 5.9 build on Windows to use Docker image --- azure-pipelines.yml | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 42ce83ed..efac028e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -104,35 +104,8 @@ stages: strategy: matrix: windowsSwift59: - imageName: 'windows-2019' - SWIFT_VERSION: '5.9.2' - ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' - SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' - XCTEST_LIB_PATH: '\x86_64' - pool: - vmImage: $(imageName) - variables: - DEVELOPER_DIR: 'C:\Library\Developer' - SDKROOT: 'C:\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk' - SWIFTFLAGS: '--sdk $(SDKROOT) -Xswiftc -sdk -Xswiftc $(SDKROOT) -Xswiftc -resource-dir -Xswiftc $(SDKROOT)/usr/lib/swift -Xswiftc -I -Xswiftc $(SDKROOT)/usr/lib/swift -Xswiftc -L -Xswiftc $(SDKROOT)/usr/lib/swift/windows' - steps: - - pwsh: Invoke-WebRequest -Uri https://swift.org/builds/swift-$(SWIFT_VERSION)-release/windows10/swift-$(SWIFT_VERSION)-RELEASE/swift-$(SWIFT_VERSION)-RELEASE-windows10.exe -OutFile swift-install.exe - displayName: 'Download Swift' - - task: BatchScript@1 - inputs: - filename: .\swift-install.exe - arguments: /install /quiet - modifyEnvironment: true - displayName: 'Install Swift' - - script: | - set PATH=C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin;%SWIFT_DEV_PATH%;%ICU_PATH%;%LocalAppData%\Programs\Swift;%PATH% - swift.exe --version - swift.exe build %SWIFTFLAGS% - swift.exe build -c release %SWIFTFLAGS% - displayName: 'Build SPM Debug & Release' - - job: windowsDocker - strategy: - matrix: + imageName: 'windows-2022' + containerImage: 'swift:5.9.2-windowsservercore-ltsc2022' windowsSwift510: imageName: 'windows-2022' containerImage: 'swift:5.10.1-windowsservercore-ltsc2022' From 38ac3e63758f99b0baf5887dc352e70a48d1f77c Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:12:17 +0800 Subject: [PATCH 023/106] [CI] Fix macOS version for Swift 5.10 build --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index efac028e..81d59d72 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,7 +21,7 @@ stages: IOS_SIMULATOR: 'iPhone 15' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' macosSwift510: - imageName: 'macOS-15' + imageName: 'macOS-14' DEVELOPER_DIR: '/Applications/Xcode_15.4.app' IOS_SIMULATOR: 'iPhone 15' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' From 44476e46b40c128bb42e1681455ebffaf28579c6 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:20:30 +0800 Subject: [PATCH 024/106] [CI] Disable Swift 5.10 build on Windows Swift 5.10 Windows Docker container causes ridiculous errors like "unknown or missing subcommand 'swift-build.exe'". --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 81d59d72..837a90bc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -106,9 +106,9 @@ stages: windowsSwift59: imageName: 'windows-2022' containerImage: 'swift:5.9.2-windowsservercore-ltsc2022' - windowsSwift510: - imageName: 'windows-2022' - containerImage: 'swift:5.10.1-windowsservercore-ltsc2022' + # windowsSwift510: + # imageName: 'windows-2022' + # containerImage: 'swift:5.10.1-windowsservercore-ltsc2022' windowsSwift60: imageName: 'windows-2022' containerImage: 'swift:6.0.3-windowsservercore-ltsc2022' From 713ae2c630eddbe2d562f72436f8df4c99b69012 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:34:57 +0800 Subject: [PATCH 025/106] Remove mention of generate-xcodeproj command in README This command was deprecated by SPM a long time ago. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 10fc6be2..dc28b024 100644 --- a/README.md +++ b/README.md @@ -175,9 +175,6 @@ In the case of a bug, it will be especially helpful if you attach a file (archiv If you'd like to contribute, please [create a pull request](https://github.com/tsolomko/SWCompression/pulls) on GitHub. -__Note:__ If you are considering working on SWCompression, please note that Xcode project (SWCompression.xcodeproj) -was created manually and you shouldn't use `swift package generate-xcodeproj` command. - ### Executing tests locally If you want to run tests on your computer, you need to do a couple of additional steps after cloning the repository: From 718bdfa493e01b5078558fe32a6e9d4138bd2254 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 20:35:40 +0800 Subject: [PATCH 026/106] utils.py: add an option to build BBD only for macOS This option is mutually exclusive with xros. --- utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/utils.py b/utils.py index 70b0fb0f..83264d04 100755 --- a/utils.py +++ b/utils.py @@ -57,7 +57,9 @@ def action_dbm(args): script = ["carthage", "bootstrap", "--no-use-binaries", "--use-xcframeworks"] if args.debug: script += ["--configuration", "Debug"] - if args.xros: + if args.macos_only: + script += ["--platform", "macOS"] + elif args.xros: script += ["--platform", "macOS,iOS,watchOS,tvOS,visionOS"] else: script += ["--platform", "macOS,iOS,watchOS,tvOS"] @@ -119,8 +121,11 @@ parser_dbm = subparsers.add_parser("download-bbd-macos", help="download BitByteD description="downloads BitByteData dependency using Carthage (macOS only)") parser_dbm.add_argument("--debug", "-d", action="store_true", dest="debug", help="build BitByteData in Debug configuration") -parser_dbm.add_argument("--xros", action="store_true", dest="xros", - help="build BitByteData for visionOS as well (requires Apple Silicon)") +platform_group = parser_dbm.add_mutually_exclusive_group() +platform_group.add_argument("--macos-only", action="store_true", dest="macos_only", + help="build BitByteData for macOS only") +platform_group.add_argument("--xros", action="store_true", dest="xros", + help="build BitByteData for visionOS as well (requires Apple Silicon)") parser_dbm.set_defaults(func=action_dbm) # Parser for 'prepare-release' command. From cd116a92ddd53a8239c12ec2a7b103e946b0b5ef Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 31 Jan 2026 21:48:04 +0800 Subject: [PATCH 027/106] [CI] Add a step for installing Darwin platforms This needs to be done before downloading BBD. AZP now has an extremely limited set of preinstalled simulators. --- azure-pipelines.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 837a90bc..c85ac8f3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -66,6 +66,13 @@ stages: git lfs pull git lfs checkout displayName: 'Download or update test files' + - script: | + set -e -o xtrace + xcrun simctl list > /dev/null + xcodebuild -downloadPlatform iOS + xcodebuild -downloadPlatform watchOS + xcodebuild -downloadPlatform tvOS + displayName: 'Install Darwin platforms' - script: ./utils.py download-bbd-macos displayName: 'Download BitByteData' - script: ./utils.py ci script-macos From eb83e098af5ec040b3d037fa10fb1e966e9701eb Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Mon, 2 Feb 2026 17:17:45 +0800 Subject: [PATCH 028/106] Fix framework search paths in Xcode project This should fix installation with Carthage on the currently supported versions of Xcode. --- SWCompression.xcodeproj/project.pbxproj | 36 +++++++++++-------------- azure-pipelines.yml | 1 + 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 50117dfe..12d16981 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1369,7 +1369,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "if [[ ${SDK_NAME} == iphone* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == iphonesimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-arm64*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == appletv* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == appletvsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-arm64/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == watch* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == watchsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-arm64_32_armv7k/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == macos* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\n fi\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/macos-*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\nfi\n\nxattr -rc ${BUILT_PRODUCTS_DIR}\n"; + shellScript = "if [[ ${SDK_NAME} == iphone* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == iphonesimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-arm64*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == appletv* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == appletvsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-arm64/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == watch* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == watchsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-*_armv7k/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == macos* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\n fi\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/macos-*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\nfi\n\nxattr -rc ${BUILT_PRODUCTS_DIR}\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1552,17 +1552,14 @@ ENABLE_TESTABILITY = YES; "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64\""; "FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64_x86_64-simulator\""; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_armv7\"", - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\"", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_i386_x86_64-simulator\"", - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\"", - ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\""; + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\""; "FRAMEWORK_SEARCH_PATHS[sdk=macosx*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/macos-arm64_x86_64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_32_armv7k\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\""; + "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_arm64_32_armv7k\""; + "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = ( + "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\"", + "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_x86_64-simulator\"", + ); GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1622,17 +1619,14 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64\""; "FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64_x86_64-simulator\""; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_armv7\"", - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\"", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_i386_x86_64-simulator\"", - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\"", - ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\""; + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\""; "FRAMEWORK_SEARCH_PATHS[sdk=macosx*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/macos-arm64_x86_64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_32_armv7k\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\""; + "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_arm64_32_armv7k \""; + "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = ( + "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\"", + "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_x86_64-simulator\"", + ); GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c85ac8f3..e0556730 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,6 +30,7 @@ stages: DEVELOPER_DIR: '/Applications/Xcode_16.2.app' IOS_SIMULATOR: 'iPhone 16' WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' + # TODO: When Swift 6.0 support is dropped we can remove i386 search path for the watchOS simulator in Xcode. macosSwift61: imageName: 'macOS-15' DEVELOPER_DIR: '/Applications/Xcode_16.4.app' From f1b635056d8efe45fecee6d2fa5df3dc5f94a10a Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:07:34 +0800 Subject: [PATCH 029/106] Add BitByteData as a package dependency in Xcode project --- SWCompression.xcodeproj/project.pbxproj | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 12d16981..a326164d 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -275,6 +275,7 @@ E6C057D627498F5A007C83DF /* TarWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C057D527498F5A007C83DF /* TarWriter.swift */; }; E6C4150726FE230A00F9D36F /* XxHash32.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C4150626FE230A00F9D36F /* XxHash32.swift */; }; E6D86D2F26FE35C50032CFFA /* XxHash32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6D86D2E26FE35C50032CFFA /* XxHash32Tests.swift */; }; + E6DCC9D82F30DA080042AE2E /* BitByteData in Frameworks */ = {isa = PBXBuildFile; productRef = E6DCC9D72F30DA080042AE2E /* BitByteData */; }; E6DF9ECE2704BF14003BD91E /* test_multi_frame.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E6DF9ECD2704BF14003BD91E /* test_multi_frame.lz4 */; }; E6E49D522BD5081D00BFC756 /* PrivacyInfo.xcprivacy in CopyFiles */ = {isa = PBXBuildFile; fileRef = E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */; }; E6EDD6A826F7767F00884532 /* test_minor_version_3.7z in Resources */ = {isa = PBXBuildFile; fileRef = E6EDD6A626F7767E00884532 /* test_minor_version_3.7z */; }; @@ -592,6 +593,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E6DCC9D52F30DA020042AE2E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E6DCC9D82F30DA080042AE2E /* BitByteData in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -1125,10 +1134,12 @@ buildPhases = ( 06BE1AC31DB410F100EE0F59 /* Sources */, E6E49D512BD5081700BFC756 /* CopyFiles */, + E6DCC9D52F30DA020042AE2E /* Frameworks */, ); buildRules = ( ); dependencies = ( + E6DCC9DA2F30DB4E0042AE2E /* PBXTargetDependency */, ); name = SWCompression; productName = SWCompression; @@ -1184,6 +1195,9 @@ Base, ); mainGroup = 06BE1ABE1DB410F100EE0F59; + packageReferences = ( + E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */, + ); productRefGroup = 06BE1AC91DB410F100EE0F59 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1517,6 +1531,10 @@ target = 06BE1AC71DB410F100EE0F59 /* SWCompression */; targetProxy = 06F0659B1FFB761600312A82 /* PBXContainerItemProxy */; }; + E6DCC9DA2F30DB4E0042AE2E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = E6DCC9D92F30DB4E0042AE2E /* BitByteData */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -1778,6 +1796,30 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tsolomko/BitByteData"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.4; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + E6DCC9D72F30DA080042AE2E /* BitByteData */ = { + isa = XCSwiftPackageProductDependency; + package = E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */; + productName = BitByteData; + }; + E6DCC9D92F30DB4E0042AE2E /* BitByteData */ = { + isa = XCSwiftPackageProductDependency; + package = E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */; + productName = BitByteData; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 06BE1ABF1DB410F100EE0F59 /* Project object */; } From 29697ca7ceb4abfed9526224d344297da75498f3 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:08:25 +0800 Subject: [PATCH 030/106] Remove from Xcode project settings for embedding BBD built with Carthage --- SWCompression.xcodeproj/project.pbxproj | 88 +------------------------ 1 file changed, 2 insertions(+), 86 deletions(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index a326164d..cccc0158 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1153,7 +1153,6 @@ 06F065911FFB761600312A82 /* Sources */, 06F065921FFB761600312A82 /* Frameworks */, 06F065931FFB761600312A82 /* Resources */, - 062BE32F1FFBADB300343CAD /* Copy BitByteData.framework into test bundle */, ); buildRules = ( ); @@ -1369,25 +1368,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 062BE32F1FFBADB300343CAD /* Copy BitByteData.framework into test bundle */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy BitByteData.framework into test bundle"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/bash; - shellScript = "if [[ ${SDK_NAME} == iphone* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == iphonesimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-arm64*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == appletv* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == appletvsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-arm64/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == watch* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == watchsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-*_armv7k/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == macos* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\n fi\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/macos-*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\nfi\n\nxattr -rc ${BUILT_PRODUCTS_DIR}\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 06BE1AC31DB410F100EE0F59 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1568,16 +1548,7 @@ EAGER_LINKING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64_x86_64-simulator\""; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\""; - "FRAMEWORK_SEARCH_PATHS[sdk=macosx*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/macos-arm64_x86_64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_arm64_32_armv7k\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = ( - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\"", - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_x86_64-simulator\"", - ); + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1589,11 +1560,6 @@ IPHONEOS_DEPLOYMENT_TARGET = 17.6; MACOSX_DEPLOYMENT_TARGET = 14.6; ONLY_ACTIVE_ARCH = YES; - OTHER_CODE_SIGN_FLAGS = "--deep"; - OTHER_LDFLAGS = ( - "-framework", - BitByteData, - ); SDKROOT = macosx; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; @@ -1635,16 +1601,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; EAGER_LINKING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; - "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64_x86_64-simulator\""; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\""; - "FRAMEWORK_SEARCH_PATHS[sdk=macosx*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/macos-arm64_x86_64\""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_arm64_32_armv7k \""; - "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = ( - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\"", - "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_x86_64-simulator\"", - ); + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; @@ -1654,11 +1611,6 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; MACOSX_DEPLOYMENT_TARGET = 14.6; - OTHER_CODE_SIGN_FLAGS = "--deep"; - OTHER_LDFLAGS = ( - "-framework", - BitByteData, - ); SDKROOT = macosx; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros"; @@ -1682,19 +1634,8 @@ ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@loader_path/Frameworks", - "@executable_path/Frameworks", - "@loader_path/../Frameworks", - "@executable_path/../Frameworks", - ); MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14"; - OTHER_LDFLAGS = ( - "-framework", - BitByteData, - ); PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.SWCompression; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1712,19 +1653,8 @@ ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@loader_path/Frameworks", - "@executable_path/Frameworks", - "@loader_path/../Frameworks", - "@executable_path/../Frameworks", - ); MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14"; - OTHER_LDFLAGS = ( - "-framework", - BitByteData, - ); PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.SWCompression; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1736,13 +1666,6 @@ buildSettings = { COPY_PHASE_STRIP = NO; INFOPLIST_FILE = SWCompression.xcodeproj/TestSWCompression.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@loader_path/Frameworks", - "@executable_path/Frameworks", - "@loader_path/../Frameworks", - "@executable_path/../Frameworks", - ); PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.TestSWCompression; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -1753,13 +1676,6 @@ buildSettings = { COPY_PHASE_STRIP = NO; INFOPLIST_FILE = SWCompression.xcodeproj/TestSWCompression.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@loader_path/Frameworks", - "@executable_path/Frameworks", - "@loader_path/../Frameworks", - "@executable_path/../Frameworks", - ); PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.TestSWCompression; PRODUCT_NAME = "$(TARGET_NAME)"; }; From 5b3840f6132629863bcd012f8e01018867580d4c Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:09:16 +0800 Subject: [PATCH 031/106] utils.py: remove download-bbd-macos command --- azure-pipelines.yml | 2 -- utils.py | 25 ------------------------- 2 files changed, 27 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e0556730..2d42e3d0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -74,8 +74,6 @@ stages: xcodebuild -downloadPlatform watchOS xcodebuild -downloadPlatform tvOS displayName: 'Install Darwin platforms' - - script: ./utils.py download-bbd-macos - displayName: 'Download BitByteData' - script: ./utils.py ci script-macos displayName: 'Build & Test' - script: swift build -c release diff --git a/utils.py b/utils.py index 83264d04..4201234d 100755 --- a/utils.py +++ b/utils.py @@ -52,19 +52,6 @@ def action_cw(args): _sprun(["rm", "-f", "Package.resolved"]) _sprun(["rm", "-f", "SWCompression.framework.zip"]) -def action_dbm(args): - print("=> Downloading BitByteData dependency using Carthage") - script = ["carthage", "bootstrap", "--no-use-binaries", "--use-xcframeworks"] - if args.debug: - script += ["--configuration", "Debug"] - if args.macos_only: - script += ["--platform", "macOS"] - elif args.xros: - script += ["--platform", "macOS,iOS,watchOS,tvOS,visionOS"] - else: - script += ["--platform", "macOS,iOS,watchOS,tvOS"] - _sprun(script) - def action_pr(args): _sprun(["agvtool", "next-version", "-all"]) _sprun(["agvtool", "new-marketing-version", args.version]) @@ -116,18 +103,6 @@ parser_cw = subparsers.add_parser("cleanup-workspace", help="cleanup workspace", description="cleans workspace from files produced by various build systems") parser_cw.set_defaults(func=action_cw) -# Parser for 'download-bbd-macos' command. -parser_dbm = subparsers.add_parser("download-bbd-macos", help="download BitByteData", - description="downloads BitByteData dependency using Carthage (macOS only)") -parser_dbm.add_argument("--debug", "-d", action="store_true", dest="debug", - help="build BitByteData in Debug configuration") -platform_group = parser_dbm.add_mutually_exclusive_group() -platform_group.add_argument("--macos-only", action="store_true", dest="macos_only", - help="build BitByteData for macOS only") -platform_group.add_argument("--xros", action="store_true", dest="xros", - help="build BitByteData for visionOS as well (requires Apple Silicon)") -parser_dbm.set_defaults(func=action_dbm) - # Parser for 'prepare-release' command. parser_pr = subparsers.add_parser("prepare-release", help="prepare next release", description="prepare next release of SWCompression") From 8a90134aa59ae89a4dbedb428e7c2df0faa96361 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:09:59 +0800 Subject: [PATCH 032/106] Remove Cartfile --- Cartfile | 1 - utils.py | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 Cartfile diff --git a/Cartfile b/Cartfile deleted file mode 100644 index 656bf838..00000000 --- a/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "tsolomko/BitByteData" ~> 2.0.0 diff --git a/utils.py b/utils.py index 4201234d..eb5b11c1 100755 --- a/utils.py +++ b/utils.py @@ -43,11 +43,9 @@ def action_ci(args): def action_cw(args): _sprun(["rm", "-rf", "build/"]) - _sprun(["rm", "-rf", "Carthage/"]) _sprun(["rm", "-rf", "docs/"]) _sprun(["rm", "-rf", "Pods/"]) _sprun(["rm", "-rf", ".build/"]) - _sprun(["rm", "-f", "Cartfile.resolved"]) _sprun(["rm", "-f", "docs.json"]) _sprun(["rm", "-f", "Package.resolved"]) _sprun(["rm", "-f", "SWCompression.framework.zip"]) From ad4a9574f79437deadb28c84bb9379d67053add1 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:10:15 +0800 Subject: [PATCH 033/106] Remove podspec --- SWCompression.podspec | 86 ------------------------------------------- utils.py | 11 ------ 2 files changed, 97 deletions(-) delete mode 100644 SWCompression.podspec diff --git a/SWCompression.podspec b/SWCompression.podspec deleted file mode 100644 index e6216032..00000000 --- a/SWCompression.podspec +++ /dev/null @@ -1,86 +0,0 @@ -Pod::Spec.new do |s| - - s.name = "SWCompression" - s.version = "4.8.6" - s.summary = "A framework with functions for working with compression, archives and containers." - - s.description = "A framework with (de)compression algorithms and functions for processing various archives and containers." - - s.homepage = "https://github.com/tsolomko/SWCompression" - s.documentation_url = "http://tsolomko.github.io/SWCompression" - - s.license = { :type => "MIT", :file => "LICENSE" } - - s.author = { "Timofey Solomko" => "tsolomko@gmail.com" } - - s.source = { :git => "https://github.com/tsolomko/SWCompression.git", :tag => "#{s.version}" } - - s.ios.deployment_target = "17.0" - s.osx.deployment_target = "14.0" - s.tvos.deployment_target = "17.0" - s.watchos.deployment_target = "10.0" - s.visionos.deployment_target = "1.0" - - s.swift_versions = ["5"] - - s.dependency "BitByteData", "~> 2.0" - - s.resource_bundles = {"SWCompression" => ["Sources/PrivacyInfo.xcprivacy"]} - - s.subspec "Deflate" do |sp| - sp.source_files = "Sources/{Deflate/*,Common/*,Common/CodingTree/*}.swift" - sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_DEFLATE" } - end - - s.subspec "GZip" do |sp| - sp.dependency "SWCompression/Deflate" - sp.source_files = "Sources/{GZip/*,Common/*}.swift" - end - - s.subspec "Zlib" do |sp| - sp.dependency "SWCompression/Deflate" - sp.source_files = "Sources/{Zlib/*,Common/*}.swift" - end - - s.subspec "BZip2" do |sp| - sp.source_files = "Sources/{BZip2/*,Common/*,Common/CodingTree/*}.swift" - sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_BZ2" } - end - - s.subspec "LZMA" do |sp| - sp.source_files = "Sources/{LZMA/*,Common/*}.swift" - sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_LZMA" } - end - - s.subspec "LZMA2" do |sp| - sp.dependency "SWCompression/LZMA" - sp.source_files = "Sources/{LZMA2/*,Common/*}.swift" - end - - s.subspec "LZ4" do |sp| - sp.source_files = "Sources/{LZ4/*,Common/*}.swift" - sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_LZ4" } - end - - s.subspec "XZ" do |sp| - sp.dependency "SWCompression/LZMA2" - sp.source_files = "Sources/{XZ/*,Common/*}.swift" - end - - s.subspec "ZIP" do |sp| - sp.dependency "SWCompression/Deflate" - sp.source_files = "Sources/{Zip/*,Common/*,Common/Container/*}.swift" - sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_ZIP" } - end - - s.subspec "TAR" do |sp| - sp.source_files = "Sources/{TAR/*,Common/*,Common/Container/*}.swift" - end - - s.subspec "SevenZip" do |sp| - sp.dependency "SWCompression/LZMA2" - sp.source_files = "Sources/{7-Zip/*,Common/*,Common/Container/*}.swift" - sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_SEVENZIP" } - end - -end diff --git a/utils.py b/utils.py index eb5b11c1..392a8fb9 100755 --- a/utils.py +++ b/utils.py @@ -44,7 +44,6 @@ def action_ci(args): def action_cw(args): _sprun(["rm", "-rf", "build/"]) _sprun(["rm", "-rf", "docs/"]) - _sprun(["rm", "-rf", "Pods/"]) _sprun(["rm", "-rf", ".build/"]) _sprun(["rm", "-f", "docs.json"]) _sprun(["rm", "-f", "Package.resolved"]) @@ -54,16 +53,6 @@ def action_pr(args): _sprun(["agvtool", "next-version", "-all"]) _sprun(["agvtool", "new-marketing-version", args.version]) - f = open("SWCompression.podspec", "r", encoding="utf-8") - lines = f.readlines() - f.close() - f = open("SWCompression.podspec", "w", encoding="utf-8") - for line in lines: - if line.startswith(" s.version = "): - line = " s.version = \"" + args.version + "\"\n" - f.write(line) - f.close() - f = open(".jazzy.yaml", "r", encoding="utf-8") lines = f.readlines() f.close() From 1566b49ce3a5937ea3e16387257f56c2366f1e0a Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:15:33 +0800 Subject: [PATCH 034/106] [7-Zip][ZIP] Remove support for CocoaPods optional dependencies --- Sources/7-Zip/7zFolder.swift | 18 +++--------------- Sources/ZIP/ZipContainer.swift | 26 +++++++++----------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/Sources/7-Zip/7zFolder.swift b/Sources/7-Zip/7zFolder.swift index d917a12e..edcfdd7c 100644 --- a/Sources/7-Zip/7zFolder.swift +++ b/Sources/7-Zip/7zFolder.swift @@ -147,17 +147,9 @@ class SevenZipFolder { case .copy: continue case .deflate: - #if (!SWCOMPRESSION_POD_SEVENZIP) || (SWCOMPRESSION_POD_SEVENZIP && SWCOMPRESSION_POD_DEFLATE) - decodedData = try Deflate.decompress(data: decodedData) - #else - throw SevenZipError.compressionNotSupported - #endif + decodedData = try Deflate.decompress(data: decodedData) case .bzip2: - #if (!SWCOMPRESSION_POD_SEVENZIP) || (SWCOMPRESSION_POD_SEVENZIP && SWCOMPRESSION_POD_BZ2) - decodedData = try BZip2.decompress(data: decodedData) - #else - throw SevenZipError.compressionNotSupported - #endif + decodedData = try BZip2.decompress(data: decodedData) case .lzma2: // Dictionary size is stored in coder's properties. guard let properties = coder.properties, @@ -187,11 +179,7 @@ class SevenZipFolder { decodedData = DeltaFilter.decode(LittleEndianByteReader(data: decodedData), (properties[0] &+ 1).toInt()) } else if coder.id == [0x04, 0xF7, 0x11, 0x04] { - #if (!SWCOMPRESSION_POD_SEVENZIP) || (SWCOMPRESSION_POD_SEVENZIP && SWCOMPRESSION_POD_LZ4) - decodedData = try LZ4.decompress(data: decodedData) - #else - throw SevenZipError.compressionNotSupported - #endif + decodedData = try LZ4.decompress(data: decodedData) } else if coder.isEncryptionMethod { throw SevenZipError.encryptionNotSupported } else { diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift index 97c14ffd..92300590 100644 --- a/Sources/ZIP/ZipContainer.swift +++ b/Sources/ZIP/ZipContainer.swift @@ -75,24 +75,16 @@ public class ZipContainer: Container { bitReader.align() byteReader.offset = bitReader.offset case .bzip2: - #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_BZ2) - // BZip2 algorithm uses different bit numbering scheme. - let bitReader = MsbBitReader(byteReader) - fileData = try BZip2.decompress(bitReader) - // Sometimes bitReader is misaligned after BZip2 decompression, so we need to align before getting end - // index back. - bitReader.align() - byteReader.offset = bitReader.offset - #else - throw ZipError.compressionNotSupported - #endif + // BZip2 algorithm uses different bit numbering scheme. + let bitReader = MsbBitReader(byteReader) + fileData = try BZip2.decompress(bitReader) + // Sometimes bitReader is misaligned after BZip2 decompression, so we need to align before getting end + // index back. + bitReader.align() + byteReader.offset = bitReader.offset case .lzma: - #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_LZMA) - byteReader.offset += 4 // Skipping LZMA SDK version and size of properties. - fileData = try LZMA.decompress(byteReader, LZMAProperties(byteReader), uncompSize.toInt()) - #else - throw ZipError.compressionNotSupported - #endif + byteReader.offset += 4 // Skipping LZMA SDK version and size of properties. + fileData = try LZMA.decompress(byteReader, LZMAProperties(byteReader), uncompSize.toInt()) default: throw ZipError.compressionNotSupported } From b5a0a02b54da42957dbe7a8f2cd42ff852600d0f Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:16:33 +0800 Subject: [PATCH 035/106] Remove installation instructions with CocoaPods and Carthage from README --- README.md | 64 ++++--------------------------------------------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index dc28b024..fdcf456c 100644 --- a/README.md +++ b/README.md @@ -32,15 +32,12 @@ Also, SWCompression is _written with Swift only._ ## Installation -SWCompression can be integrated into your project using Swift Package Manager, CocoaPods, or Carthage. +SWCompression can be integrated into your project using Swift Package Manager. -__Note:__ Due to [upcoming](https://blog.cocoapods.org/CocoaPods-Specs-Repo/) deprecation of CocoaPods no new releases -of SWCompression will be published using CocoaPods. The last release of SWCompression made available via CocoaPods was -4.8.7. Consider switching to Swift Package Manager instead. +__Note:__ SWCompression versions 4.8.6 and earlier were also made available via CocoaPods or Carthage. -### Swift Package Manager - -To install using SPM, add SWCompression to you package dependencies and specify it as a dependency for your target, e.g.: +To install with Swift Package manager, add SWCompression to you package dependencies and specify it as a dependency for +your target, e.g.: ```swift import PackageDescription @@ -62,59 +59,6 @@ let package = Package( More details you can find in [Swift Package Manager's Documentation](https://github.com/apple/swift-package-manager/tree/main/Documentation). -### CocoaPods - -Add `pod 'SWCompression', '~> 4.8'` and `use_frameworks!` lines to your Podfile. - -To complete installation, run `pod install`. - -If you need only some parts of framework, you can install only them using sub-podspecs. Available subspecs: - -- SWCompression/BZip2 -- SWCompression/Deflate -- SWCompression/Gzip -- SWCompression/LZMA -- SWCompression/LZMA2 -- SWCompression/LZ4 -- SWCompression/SevenZip -- SWCompression/TAR -- SWCompression/XZ -- SWCompression/Zlib -- SWCompression/ZIP - -#### "Optional Dependencies" - -For both ZIP and 7-Zip there is the most commonly used compression method: Deflate and LZMA/LZMA2 correspondingly. Thus, -SWCompression/ZIP subspec has SWCompression/Deflate subspec as a dependency and SWCompression/LZMA subspec is a -dependency for SWCompression/SevenZip. - -But both of these formats also support other compression methods, and some of them are implemented in SWCompression. -For CocoaPods configurations there are some sort of 'optional dependencies' for such compression methods. - -"Optional dependency" in this context means that SWCompression/ZIP or SWCompression/7-Zip will support a compression -method only if a corresponding subspec is expicitly specified in your Podfile and installed. - -List of "optional dependecies": - -- For SWCompression/ZIP: - - SWCompression/BZip2 - - SWCompression/LZMA -- For SWCompression/SevenZip: - - SWCompression/BZip2 - - SWCompression/Deflate - - SWCompression/LZ4 - -__Note:__ If you use Swift Package Manager or Carthage you always have everything (ZIP and 7-Zip are built with Deflate, -BZip2, LZMA/LZMA2 and LZ4 support). - -### Carthage - -Add to your Cartfile `github "tsolomko/SWCompression" ~> 4.8`. - -Then you should run `carthage update --use-xcframeworks`. After that drag and drop both `SWCompression.xcframework` and -`BitByteData.xcframework` files from from the `Carthage/Build/` directory into the "Frameworks, Libraries, and Embedded -Content" section of your target's "General" tab in Xcode. - ## Usage ### Basic Example From 9916541aa834f0178625456272849c78f4f2ac26 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:16:51 +0800 Subject: [PATCH 036/106] utils.py: change OS X to macOS --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index 392a8fb9..d582adeb 100755 --- a/utils.py +++ b/utils.py @@ -23,7 +23,7 @@ def _ci_script_macos(): _sprun_shell("xcodebuild -version") _sprun(["swift", "--version"]) xcodebuild_command_parts = ["xcodebuild", "-quiet", "-project", "SWCompression.xcodeproj", "-scheme", "SWCompression"] - destinations_actions = [(["-destination 'platform=OS X'"], ["clean", "test"]), + destinations_actions = [(["-destination 'platform=macOS'"], ["clean", "test"]), (["-destination 'platform=iOS Simulator,name=" + os.environ["IOS_SIMULATOR"] + "'"], ["clean", "test"]), (["-destination 'platform=watchOS Simulator,name=" + os.environ["WATCHOS_SIMULATOR"] + "'"], ["clean", "test"]), (["-destination 'platform=tvOS Simulator,name=Apple TV'"], ["clean", "test"])] From 33341b690b6cf1c4ffecc620292a274726d8477d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 01:17:21 +0800 Subject: [PATCH 037/106] [LZ4] Code comments grammar and wording improvements --- Sources/LZ4/LZ4.swift | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Sources/LZ4/LZ4.swift b/Sources/LZ4/LZ4.swift index 6d4aef2c..43c082b3 100644 --- a/Sources/LZ4/LZ4.swift +++ b/Sources/LZ4/LZ4.swift @@ -155,7 +155,7 @@ public enum LZ4: DecompressionAlgorithm { } // The functions below return uncompressed data and the offset to the next byte after the processed frame (even if - // the end of the input data was reached). The offset is needed to make multiDecompress function work. + // the end of the input data was reached). The offset is required to make multiDecompress function work. private static func process(legacyFrame data: Data) throws -> (Data, Data.Index) { let reader = LittleEndianByteReader(data: data) @@ -229,11 +229,11 @@ public enum LZ4: DecompressionAlgorithm { let contentSize: Int? if contentSizePresent { - // At this point valid LZ4 frame must have at least 13 bytes remaining for: content size (8 bytes), header + // At this point a valid LZ4 frame must have at least 13 bytes remaining for content size (8 bytes), header // checksum (1 byte), and EndMark (4 bytes), assuming zero data blocks. guard reader.bytesLeft >= 13 else { throw DataError.truncated } - // Since Data is indexed by the Int type, the maximum size of the uncompressed data that we can decode is + // Since Data is indexed by the Int type, the maximum size of uncompressed data that we can decode is // Int.max. However, LZ4 supports uncompressed data sizes up to UInt64.max, which is larger, so we check // for this possibility. let rawContentSize = reader.uint64() @@ -249,7 +249,7 @@ public enum LZ4: DecompressionAlgorithm { // If dictionary ID is present in the frame, then we must have a dictionary to successfully decode it. guard dictionary != nil else { throw DataError.corrupted } - // At this point valid LZ4 frame must have at least 9 bytes remaining for: dictionary ID (4 bytes), header + // At this point a valid LZ4 frame must have at least 9 bytes remaining for dictionary ID (4 bytes), header // checksum (1 byte), and EndMark (4 bytes), assuming zero data blocks. guard reader.bytesLeft >= 9 else { throw DataError.truncated } @@ -264,7 +264,7 @@ public enum LZ4: DecompressionAlgorithm { } if let extDictId = extDictId, let dictId = dictId { - // If dictionary ID is present in the frame, and passed as an argument, then they must be equal. + // If dictionary ID is present in the frame and passed as an argument, then they must be equal. guard extDictId == dictId else { throw DataError.corrupted } } @@ -278,7 +278,7 @@ public enum LZ4: DecompressionAlgorithm { while true { guard reader.bytesLeft >= 4 else { throw DataError.truncated } - // Either the size of the block, or the EndMark. + // Either the size of a block, or the EndMark. let blockMark = reader.uint32() // Check for the EndMark. if blockMark == 0 { @@ -287,8 +287,8 @@ public enum LZ4: DecompressionAlgorithm { // The highest bit indicates if the block is compressed. let compressed = blockMark & 0x80000000 == 0 let blockSize = (blockMark & 0x7FFFFFFF).toInt() - // Since we don't do manual memory allocation we don't have to constraint the block size. Nevertheless, we - // follow the reference implementation here and reject blocks with sizes greater than maximum block size. + // Since we don't do manual memory allocation we don't have to constrain the block size. Nevertheless, we + // follow the reference implementation here and reject blocks with sizes greater than the maximum block size. guard blockSize <= maxBlockSize else { throw DataError.corrupted } @@ -350,7 +350,7 @@ public enum LZ4: DecompressionAlgorithm { guard data.endIndex - reader.offset >= 1 else { throw DataError.truncated } let byte = reader.byte() - // There is no size limit on the literal count, so we need to check that it remains within Int range + // There is no size limit on the literal count, so we have to check that it remains within Int range // (similar to content size considerations). let (newLiteralCount, overflow) = literalCount.addingReportingOverflow(byte.toInt()) guard !overflow @@ -388,7 +388,7 @@ public enum LZ4: DecompressionAlgorithm { guard data.endIndex - reader.offset >= 1 else { throw DataError.truncated } let byte = reader.byte() - // Again, there is no size limit on the match length, so we need to check that it remains within Int + // Again, there is no size limit on the match length, so we have to check that it remains within Int // range. let (newMatchLength, overflow) = matchLength.addingReportingOverflow(byte.toInt()) guard !overflow @@ -401,8 +401,7 @@ public enum LZ4: DecompressionAlgorithm { } // We record the start index of the last encountered match to verify it against end-of-block restrictions. - // Note, that this refers to the bytes that we have found a match for, and not to the bytes that we're - // matching to. + // This refers to the bytes that we have found a match for, and not to the bytes that we're matching to. lastMatchStartIndex = out.endIndex let matchStartIndex = out.endIndex - offset for i in 0.. Date: Tue, 3 Feb 2026 12:27:27 +0800 Subject: [PATCH 038/106] [BZip2] Code comments clarification --- Sources/BZip2/BZip2+Compress.swift | 3 ++- Sources/BZip2/BZip2.swift | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index ed4cf7dc..e09ab1d6 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -41,7 +41,8 @@ extension BZip2: CompressionAlgorithm { public static func compress(data: Data, blockSize: BlockSize) -> Data { let bitWriter = MsbBitWriter() // We intentionally use smaller block size for compression to account for potential data size expansion - // after intial RLE, which seems to be not being expected by original BZip2 implementation. + // after intial RLE, which seems to be not being expected by original BZip2 implementation. + // In the worst case initial RLE causes expansion by a factor of 1.25, so 1000 / 1.25 = 800. let rawBlockSize = blockSize.sizeInKilobytes * 800 // BZip2 Header. bitWriter.write(number: 0x425a, bitsCount: 16) // Magic number = 'BZ'. diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index efbb844b..cf921553 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -26,7 +26,8 @@ public class BZip2: DecompressionAlgorithm { } static func decompress(_ bitReader: MsbBitReader) throws -> Data { - // Valid BZip2 "archive" must contain at least 14 bytes of data. + // Valid BZip2 "archive" must contain at least 14 bytes of data: magic number (2 bytes), method (1 byte), block + // size (1 byte), block type (6 bytes), block CRC (4 bytes). guard bitReader.bitsLeft >= 14 * 8 else { throw BZip2Error.wrongMagic } @@ -44,7 +45,7 @@ public class BZip2: DecompressionAlgorithm { var totalCRC: UInt32 = 0 while true { - // Using `Int64` because 48 bits may not fit into `Int` on some platforms. + // Using `UInt64` because 48 bits may not fit into `Int` on some platforms. let blockType = bitReader.uint64(fromBits: 48) let blockCRC32 = bitReader.uint32(fromBits: 32) From 60283d4a54106f9acc8997b8a6bb3588d02a1c3d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 3 Feb 2026 12:27:41 +0800 Subject: [PATCH 039/106] Reorder PrivacyInfo file in Xcode project --- SWCompression.xcodeproj/project.pbxproj | 2 +- azure-pipelines.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index cccc0158..4c450a1a 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -702,9 +702,9 @@ 06BE1ABE1DB410F100EE0F59 = { isa = PBXGroup; children = ( - E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */, 06BE1ACA1DB410F100EE0F59 /* Sources */, 06F065A01FFB763300312A82 /* Tests */, + E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */, E631055A27086132006EACC3 /* SWCompression.xctestplan */, 06BE1AC91DB410F100EE0F59 /* Products */, ); diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2d42e3d0..eb7f3d00 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,7 +30,6 @@ stages: DEVELOPER_DIR: '/Applications/Xcode_16.2.app' IOS_SIMULATOR: 'iPhone 16' WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' - # TODO: When Swift 6.0 support is dropped we can remove i386 search path for the watchOS simulator in Xcode. macosSwift61: imageName: 'macOS-15' DEVELOPER_DIR: '/Applications/Xcode_16.4.app' From d8c4527c89abeb97e7574964485d09d7e438509c Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 4 Feb 2026 17:27:56 +0800 Subject: [PATCH 040/106] [BZip2] Slightly optimize MTF transform of selectors during compression It can be statically proven that the output of MTF transform lies within a certain range, so a guard check can be replaced with an assertion. --- Sources/BZip2/BZip2+Compress.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index e09ab1d6..20d117bf 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -175,10 +175,14 @@ extension BZip2: CompressionAlgorithm { bitWriter.write(number: tables.count, bitsCount: 3) bitWriter.write(number: selectors.count, bitsCount: 15) - let mtfSelectors = mtf(selectors, characters: Array(0.. [Int] { + /// Assumes that the characters are given by a list of integers from 0 up to and including `maxValue`. + private static func mtf(_ array: [Int], maxValue: Int) -> [Int] { var out = [Int]() - /// Mutable copy of `characters`. - var dictionary = characters + var dictionary = Array(0...maxValue) for i in 0.. Date: Thu, 5 Feb 2026 15:02:44 +0800 Subject: [PATCH 041/106] [swcomp] Print benchmark metadatas in normal order --- Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift | 4 ++-- Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index 8b6bed42..cce435e4 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -54,7 +54,7 @@ final class RunBenchmarkCommand: Command { if baseMetadatas.count == 1 { baseMetadatas[baseMetadatas.first!.key] = "" } - for (metadataUUID, index) in baseMetadatas.sorted(by: { $0.value < $1.value }) { + for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { print("BASE\(index) Metadata") print("----------------") baseSaveFile.metadatas[metadataUUID]!.print() @@ -110,7 +110,7 @@ final class RunBenchmarkCommand: Command { if let baseResults = baseResults[result.id] { print("\nNEW: average = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))") - for (other, baseUUID) in baseResults { + for (other, baseUUID) in baseResults.sorted(by: { Int(baseMetadatas[$0.1]!.dropFirst().dropLast())! < Int(baseMetadatas[$1.1]!.dropFirst().dropLast())! }) { print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") result.printComparison(with: other) } diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index 6072869b..de451704 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -26,7 +26,7 @@ final class ShowBenchmarkCommand: Command { if newMetadatas.count == 1 { newMetadatas[newMetadatas.first!.key] = "" } - for (metadataUUID, index) in newMetadatas.sorted(by: { $0.value < $1.value }) { + for (metadataUUID, index) in newMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { print("NEW\(index) Metadata") print("---------------") newSaveFile.metadatas[metadataUUID]!.print() @@ -47,7 +47,7 @@ final class ShowBenchmarkCommand: Command { if baseMetadatas.count == 1 { baseMetadatas[baseMetadatas.first!.key] = "" } - for (metadataUUID, index) in baseMetadatas.sorted(by: { $0.value < $1.value }) { + for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { print("BASE\(index) Metadata") print("----------------") baseSaveFile.metadatas[metadataUUID]!.print() @@ -61,14 +61,14 @@ final class ShowBenchmarkCommand: Command { for resultId in newResults.keys.sorted() { let results = newResults[resultId]! - for (result, metadataUUID) in results { + for (result, metadataUUID) in results.sorted(by: { Int(newMetadatas[$0.1]!.dropFirst().dropLast())! < Int(newMetadatas[$1.1]!.dropFirst().dropLast())! }) { let benchmark = Benchmarks(rawValue: result.name)?.initialized(result.input) print("\(result.name) => \(result.input), iterations = \(result.iterCount)") print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))") if let baseResults = baseResults[resultId] { - for (other, baseUUID) in baseResults { + for (other, baseUUID) in baseResults.sorted(by: { Int(baseMetadatas[$0.1]!.dropFirst().dropLast())! < Int(baseMetadatas[$1.1]!.dropFirst().dropLast())! }) { print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") result.printComparison(with: other) } From 5a0eb8187abe943bdd8fc3b6775fcf81750928f1 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 7 Feb 2026 23:10:11 +0800 Subject: [PATCH 042/106] [XZ] Rename a file for consistency --- SWCompression.xcodeproj/project.pbxproj | 8 ++++---- ...yteReader+XZ.swift => LittleEndianByteReader+XZ.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename Sources/XZ/{ByteReader+XZ.swift => LittleEndianByteReader+XZ.swift} (100%) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 4c450a1a..7315276f 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -96,7 +96,7 @@ 06BAC5941F8CFF28002443F4 /* ZipEntryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BAC5931F8CFF28002443F4 /* ZipEntryInfo.swift */; }; 06BB8F301E0C2C720079FB9E /* LZMA2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */; }; 06CC3FDD1F8AAE8B00BD576D /* MsbBitReader+7z.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CC3FDC1F8AAE8B00BD576D /* MsbBitReader+7z.swift */; }; - 06CC3FE21F8AAF3100BD576D /* ByteReader+XZ.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CC3FE11F8AAF3100BD576D /* ByteReader+XZ.swift */; }; + 06CC3FE21F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CC3FE11F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift */; }; 06CDFC9E1F111D2600292758 /* Deflate+Compress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDFC9D1F111D2600292758 /* Deflate+Compress.swift */; }; 06CDFCA31F111D6C00292758 /* BZip2Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDFCA21F111D6C00292758 /* BZip2Error.swift */; }; 06CDFCA81F111D9700292758 /* DeflateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDFCA71F111D9700292758 /* DeflateError.swift */; }; @@ -396,7 +396,7 @@ 06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LZMA2.swift; sourceTree = ""; }; 06BE1AC81DB410F100EE0F59 /* SWCompression.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SWCompression.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 06CC3FDC1F8AAE8B00BD576D /* MsbBitReader+7z.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MsbBitReader+7z.swift"; sourceTree = ""; }; - 06CC3FE11F8AAF3100BD576D /* ByteReader+XZ.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ByteReader+XZ.swift"; sourceTree = ""; }; + 06CC3FE11F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LittleEndianByteReader+XZ.swift"; sourceTree = ""; }; 06CDFC9D1F111D2600292758 /* Deflate+Compress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deflate+Compress.swift"; sourceTree = ""; }; 06CDFCA21F111D6C00292758 /* BZip2Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BZip2Error.swift; sourceTree = ""; }; 06CDFCA71F111D9700292758 /* DeflateError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeflateError.swift; sourceTree = ""; }; @@ -683,7 +683,7 @@ 061C06201F0E89D900832F0C /* XZError.swift */, 065146221FA8E20C007F83F4 /* XZBlock.swift */, 0651461D1FA8E004007F83F4 /* XZStreamHeader.swift */, - 06CC3FE11F8AAF3100BD576D /* ByteReader+XZ.swift */, + 06CC3FE11F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift */, 06516B75213938B000C9823B /* Sha256.swift */, ); path = XZ; @@ -1446,7 +1446,7 @@ 06CDFCA31F111D6C00292758 /* BZip2Error.swift in Sources */, 06D3802E21E36190008B50C6 /* Code.swift in Sources */, 06CFF6B22078C5A5003B8375 /* TarParser.swift in Sources */, - 06CC3FE21F8AAF3100BD576D /* ByteReader+XZ.swift in Sources */, + 06CC3FE21F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift in Sources */, 065D0ADC1F2B9D9100CE2DA8 /* 7zEntryInfo.swift in Sources */, 06F276DF1F2BAB4A00E67335 /* 7zEntry.swift in Sources */, 0651461E1FA8E004007F83F4 /* XZStreamHeader.swift in Sources */, diff --git a/Sources/XZ/ByteReader+XZ.swift b/Sources/XZ/LittleEndianByteReader+XZ.swift similarity index 100% rename from Sources/XZ/ByteReader+XZ.swift rename to Sources/XZ/LittleEndianByteReader+XZ.swift From 5e8f568f6486b0a27a71c0dc77b5ef7624f5e502 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 17:20:16 +0800 Subject: [PATCH 043/106] [swcomp] Save and show benchmark warmup iteration --- Sources/swcomp/Benchmarks/Benchmark.swift | 6 +++--- Sources/swcomp/Benchmarks/BenchmarkResult.swift | 1 + .../swcomp/Benchmarks/RunBenchmarkCommand.swift | 16 ++++++++++++---- .../swcomp/Benchmarks/ShowBenchmarkCommand.swift | 13 +++++++++++-- Sources/swcomp/Containers/CommonFunctions.swift | 2 +- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Sources/swcomp/Benchmarks/Benchmark.swift b/Sources/swcomp/Benchmarks/Benchmark.swift index 3f353e97..695b9d3c 100644 --- a/Sources/swcomp/Benchmarks/Benchmark.swift +++ b/Sources/swcomp/Benchmarks/Benchmark.swift @@ -9,7 +9,7 @@ protocol Benchmark { init(_ input: String) - func warmupIteration() + func warmupIteration() -> Double func measure() -> Double @@ -23,8 +23,8 @@ extension Benchmark { return 10 } - func warmupIteration() { - _ = measure() + func warmupIteration() -> Double { + return measure() } func format(_ value: Double) -> String { diff --git a/Sources/swcomp/Benchmarks/BenchmarkResult.swift b/Sources/swcomp/Benchmarks/BenchmarkResult.swift index e32563dc..2b1fd105 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkResult.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkResult.swift @@ -12,6 +12,7 @@ struct BenchmarkResult: Codable { var iterCount: Int var avg: Double var std: Double + var warmup: Double? var id: String { return [self.name, self.input, String(self.iterCount)].joined(separator: "<#>") diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index cce435e4..434257ae 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -77,10 +77,14 @@ final class RunBenchmarkCommand: Command { let benchmark = self.selectedBenchmark.initialized(input) let iterationCount = self.iterationCount ?? benchmark.defaultIterationCount + let warmup: Double? if !self.noWarmup { - print("Warmup iteration...") + print("Warmup iteration...", terminator: " ") // Zeroth (excluded) iteration. - benchmark.warmupIteration() + warmup = benchmark.warmupIteration() + print(benchmark.format(warmup!)) + } else { + warmup = nil } var sum = 0.0 @@ -106,12 +110,16 @@ final class RunBenchmarkCommand: Command { let avg = sum / Double(iterationCount) let std = sqrt(squareSum / Double(iterationCount) - sum * sum / Double(iterationCount * iterationCount)) let result = BenchmarkResult(name: self.selectedBenchmark.rawValue, input: input, iterCount: iterationCount, - avg: avg, std: std) + avg: avg, std: std, warmup: warmup) if let baseResults = baseResults[result.id] { print("\nNEW: average = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))") for (other, baseUUID) in baseResults.sorted(by: { Int(baseMetadatas[$0.1]!.dropFirst().dropLast())! < Int(baseMetadatas[$1.1]!.dropFirst().dropLast())! }) { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") + if let otherWarmup = other.warmup { + print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std)), warmup = \(benchmark.format(otherWarmup))") + } else { + print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") + } result.printComparison(with: other) } } else { diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index de451704..c234c8a7 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -66,10 +66,19 @@ final class ShowBenchmarkCommand: Command { print("\(result.name) => \(result.input), iterations = \(result.iterCount)") - print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))") + + if let warmup = result.warmup { + print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std)), warmup = \(benchmark.format(warmup))") + } else { + print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))") + } if let baseResults = baseResults[resultId] { for (other, baseUUID) in baseResults.sorted(by: { Int(baseMetadatas[$0.1]!.dropFirst().dropLast())! < Int(baseMetadatas[$1.1]!.dropFirst().dropLast())! }) { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") + if let otherWarmup = other.warmup { + print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std)), warmup = \(benchmark.format(otherWarmup))") + } else { + print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") + } result.printComparison(with: other) } } diff --git a/Sources/swcomp/Containers/CommonFunctions.swift b/Sources/swcomp/Containers/CommonFunctions.swift index b41c8216..89db6a9d 100644 --- a/Sources/swcomp/Containers/CommonFunctions.swift +++ b/Sources/swcomp/Containers/CommonFunctions.swift @@ -147,4 +147,4 @@ func writeFile(_ entry: T, _ outputURL: URL, _ verbose: Bool) } try fileManager.setAttributes(attributes, ofItemAtPath: entryFullURL.path) -} \ No newline at end of file +} From 3537e0ada1b8632cc1fa004db6316950b63b2506 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 17:27:02 +0800 Subject: [PATCH 044/106] [swcomp] Save raw benchmark data for posterity --- Sources/swcomp/Benchmarks/BenchmarkResult.swift | 1 + Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/swcomp/Benchmarks/BenchmarkResult.swift b/Sources/swcomp/Benchmarks/BenchmarkResult.swift index 2b1fd105..d26c2902 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkResult.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkResult.swift @@ -13,6 +13,7 @@ struct BenchmarkResult: Codable { var avg: Double var std: Double var warmup: Double? + var iters: [Double]? var id: String { return [self.name, self.input, String(self.iterCount)].joined(separator: "<#>") diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index 434257ae..a0ad523c 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -94,6 +94,7 @@ final class RunBenchmarkCommand: Command { #if !os(Linux) fflush(__stdoutp) #endif + var iterations = [Double]() for i in 1...iterationCount { if i > 1 { print(", ", terminator: "") @@ -105,12 +106,13 @@ final class RunBenchmarkCommand: Command { #endif sum += speed squareSum += speed * speed + iterations.append(speed) } let avg = sum / Double(iterationCount) let std = sqrt(squareSum / Double(iterationCount) - sum * sum / Double(iterationCount * iterationCount)) let result = BenchmarkResult(name: self.selectedBenchmark.rawValue, input: input, iterCount: iterationCount, - avg: avg, std: std, warmup: warmup) + avg: avg, std: std, warmup: warmup, iters: iterations) if let baseResults = baseResults[result.id] { print("\nNEW: average = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))") From e236b502b713c04eb6283a62eece40e55a97e927 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 17:54:06 +0800 Subject: [PATCH 045/106] [swcomp] Improve benchmark show output for multi-way comparsions --- Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index c234c8a7..403783fd 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -61,12 +61,13 @@ final class ShowBenchmarkCommand: Command { for resultId in newResults.keys.sorted() { let results = newResults[resultId]! + print() + print("----------------") + print() + print("\(results[0].0.name) => \(results[0].0.input), iterations = \(results[0].0.iterCount)") + print() for (result, metadataUUID) in results.sorted(by: { Int(newMetadatas[$0.1]!.dropFirst().dropLast())! < Int(newMetadatas[$1.1]!.dropFirst().dropLast())! }) { let benchmark = Benchmarks(rawValue: result.name)?.initialized(result.input) - - print("\(result.name) => \(result.input), iterations = \(result.iterCount)") - - if let warmup = result.warmup { print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std)), warmup = \(benchmark.format(warmup))") } else { @@ -82,8 +83,6 @@ final class ShowBenchmarkCommand: Command { result.printComparison(with: other) } } - - print() } } } From 6b701e0b2fd8fa96ffeba41045b6d740d41358d0 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 19:14:26 +0800 Subject: [PATCH 046/106] [swcomp] Add --print-uuid option to benchmark show command This option enables printing of internal UUIDs of benchmark runs saved in the file. --- Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index 403783fd..67e1ea98 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -18,6 +18,9 @@ final class ShowBenchmarkCommand: Command { @Key("-c", "--compare", description: "Compare with other saved benchmarks results") var comparePath: String? + @Flag("--print-uuid", description: "Prints internal UUIDs of saved benchmark runs") + var printUuid: Bool + @Param var path: String func execute() throws { @@ -29,6 +32,9 @@ final class ShowBenchmarkCommand: Command { for (metadataUUID, index) in newMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { print("NEW\(index) Metadata") print("---------------") + if self.printUuid { + print("UUID: \(metadataUUID)") + } newSaveFile.metadatas[metadataUUID]!.print() } @@ -50,6 +56,9 @@ final class ShowBenchmarkCommand: Command { for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { print("BASE\(index) Metadata") print("----------------") + if self.printUuid { + print("UUID: \(metadataUUID)") + } baseSaveFile.metadatas[metadataUUID]!.print() } From c05b37d3245f176f4bbceec9a1cf812b2e5d744a Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 19:15:20 +0800 Subject: [PATCH 047/106] [swcomp] Add --metadata-only option to benchmark show command With this option only metadata is printed for the benchmark runs saved in the file. --- Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index 67e1ea98..649ebb48 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -21,6 +21,9 @@ final class ShowBenchmarkCommand: Command { @Flag("--print-uuid", description: "Prints internal UUIDs of saved benchmark runs") var printUuid: Bool + @Flag("--metadata-only", description: "Prints only metadata of saved benchmark runs") + var metadataOnly: Bool + @Param var path: String func execute() throws { @@ -68,6 +71,10 @@ final class ShowBenchmarkCommand: Command { } } + if self.metadataOnly { + return + } + for resultId in newResults.keys.sorted() { let results = newResults[resultId]! print() From 5618fea2ee78353a419fa60ba4c0f0e796c0074d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 21:20:36 +0800 Subject: [PATCH 048/106] [swcomp] Add --self-compare option to benchmark show command This option allows to compare a run with other runs in the same file. --- .../Benchmarks/RunBenchmarkCommand.swift | 4 +- .../Benchmarks/ShowBenchmarkCommand.swift | 47 +++++++++++++++++-- Sources/swcomp/SwcompError.swift | 10 ++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index a0ad523c..7b8c81fb 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -32,7 +32,7 @@ final class RunBenchmarkCommand: Command { @Key("-d", "--description", description: "Adds a custom description when saving results") var description: String? - @Flag("-t", "--preserve-timestamp", description: "Adds a timestamp when saving a result") + @Flag("-t", "--preserve-timestamp", description: "Adds a timestamp when saving results") var preserveTimestamp: Bool @Flag("-W", "--no-warmup", description: "Disables warmup iteration") @@ -139,7 +139,7 @@ final class RunBenchmarkCommand: Command { var isDir = ObjCBool(false) let saveFileExists = FileManager.default.fileExists(atPath: savePath, isDirectory: &isDir) - if self.append && saveFileExists { + if self.append && saveFileExists { if isDir.boolValue { swcompExit(.benchmarkCannotAppendToDirectory) } diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index 649ebb48..905c442b 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -18,6 +18,9 @@ final class ShowBenchmarkCommand: Command { @Key("-c", "--compare", description: "Compare with other saved benchmarks results") var comparePath: String? + @Key("--self-compare", description: "Compare runs within the same file with new results identified by run UUID") + var selfCompare: String? + @Flag("--print-uuid", description: "Prints internal UUIDs of saved benchmark runs") var printUuid: Bool @@ -26,12 +29,26 @@ final class ShowBenchmarkCommand: Command { @Param var path: String + var optionGroups: [OptionGroup] { + return [.atMostOne($comparePath, $selfCompare)] + } + func execute() throws { let newSaveFile = try SaveFile.load(from: self.path) - var newMetadatas = Dictionary(uniqueKeysWithValues: zip(newSaveFile.metadatas.keys, (1...newSaveFile.metadatas.count).map { "(\($0))" })) - if newMetadatas.count == 1 { - newMetadatas[newMetadatas.first!.key] = "" + var newMetadatas: [UUID: String] + if let newUUIDString = self.selfCompare { + guard let newUUID = UUID(uuidString: newUUIDString) + else { swcompExit(.benchmarkBadUUID) } + guard newSaveFile.metadatas.contains(where: { $0.key == newUUID} ) + else { swcompExit(.benchmarkNoUUID) } + newMetadatas = [newUUID: ""] + } else { + newMetadatas = Dictionary(uniqueKeysWithValues: zip(newSaveFile.metadatas.keys, (1...newSaveFile.metadatas.count).map { "(\($0))" })) + if newMetadatas.count == 1 { + newMetadatas[newMetadatas.first!.key] = "" + } } + for (metadataUUID, index) in newMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { print("NEW\(index) Metadata") print("---------------") @@ -42,7 +59,7 @@ final class ShowBenchmarkCommand: Command { } var newResults = [String: [(BenchmarkResult, UUID)]]() - for newRun in newSaveFile.runs { + for newRun in newSaveFile.runs.filter ( { (run: SaveFile.Run) in newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { newResults.merge(Dictionary(grouping: newRun.results.map { ($0, newRun.metadataUUID) }, by: { $0.0.id }), uniquingKeysWith: { $0 + $1 }) } @@ -69,6 +86,28 @@ final class ShowBenchmarkCommand: Command { baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }), uniquingKeysWith: { $0 + $1 }) } + } else if let newUUIDString = self.selfCompare { + guard let newUUID = UUID(uuidString: newUUIDString) + else { swcompExit(.benchmarkBadUUID) } + guard newSaveFile.metadatas.contains(where: { $0.key == newUUID} ) + else { swcompExit(.benchmarkNoUUID) } + baseMetadatas = Dictionary(uniqueKeysWithValues: zip(newSaveFile.metadatas.keys.filter({ $0 != newUUID }), (1...(newSaveFile.metadatas.count - 1)).map { "(\($0))" })) + if baseMetadatas.count == 1 { + baseMetadatas[baseMetadatas.first!.key] = "" + } + for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { + print("BASE\(index) Metadata") + print("----------------") + if self.printUuid { + print("UUID: \(metadataUUID)") + } + newSaveFile.metadatas[metadataUUID]!.print() + } + + for baseRun in newSaveFile.runs.filter ( { (run: SaveFile.Run) in !newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { + baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }), + uniquingKeysWith: { $0 + $1 }) + } } if self.metadataOnly { diff --git a/Sources/swcomp/SwcompError.swift b/Sources/swcomp/SwcompError.swift index 50196d20..4fe3312c 100644 --- a/Sources/swcomp/SwcompError.swift +++ b/Sources/swcomp/SwcompError.swift @@ -19,6 +19,8 @@ enum SwcompError { case benchmarkReaderTarNoInputSize(String) case benchmarkCannotGetSubcommandPathWindows case benchmarkCannotAppendToDirectory + case benchmarkBadUUID + case benchmarkNoUUID case containerSymLinkDestPath(String) case containerHardLinkDestPath(String) case containerNoEntryData(String) @@ -54,6 +56,10 @@ enum SwcompError { return 206 case .benchmarkCannotAppendToDirectory: return 207 + case .benchmarkBadUUID: + return 208 + case .benchmarkNoUUID: + return 218 case .containerSymLinkDestPath: return 301 case .containerHardLinkDestPath: @@ -99,6 +105,10 @@ enum SwcompError { return "Cannot get subcommand path on Windows. (This error should never be shown!)" case .benchmarkCannotAppendToDirectory: return "Cannot append results to the save path since it is a directory." + case .benchmarkBadUUID: + return "Specified run UUID is not well-formed." + case .benchmarkNoUUID: + return "Specified run UUID is not found in the file." case .containerSymLinkDestPath(let entryName): return "Unable to get destination path for symbolic link \(entryName)." case .containerHardLinkDestPath(let entryName): From 871b5dbe03c57a416ab38798f21462529e6a7767 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 21:53:00 +0800 Subject: [PATCH 049/106] [swcomp] Add benchmark remove-run subcommand --- .../swcomp/Benchmarks/BenchmarkGroup.swift | 3 +- .../swcomp/Benchmarks/RemoveRunCommand.swift | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Sources/swcomp/Benchmarks/RemoveRunCommand.swift diff --git a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift index 2852c053..b7042baf 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift @@ -14,7 +14,8 @@ final class BenchmarkGroup: CommandGroup { let children: [Routable] = [ RunBenchmarkCommand(), - ShowBenchmarkCommand() + ShowBenchmarkCommand(), + RemoveRunCommand() ] } diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift new file mode 100644 index 00000000..a40de786 --- /dev/null +++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift @@ -0,0 +1,37 @@ +// Copyright (c) 2026 Timofey Solomko +// Licensed under MIT License +// +// See LICENSE for license information + +#if os(Linux) + import CoreFoundation +#endif + +import Foundation +import SwiftCLI + +final class RemoveRunCommand: Command { + + let name = "remove-run" + let shortDescription = "Removes run from the file" + let longDescription = "Removes a run identified by UUID and any associated results from the specified file" + + @Param var runUUID: String + @Param var path: String + + func execute() throws { + var saveFile = try SaveFile.load(from: self.path) + guard let uuid = UUID(uuidString: self.runUUID) + else { swcompExit(.benchmarkBadUUID) } + guard saveFile.metadatas.contains(where: { $0.key == uuid} ) + else { swcompExit(.benchmarkNoUUID) } + + saveFile.metadatas.removeValue(forKey: uuid) + saveFile.runs.removeAll(where: { $0.metadataUUID == uuid }) + + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(saveFile) + try data.write(to: URL(fileURLWithPath: path)) + } +} From b2b4e18a581f2d38c731138f17b10b84ce8db209 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 11 Feb 2026 21:33:27 +0800 Subject: [PATCH 050/106] [swcomp] Remove redundant import statements --- Sources/swcomp/Benchmarks/RemoveRunCommand.swift | 4 ---- Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift | 1 - Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 4 ---- .../CompressionMethod+CustomStringConvertible.swift | 1 - .../ContainerEntryInfo+CustomStringConvertible.swift | 1 - .../Extensions/FileSystemType+CustomStringConvertible.swift | 1 - .../Extensions/GzipHeader+CustomStringConvertible.swift | 1 - .../swcomp/Extensions/TarFormat+ConvertibleFromString.swift | 1 - 8 files changed, 14 deletions(-) diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift index a40de786..a9e1072c 100644 --- a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift +++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift @@ -3,10 +3,6 @@ // // See LICENSE for license information -#if os(Linux) - import CoreFoundation -#endif - import Foundation import SwiftCLI diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index 7b8c81fb..f9c58b76 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -8,7 +8,6 @@ #endif import Foundation -import SWCompression import SwiftCLI final class RunBenchmarkCommand: Command { diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index 905c442b..4fb08502 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -3,10 +3,6 @@ // // See LICENSE for license information -#if os(Linux) - import CoreFoundation -#endif - import Foundation import SwiftCLI diff --git a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift index 67a49b00..5a3f66d3 100644 --- a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import SWCompression extension CompressionMethod: CustomStringConvertible { diff --git a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift index 891c5c3f..e885adad 100644 --- a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import SWCompression extension ContainerEntryInfo where Self: CustomStringConvertible { diff --git a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift index 98503229..6c0f40c8 100644 --- a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import SWCompression extension FileSystemType: CustomStringConvertible { diff --git a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift index 25011661..a0cc5fa2 100644 --- a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import SWCompression extension GzipHeader: CustomStringConvertible { diff --git a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift index 7d0231a1..5d1e9427 100644 --- a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift +++ b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import SwiftCLI import SWCompression From 3f5694aeee60ebbd9751b93500def43133d6b4de Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 01:34:05 +0800 Subject: [PATCH 051/106] [swcomp] Rename SaveFile to OldSaveFile --- .../Benchmarks/{SaveFile.swift => OldSaveFile.swift} | 6 +++--- Sources/swcomp/Benchmarks/RemoveRunCommand.swift | 2 +- Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift | 10 +++++----- Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) rename Sources/swcomp/Benchmarks/{SaveFile.swift => OldSaveFile.swift} (72%) diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/OldSaveFile.swift similarity index 72% rename from Sources/swcomp/Benchmarks/SaveFile.swift rename to Sources/swcomp/Benchmarks/OldSaveFile.swift index ac3b859f..61610f01 100644 --- a/Sources/swcomp/Benchmarks/SaveFile.swift +++ b/Sources/swcomp/Benchmarks/OldSaveFile.swift @@ -5,7 +5,7 @@ import Foundation -struct SaveFile: Codable { +struct OldSaveFile: Codable { struct Run: Codable { @@ -18,10 +18,10 @@ struct SaveFile: Codable { var runs: [Run] - static func load(from path: String) throws -> SaveFile { + static func load(from path: String) throws -> OldSaveFile { let decoder = JSONDecoder() let data = try Data(contentsOf: URL(fileURLWithPath: path)) - return try decoder.decode(SaveFile.self, from: data) + return try decoder.decode(OldSaveFile.self, from: data) } } diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift index a9e1072c..8c8384b6 100644 --- a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift +++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift @@ -16,7 +16,7 @@ final class RemoveRunCommand: Command { @Param var path: String func execute() throws { - var saveFile = try SaveFile.load(from: self.path) + var saveFile = try OldSaveFile.load(from: self.path) guard let uuid = UUID(uuidString: self.runUUID) else { swcompExit(.benchmarkBadUUID) } guard saveFile.metadatas.contains(where: { $0.key == uuid} ) diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index f9c58b76..4a6edeab 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -47,7 +47,7 @@ final class RunBenchmarkCommand: Command { var baseResults = [String: [(BenchmarkResult, UUID)]]() var baseMetadatas = [UUID: String]() if let comparePath = comparePath { - let baseSaveFile = try SaveFile.load(from: comparePath) + let baseSaveFile = try OldSaveFile.load(from: comparePath) baseMetadatas = Dictionary(uniqueKeysWithValues: zip(baseSaveFile.metadatas.keys, (1...baseSaveFile.metadatas.count).map { "(\($0))" })) if baseMetadatas.count == 1 { @@ -133,7 +133,7 @@ final class RunBenchmarkCommand: Command { if let savePath = self.savePath { let metadata = try BenchmarkMetadata(self.description, self.preserveTimestamp) - var saveFile: SaveFile + var saveFile: OldSaveFile var isDir = ObjCBool(false) let saveFileExists = FileManager.default.fileExists(atPath: savePath, isDirectory: &isDir) @@ -142,7 +142,7 @@ final class RunBenchmarkCommand: Command { if isDir.boolValue { swcompExit(.benchmarkCannotAppendToDirectory) } - saveFile = try SaveFile.load(from: savePath) + saveFile = try OldSaveFile.load(from: savePath) var uuid: UUID if let foundUUID = saveFile.metadatas.first(where: { $0.value == metadata })?.key { uuid = foundUUID @@ -152,10 +152,10 @@ final class RunBenchmarkCommand: Command { } while saveFile.metadatas[uuid] != nil saveFile.metadatas[uuid] = metadata } - saveFile.runs.append(SaveFile.Run(metadataUUID: uuid, results: newResults)) + saveFile.runs.append(OldSaveFile.Run(metadataUUID: uuid, results: newResults)) } else { let uuid = UUID() - saveFile = SaveFile(metadatas: [uuid: metadata], runs: [SaveFile.Run(metadataUUID: uuid, results: newResults)]) + saveFile = OldSaveFile(metadatas: [uuid: metadata], runs: [OldSaveFile.Run(metadataUUID: uuid, results: newResults)]) } let encoder = JSONEncoder() diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index 4fb08502..ac78413c 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -30,7 +30,7 @@ final class ShowBenchmarkCommand: Command { } func execute() throws { - let newSaveFile = try SaveFile.load(from: self.path) + let newSaveFile = try OldSaveFile.load(from: self.path) var newMetadatas: [UUID: String] if let newUUIDString = self.selfCompare { guard let newUUID = UUID(uuidString: newUUIDString) @@ -55,7 +55,7 @@ final class ShowBenchmarkCommand: Command { } var newResults = [String: [(BenchmarkResult, UUID)]]() - for newRun in newSaveFile.runs.filter ( { (run: SaveFile.Run) in newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { + for newRun in newSaveFile.runs.filter ( { (run: OldSaveFile.Run) in newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { newResults.merge(Dictionary(grouping: newRun.results.map { ($0, newRun.metadataUUID) }, by: { $0.0.id }), uniquingKeysWith: { $0 + $1 }) } @@ -63,7 +63,7 @@ final class ShowBenchmarkCommand: Command { var baseResults = [String: [(BenchmarkResult, UUID)]]() var baseMetadatas = [UUID: String]() if let comparePath = comparePath { - let baseSaveFile = try SaveFile.load(from: comparePath) + let baseSaveFile = try OldSaveFile.load(from: comparePath) baseMetadatas = Dictionary(uniqueKeysWithValues: zip(baseSaveFile.metadatas.keys, (1...baseSaveFile.metadatas.count).map { "(\($0))" })) if baseMetadatas.count == 1 { @@ -100,7 +100,7 @@ final class ShowBenchmarkCommand: Command { newSaveFile.metadatas[metadataUUID]!.print() } - for baseRun in newSaveFile.runs.filter ( { (run: SaveFile.Run) in !newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { + for baseRun in newSaveFile.runs.filter ( { (run: OldSaveFile.Run) in !newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }), uniquingKeysWith: { $0 + $1 }) } From 98b5ee3610f155231deff9c9ead5fb26a1d3c179 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 11:45:53 +0800 Subject: [PATCH 052/106] [swcomp] Save results file JSON with keys sorted This ensures a more stable layout of JSON fields between updates to the same file. --- Sources/swcomp/Benchmarks/RemoveRunCommand.swift | 3 ++- Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift index 8c8384b6..779614e7 100644 --- a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift +++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift @@ -26,8 +26,9 @@ final class RemoveRunCommand: Command { saveFile.runs.removeAll(where: { $0.metadataUUID == uuid }) let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let data = try encoder.encode(saveFile) try data.write(to: URL(fileURLWithPath: path)) } + } diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index 4a6edeab..8b9844fd 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -159,8 +159,7 @@ final class RunBenchmarkCommand: Command { } let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let data = try encoder.encode(saveFile) try data.write(to: URL(fileURLWithPath: savePath)) } From 20d33b0966505ab50bbdc026626c8038b9c5034c Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 11:49:10 +0800 Subject: [PATCH 053/106] [swcomp] Add a new benchmark save file format Ultimately, it turned out that the approach of the old save file format of separating runs metadatas and results only leads to super complicated code. Unfortunately, it didn't help with append-updates to save files (they are still loaded fully, their content is modified in memory, and the actual file is then overwritten). --- Sources/swcomp/Benchmarks/SaveFile.swift | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Sources/swcomp/Benchmarks/SaveFile.swift diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift new file mode 100644 index 00000000..9ff6f0bf --- /dev/null +++ b/Sources/swcomp/Benchmarks/SaveFile.swift @@ -0,0 +1,27 @@ +// Copyright (c) 2026 Timofey Solomko +// Licensed under MIT License +// +// See LICENSE for license information + +import Foundation + +struct SaveFile: Codable { + + struct Run: Codable { + + var uuid: UUID + var metadata: BenchmarkMetadata + var results: [BenchmarkResult] + + } + + var formatVersion = 2 + var runs: [Run] + + static func load(from path: String) throws -> SaveFile { + let decoder = JSONDecoder() + let data = try Data(contentsOf: URL(fileURLWithPath: path)) + return try decoder.decode(SaveFile.self, from: data) + } + +} From 4ec437e9120192f6e793718a76f7bf057f433bb5 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 13:02:58 +0800 Subject: [PATCH 054/106] [swcomp] Add benchmark convert command This command converts save file from old format to the new one. --- .../swcomp/Benchmarks/BenchmarkGroup.swift | 3 ++- .../swcomp/Benchmarks/ConvertCommand.swift | 25 +++++++++++++++++++ Sources/swcomp/Benchmarks/SaveFile.swift | 14 +++++++++++ Sources/swcomp/SwcompError.swift | 5 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Sources/swcomp/Benchmarks/ConvertCommand.swift diff --git a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift index b7042baf..40eb0aa8 100644 --- a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift +++ b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift @@ -15,7 +15,8 @@ final class BenchmarkGroup: CommandGroup { let children: [Routable] = [ RunBenchmarkCommand(), ShowBenchmarkCommand(), - RemoveRunCommand() + RemoveRunCommand(), + ConvertCommand() ] } diff --git a/Sources/swcomp/Benchmarks/ConvertCommand.swift b/Sources/swcomp/Benchmarks/ConvertCommand.swift new file mode 100644 index 00000000..1781af30 --- /dev/null +++ b/Sources/swcomp/Benchmarks/ConvertCommand.swift @@ -0,0 +1,25 @@ +// Copyright (c) 2026 Timofey Solomko +// Licensed under MIT License +// +// See LICENSE for license information + +import Foundation +import SwiftCLI + +final class ConvertCommand: Command { + + let name = "convert" + let shortDescription = "Converts save file to a new format" + let longDescription = "Converts specified with saved benchmark results to a new format" + + @Param var path: String + + func execute() throws { + let oldSaveFile = try OldSaveFile.load(from: self.path) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(SaveFile(oldSaveFile)) + try data.write(to: URL(fileURLWithPath: path)) + } + +} diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift index 9ff6f0bf..8ee00e6f 100644 --- a/Sources/swcomp/Benchmarks/SaveFile.swift +++ b/Sources/swcomp/Benchmarks/SaveFile.swift @@ -18,6 +18,20 @@ struct SaveFile: Codable { var formatVersion = 2 var runs: [Run] + init(_ oldSaveFile: OldSaveFile) { + var d = [UUID: [BenchmarkResult]]() + for run in oldSaveFile.runs { + d[run.metadataUUID] = (d[run.metadataUUID] ?? [BenchmarkResult]()) + run.results + } + + self.runs = [Run]() + for (uuid, results) in d { + guard let metadata = oldSaveFile.metadatas[uuid] + else { swcompExit(.benchmarkOldFormatNoUUIDMetadata(uuid)) } + self.runs.append(Run(uuid: uuid, metadata: metadata, results: results.sorted(by: { $0.id < $1.id }))) + } + } + static func load(from path: String) throws -> SaveFile { let decoder = JSONDecoder() let data = try Data(contentsOf: URL(fileURLWithPath: path)) diff --git a/Sources/swcomp/SwcompError.swift b/Sources/swcomp/SwcompError.swift index 4fe3312c..bf90ed94 100644 --- a/Sources/swcomp/SwcompError.swift +++ b/Sources/swcomp/SwcompError.swift @@ -21,6 +21,7 @@ enum SwcompError { case benchmarkCannotAppendToDirectory case benchmarkBadUUID case benchmarkNoUUID + case benchmarkOldFormatNoUUIDMetadata(UUID) case containerSymLinkDestPath(String) case containerHardLinkDestPath(String) case containerNoEntryData(String) @@ -60,6 +61,8 @@ enum SwcompError { return 208 case .benchmarkNoUUID: return 218 + case .benchmarkOldFormatNoUUIDMetadata: + return 209 case .containerSymLinkDestPath: return 301 case .containerHardLinkDestPath: @@ -109,6 +112,8 @@ enum SwcompError { return "Specified run UUID is not well-formed." case .benchmarkNoUUID: return "Specified run UUID is not found in the file." + case .benchmarkOldFormatNoUUIDMetadata(let uuid): + return "No metadata found in the old format save file for UUID = \(uuid)." case .containerSymLinkDestPath(let entryName): return "Unable to get destination path for symbolic link \(entryName)." case .containerHardLinkDestPath(let entryName): From 8847fe92ce2c073e7aeb005061da9f42a69993fa Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 13:29:12 +0800 Subject: [PATCH 055/106] [swcomp] SaveFile.load now auto-converts from old format --- Sources/swcomp/Benchmarks/SaveFile.swift | 19 +++++++++++++++++-- Sources/swcomp/SwcompError.swift | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift index 8ee00e6f..4caaaa57 100644 --- a/Sources/swcomp/Benchmarks/SaveFile.swift +++ b/Sources/swcomp/Benchmarks/SaveFile.swift @@ -33,9 +33,24 @@ struct SaveFile: Codable { } static func load(from path: String) throws -> SaveFile { - let decoder = JSONDecoder() let data = try Data(contentsOf: URL(fileURLWithPath: path)) - return try decoder.decode(SaveFile.self, from: data) + guard let generalDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] + else { swcompExit(.benchmarkUnrecognizedSaveFile) } + if let formatVersion = generalDict["formatVersion"] { + guard let intFormatVersion = formatVersion as? Int + else { swcompExit(.benchmarkUnrecognizedFormatVersion) } + guard intFormatVersion == 2 + else { swcompExit(.benchmarkUnsupportedFormatVersion(intFormatVersion)) } + let decoder = JSONDecoder() + return try decoder.decode(SaveFile.self, from: data) + } else if generalDict["metadatas"] != nil && generalDict["runs"] != nil { + let decoder = JSONDecoder() + let oldSaveFile = try decoder.decode(OldSaveFile.self, from: data) + print("WARNING: Old save file format detected. Its support will be removed in the future. Use \'benchmark convert' to upgrade.") + return SaveFile(oldSaveFile) + } else { + swcompExit(.benchmarkUnrecognizedSaveFile) + } } } diff --git a/Sources/swcomp/SwcompError.swift b/Sources/swcomp/SwcompError.swift index bf90ed94..33b740d6 100644 --- a/Sources/swcomp/SwcompError.swift +++ b/Sources/swcomp/SwcompError.swift @@ -21,6 +21,9 @@ enum SwcompError { case benchmarkCannotAppendToDirectory case benchmarkBadUUID case benchmarkNoUUID + case benchmarkUnrecognizedSaveFile + case benchmarkUnrecognizedFormatVersion + case benchmarkUnsupportedFormatVersion(Int) case benchmarkOldFormatNoUUIDMetadata(UUID) case containerSymLinkDestPath(String) case containerHardLinkDestPath(String) @@ -61,8 +64,14 @@ enum SwcompError { return 208 case .benchmarkNoUUID: return 218 - case .benchmarkOldFormatNoUUIDMetadata: + case .benchmarkUnrecognizedSaveFile: return 209 + case .benchmarkUnrecognizedFormatVersion: + return 219 + case .benchmarkUnsupportedFormatVersion: + return 229 + case .benchmarkOldFormatNoUUIDMetadata: + return 239 case .containerSymLinkDestPath: return 301 case .containerHardLinkDestPath: @@ -112,8 +121,14 @@ enum SwcompError { return "Specified run UUID is not well-formed." case .benchmarkNoUUID: return "Specified run UUID is not found in the file." + case .benchmarkUnrecognizedSaveFile: + return "The save file format is not recognized." + case .benchmarkUnrecognizedFormatVersion: + return "The save file format version is not recognized." + case .benchmarkUnsupportedFormatVersion(let formatVersion): + return "The save file format version \(formatVersion) is not supported." case .benchmarkOldFormatNoUUIDMetadata(let uuid): - return "No metadata found in the old format save file for UUID = \(uuid)." + return "No metadata found in an old format save file for UUID = \(uuid)." case .containerSymLinkDestPath(let entryName): return "Unable to get destination path for symbolic link \(entryName)." case .containerHardLinkDestPath(let entryName): From 515c11fd0e85eee89695680f448aa41a8e6428bb Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 13:29:41 +0800 Subject: [PATCH 056/106] [swcomp] Support new format in benchmark remove-run command --- .../swcomp/Benchmarks/RemoveRunCommand.swift | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift index 779614e7..c2d89e04 100644 --- a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift +++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift @@ -16,19 +16,18 @@ final class RemoveRunCommand: Command { @Param var path: String func execute() throws { - var saveFile = try OldSaveFile.load(from: self.path) + var saveFile = try SaveFile.load(from: self.path) guard let uuid = UUID(uuidString: self.runUUID) else { swcompExit(.benchmarkBadUUID) } - guard saveFile.metadatas.contains(where: { $0.key == uuid} ) - else { swcompExit(.benchmarkNoUUID) } - - saveFile.metadatas.removeValue(forKey: uuid) - saveFile.runs.removeAll(where: { $0.metadataUUID == uuid }) - - let encoder = JSONEncoder() - encoder.outputFormatting = [.prettyPrinted, .sortedKeys] - let data = try encoder.encode(saveFile) - try data.write(to: URL(fileURLWithPath: path)) + if saveFile.runs.contains(where: { $0.uuid == uuid} ) { + saveFile.runs.removeAll(where: { $0.uuid == uuid }) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(saveFile) + try data.write(to: URL(fileURLWithPath: path)) + } else { + print("WARNING: Specified run UUID is not found in the file. No changes made.") + } } } From 52961ea60dfc695e1983ecf6a749fe7016629fed Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 14:46:55 +0800 Subject: [PATCH 057/106] [swcomp] Update benchmark show command to use new format This allowed to rewrite implementation in a much clearer way. One user-visible change, however, is that NEW/BASE is now always printed with an index even if there is only one (implementation complexity to accommodate empty index is not worth it). --- Sources/swcomp/Benchmarks/SaveFile.swift | 11 ++ .../Benchmarks/ShowBenchmarkCommand.swift | 125 +++++++----------- 2 files changed, 56 insertions(+), 80 deletions(-) diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift index 4caaaa57..3353c7d6 100644 --- a/Sources/swcomp/Benchmarks/SaveFile.swift +++ b/Sources/swcomp/Benchmarks/SaveFile.swift @@ -53,4 +53,15 @@ struct SaveFile: Codable { } } + static func groupResults(runs: [SaveFile.Run]) -> [String: [(Int, BenchmarkResult)]] { + var groupedResults = [String: [(Int, BenchmarkResult)]]() + for (index, run) in runs.enumerated() { + for result in run.results { + let resultId = result.id + groupedResults[resultId] = (groupedResults[resultId] ?? Array()) + [(index, result)] + } + } + return groupedResults + } + } diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift index ac78413c..473e2677 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -30,114 +30,79 @@ final class ShowBenchmarkCommand: Command { } func execute() throws { - let newSaveFile = try OldSaveFile.load(from: self.path) - var newMetadatas: [UUID: String] + let newSaveFile = try SaveFile.load(from: self.path) + var newRuns: [SaveFile.Run] if let newUUIDString = self.selfCompare { guard let newUUID = UUID(uuidString: newUUIDString) else { swcompExit(.benchmarkBadUUID) } - guard newSaveFile.metadatas.contains(where: { $0.key == newUUID} ) + guard let newRun = newSaveFile.runs.first(where: { $0.uuid == newUUID} ) else { swcompExit(.benchmarkNoUUID) } - newMetadatas = [newUUID: ""] + newRuns = [newRun] } else { - newMetadatas = Dictionary(uniqueKeysWithValues: zip(newSaveFile.metadatas.keys, (1...newSaveFile.metadatas.count).map { "(\($0))" })) - if newMetadatas.count == 1 { - newMetadatas[newMetadatas.first!.key] = "" - } + newRuns = newSaveFile.runs } + self.printMetadatas(runs: newRuns, name: "NEW") - for (metadataUUID, index) in newMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { - print("NEW\(index) Metadata") - print("---------------") - if self.printUuid { - print("UUID: \(metadataUUID)") - } - newSaveFile.metadatas[metadataUUID]!.print() - } - - var newResults = [String: [(BenchmarkResult, UUID)]]() - for newRun in newSaveFile.runs.filter ( { (run: OldSaveFile.Run) in newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { - newResults.merge(Dictionary(grouping: newRun.results.map { ($0, newRun.metadataUUID) }, by: { $0.0.id }), - uniquingKeysWith: { $0 + $1 }) - } - - var baseResults = [String: [(BenchmarkResult, UUID)]]() - var baseMetadatas = [UUID: String]() + var baseRuns = [SaveFile.Run]() if let comparePath = comparePath { - let baseSaveFile = try OldSaveFile.load(from: comparePath) - - baseMetadatas = Dictionary(uniqueKeysWithValues: zip(baseSaveFile.metadatas.keys, (1...baseSaveFile.metadatas.count).map { "(\($0))" })) - if baseMetadatas.count == 1 { - baseMetadatas[baseMetadatas.first!.key] = "" - } - for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { - print("BASE\(index) Metadata") - print("----------------") - if self.printUuid { - print("UUID: \(metadataUUID)") - } - baseSaveFile.metadatas[metadataUUID]!.print() - } - - for baseRun in baseSaveFile.runs { - baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }), - uniquingKeysWith: { $0 + $1 }) - } + let baseSaveFile = try SaveFile.load(from: comparePath) + baseRuns = baseSaveFile.runs } else if let newUUIDString = self.selfCompare { - guard let newUUID = UUID(uuidString: newUUIDString) - else { swcompExit(.benchmarkBadUUID) } - guard newSaveFile.metadatas.contains(where: { $0.key == newUUID} ) - else { swcompExit(.benchmarkNoUUID) } - baseMetadatas = Dictionary(uniqueKeysWithValues: zip(newSaveFile.metadatas.keys.filter({ $0 != newUUID }), (1...(newSaveFile.metadatas.count - 1)).map { "(\($0))" })) - if baseMetadatas.count == 1 { - baseMetadatas[baseMetadatas.first!.key] = "" - } - for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { - print("BASE\(index) Metadata") - print("----------------") - if self.printUuid { - print("UUID: \(metadataUUID)") - } - newSaveFile.metadatas[metadataUUID]!.print() - } - - for baseRun in newSaveFile.runs.filter ( { (run: OldSaveFile.Run) in !newMetadatas.keys.contains(where: { $0 == run.metadataUUID }) }) { - baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }), - uniquingKeysWith: { $0 + $1 }) - } + // No need to double-check validity of input UUID. It was done already when loading the "new" run. + let newUUID = UUID(uuidString: newUUIDString)! + baseRuns = newSaveFile.runs.filter({ $0.uuid != newUUID }) } + self.printMetadatas(runs: baseRuns, name: "BASE") - if self.metadataOnly { - return - } + guard !self.metadataOnly + else { return } - for resultId in newResults.keys.sorted() { - let results = newResults[resultId]! + // There might be results for the same benchmark-input-iterCount tuple in different runs. We want to print those + // results together, so we have to group them based on their `id`. + let newResults = SaveFile.groupResults(runs: newRuns) + let baseResults = SaveFile.groupResults(runs: baseRuns) + + // Even though we try to sort results before writing a save file, after grouping they can appear in `newResults` + // in essentially arbitrary order. + for id in newResults.keys.sorted() { + let results = newResults[id]! print() print("----------------") print() - print("\(results[0].0.name) => \(results[0].0.input), iterations = \(results[0].0.iterCount)") + print("\(results.first!.1.name) => \(results.first!.1.input), iterations = \(results.first!.1.iterCount)") print() - for (result, metadataUUID) in results.sorted(by: { Int(newMetadatas[$0.1]!.dropFirst().dropLast())! < Int(newMetadatas[$1.1]!.dropFirst().dropLast())! }) { + for (index, result) in results { let benchmark = Benchmarks(rawValue: result.name)?.initialized(result.input) if let warmup = result.warmup { - print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std)), warmup = \(benchmark.format(warmup))") + print("NEW(\(index + 1)): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std)), warmup = \(benchmark.format(warmup))") } else { - print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))") + print("NEW(\(index + 1)): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))") } - if let baseResults = baseResults[resultId] { - for (other, baseUUID) in baseResults.sorted(by: { Int(baseMetadatas[$0.1]!.dropFirst().dropLast())! < Int(baseMetadatas[$1.1]!.dropFirst().dropLast())! }) { - if let otherWarmup = other.warmup { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std)), warmup = \(benchmark.format(otherWarmup))") + if let baseResults = baseResults[id] { + for (baseIndex, baseResult) in baseResults { + if let baseWarmup = baseResult.warmup { + print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std)), warmup = \(benchmark.format(baseWarmup))") } else { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") + print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std))") } - result.printComparison(with: other) + result.printComparison(with: baseResult) } } } } } + private func printMetadatas(runs: [SaveFile.Run], name: String) { + for (index, run) in runs.enumerated() { + print("\(name)(\(index + 1)) Metadata") + print("---------------") + if self.printUuid { + print("UUID: \(run.uuid)") + } + run.metadata.print() + } + } + } fileprivate extension Optional where Wrapped == Benchmark { From d76351caa0a8552bd72558da538edc23a1eba7a4 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 12 Feb 2026 21:40:05 +0800 Subject: [PATCH 058/106] [swcomp] Update benchmark run command to use new format --- Sources/swcomp/Benchmarks/OldSaveFile.swift | 1 - .../Benchmarks/RunBenchmarkCommand.swift | 61 +++++++++---------- Sources/swcomp/Benchmarks/SaveFile.swift | 4 ++ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Sources/swcomp/Benchmarks/OldSaveFile.swift b/Sources/swcomp/Benchmarks/OldSaveFile.swift index 61610f01..dbaba7a3 100644 --- a/Sources/swcomp/Benchmarks/OldSaveFile.swift +++ b/Sources/swcomp/Benchmarks/OldSaveFile.swift @@ -15,7 +15,6 @@ struct OldSaveFile: Codable { } var metadatas: [UUID: BenchmarkMetadata] - var runs: [Run] static func load(from path: String) throws -> OldSaveFile { diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index 8b9844fd..ffa16220 100644 --- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift @@ -44,26 +44,13 @@ final class RunBenchmarkCommand: Command { guard self.iterationCount == nil || self.iterationCount! >= 1 else { swcompExit(.benchmarkSmallIterCount) } - var baseResults = [String: [(BenchmarkResult, UUID)]]() - var baseMetadatas = [UUID: String]() + var baseRuns = [SaveFile.Run]() if let comparePath = comparePath { - let baseSaveFile = try OldSaveFile.load(from: comparePath) - - baseMetadatas = Dictionary(uniqueKeysWithValues: zip(baseSaveFile.metadatas.keys, (1...baseSaveFile.metadatas.count).map { "(\($0))" })) - if baseMetadatas.count == 1 { - baseMetadatas[baseMetadatas.first!.key] = "" - } - for (metadataUUID, index) in baseMetadatas.sorted(by: { Int($0.value.dropFirst().dropLast())! < Int($1.value.dropFirst().dropLast())! }) { - print("BASE\(index) Metadata") - print("----------------") - baseSaveFile.metadatas[metadataUUID]!.print() - } - - for baseRun in baseSaveFile.runs { - baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }), - uniquingKeysWith: { $0 + $1 }) - } + let baseSaveFile = try SaveFile.load(from: comparePath) + baseRuns = baseSaveFile.runs } + self.printBaseMetadatas(runs: baseRuns) + let baseResults = SaveFile.groupResults(runs: baseRuns) let title = "\(self.selectedBenchmark.titleName) Benchmark\n" print(String(repeating: "=", count: title.count)) @@ -115,13 +102,13 @@ final class RunBenchmarkCommand: Command { if let baseResults = baseResults[result.id] { print("\nNEW: average = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))") - for (other, baseUUID) in baseResults.sorted(by: { Int(baseMetadatas[$0.1]!.dropFirst().dropLast())! < Int(baseMetadatas[$1.1]!.dropFirst().dropLast())! }) { - if let otherWarmup = other.warmup { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std)), warmup = \(benchmark.format(otherWarmup))") + for (baseIndex, baseResult) in baseResults { + if let baseWarmup = baseResult.warmup { + print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std)), warmup = \(benchmark.format(baseWarmup))") } else { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") + print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std))") } - result.printComparison(with: other) + result.printComparison(with: baseResult) } } else { print("\nAverage = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))") @@ -133,7 +120,7 @@ final class RunBenchmarkCommand: Command { if let savePath = self.savePath { let metadata = try BenchmarkMetadata(self.description, self.preserveTimestamp) - var saveFile: OldSaveFile + var saveFile: SaveFile var isDir = ObjCBool(false) let saveFileExists = FileManager.default.fileExists(atPath: savePath, isDirectory: &isDir) @@ -142,20 +129,22 @@ final class RunBenchmarkCommand: Command { if isDir.boolValue { swcompExit(.benchmarkCannotAppendToDirectory) } - saveFile = try OldSaveFile.load(from: savePath) - var uuid: UUID - if let foundUUID = saveFile.metadatas.first(where: { $0.value == metadata })?.key { - uuid = foundUUID + saveFile = try SaveFile.load(from: savePath) + if let foundRunIndex = saveFile.runs.firstIndex(where: { $0.metadata == metadata }) { + var foundRun = saveFile.runs[foundRunIndex] + foundRun.results.append(contentsOf: newResults) + foundRun.results.sort(by: { $0.id < $1.id }) + saveFile.runs[foundRunIndex] = foundRun } else { + var uuid: UUID repeat { uuid = UUID() - } while saveFile.metadatas[uuid] != nil - saveFile.metadatas[uuid] = metadata + } while saveFile.runs.contains(where: { $0.uuid == uuid }) + saveFile.runs.append(SaveFile.Run(uuid: uuid, metadata: metadata, results: newResults.sorted(by: { $0.id < $1.id }))) } - saveFile.runs.append(OldSaveFile.Run(metadataUUID: uuid, results: newResults)) } else { let uuid = UUID() - saveFile = OldSaveFile(metadatas: [uuid: metadata], runs: [OldSaveFile.Run(metadataUUID: uuid, results: newResults)]) + saveFile = SaveFile(runs: [SaveFile.Run(uuid: uuid, metadata: metadata, results: newResults.sorted(by: { $0.id < $1.id }))]) } let encoder = JSONEncoder() @@ -165,4 +154,12 @@ final class RunBenchmarkCommand: Command { } } + private func printBaseMetadatas(runs: [SaveFile.Run]) { + for (index, run) in runs.enumerated() { + print("BASE(\(index + 1)) Metadata") + print("---------------") + run.metadata.print() + } + } + } diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift index 3353c7d6..2226d052 100644 --- a/Sources/swcomp/Benchmarks/SaveFile.swift +++ b/Sources/swcomp/Benchmarks/SaveFile.swift @@ -32,6 +32,10 @@ struct SaveFile: Codable { } } + init(runs: [SaveFile.Run]) { + self.runs = runs + } + static func load(from path: String) throws -> SaveFile { let data = try Data(contentsOf: URL(fileURLWithPath: path)) guard let generalDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] From f5b8a393ced262ad4311c6c2d90a42a655b67d28 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Fri, 13 Feb 2026 13:23:03 +0800 Subject: [PATCH 059/106] Cleanup gitignore --- .gitignore | 51 +++------------------------------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index edd94d5c..4d21141d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore -## Custom -Tests/temp - ## Build generated build/ DerivedData/ @@ -26,58 +23,16 @@ xcuserdata/ *.xccheckout *.xcscmblueprint -## Obj-C/Swift specific -*.hmap -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ -.build/ Package.pins - -# CocoaPods -Pods/ - -# Carthage -# Avoid checking in source code from Carthage dependencies. -Carthage/Checkouts -Carthage/Build -Cartfile.resolved - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output - -# Symbolic link to swcomp executable built by SPM -/swcomp - -# Package.resolved, because we don't want to fix dependencies' versions Package.resolved +.build/ -# Docs generated by SourceKitten +# Ignore docs generated by SourceKitten docs.json -# Docs generated by Jazzy +# Ignore docs generated by Jazzy docs/ - -# Vscode launch.json generated by Swift extension -.vscode/launch.json - -# API baselines generate by swift package diagnose-api-breaking-changes -api_baseline/ From ce1736a2b0e07e58c88ddf7901a8a0481b327eca Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 00:10:29 +0800 Subject: [PATCH 060/106] [Huffman] Add HuffmanCodes typealias for clarity --- Sources/BZip2/BZip2.swift | 2 +- Sources/Common/CodingTree/Code.swift | 4 +++- Sources/Common/CodingTree/DecodingTree.swift | 6 +++--- Sources/Deflate/Deflate+Compress.swift | 4 ++-- Sources/Deflate/Deflate+Constants.swift | 12 ++++++------ Sources/Deflate/Deflate.swift | 11 +++++------ 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index cf921553..8d991fe6 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -147,7 +147,7 @@ public class BZip2: DecompressionAlgorithm { } } let codes = Code.huffmanCodes(from: lengths) - let table = DecodingTree(codes: codes.codes, maxBits: codes.maxBits, bitReader) + let table = DecodingTree(codes, bitReader) tables.append(table) } diff --git a/Sources/Common/CodingTree/Code.swift b/Sources/Common/CodingTree/Code.swift index fcbe7999..f580cf9b 100644 --- a/Sources/Common/CodingTree/Code.swift +++ b/Sources/Common/CodingTree/Code.swift @@ -5,6 +5,8 @@ import Foundation +typealias HuffmanCodes = (codes: [Code], maxBits: Int) + struct Code { /// Number of bits used for `code`. @@ -13,7 +15,7 @@ struct Code { let symbol: Int /// `lengths` don't have to be sorted, but there must not be any 0 code lengths. - static func huffmanCodes(from lengths: [CodeLength]) -> (codes: [Code], maxBits: Int) { + static func huffmanCodes(from lengths: [CodeLength]) -> HuffmanCodes { // Sort `lengths` array to calculate canonical Huffman code. let sortedLengths = lengths.sorted() diff --git a/Sources/Common/CodingTree/DecodingTree.swift b/Sources/Common/CodingTree/DecodingTree.swift index 8149f3e9..65e1e615 100644 --- a/Sources/Common/CodingTree/DecodingTree.swift +++ b/Sources/Common/CodingTree/DecodingTree.swift @@ -13,14 +13,14 @@ final class DecodingTree { private let tree: [Int] private let leafCount: Int - init(codes: [Code], maxBits: Int, _ bitReader: BitReader) { + init(_ huffmanCodes: HuffmanCodes, _ bitReader: BitReader) { self.bitReader = bitReader // Calculate maximum amount of leaves in a tree. - self.leafCount = 1 << (maxBits + 1) + self.leafCount = 1 << (huffmanCodes.maxBits + 1) var tree = Array(repeating: -1, count: leafCount) - for code in codes { + for code in huffmanCodes.codes { // Put code in its place in the tree. var treeCode = code.code var index = 0 diff --git a/Sources/Deflate/Deflate+Compress.swift b/Sources/Deflate/Deflate+Compress.swift index 1bb39043..b665d115 100644 --- a/Sources/Deflate/Deflate+Compress.swift +++ b/Sources/Deflate/Deflate+Compress.swift @@ -106,9 +106,9 @@ extension Deflate: CompressionAlgorithm { // Constructing Huffman trees for the case of block with preset alphabets. // In this case codes for literals and distances are fixed. /// Huffman tree for literal and length symbols/codes. - let mainLiterals = EncodingTree(codes: Constants.staticHuffmanLiteralCodes, bitWriter, reverseCodes: true) + let mainLiterals = EncodingTree(codes: Constants.staticHuffmanLiteralCodes.codes, bitWriter, reverseCodes: true) /// Huffman tree for backward distance symbols/codes. - let mainDistances = EncodingTree(codes: Constants.staticHuffmanDistanceCodes, bitWriter, reverseCodes: true) + let mainDistances = EncodingTree(codes: Constants.staticHuffmanDistanceCodes.codes, bitWriter, reverseCodes: true) for code in bldCodes { switch code { diff --git a/Sources/Deflate/Deflate+Constants.swift b/Sources/Deflate/Deflate+Constants.swift index ca7a2cea..eb30d3f3 100644 --- a/Sources/Deflate/Deflate+Constants.swift +++ b/Sources/Deflate/Deflate+Constants.swift @@ -10,8 +10,8 @@ extension Deflate { struct Constants { // Precomputed codes for the static Huffman literal and distance trees. - static let staticHuffmanLiteralCodes = - [Code(bits: 7, code: 0, symbol: 256), Code(bits: 7, code: 64, symbol: 257), + static let staticHuffmanLiteralCodes: HuffmanCodes = + ([Code(bits: 7, code: 0, symbol: 256), Code(bits: 7, code: 64, symbol: 257), Code(bits: 7, code: 32, symbol: 258), Code(bits: 7, code: 96, symbol: 259), Code(bits: 7, code: 16, symbol: 260), Code(bits: 7, code: 80, symbol: 261), Code(bits: 7, code: 48, symbol: 262), Code(bits: 7, code: 112, symbol: 263), @@ -154,10 +154,10 @@ extension Deflate { Code(bits: 9, code: 63, symbol: 248), Code(bits: 9, code: 319, symbol: 249), Code(bits: 9, code: 191, symbol: 250), Code(bits: 9, code: 447, symbol: 251), Code(bits: 9, code: 127, symbol: 252), Code(bits: 9, code: 383, symbol: 253), - Code(bits: 9, code: 255, symbol: 254), Code(bits: 9, code: 511, symbol: 255)] + Code(bits: 9, code: 255, symbol: 254), Code(bits: 9, code: 511, symbol: 255)], 9) - static let staticHuffmanDistanceCodes = - [Code(bits: 5, code: 0, symbol: 0), Code(bits: 5, code: 16, symbol: 1), + static let staticHuffmanDistanceCodes: HuffmanCodes = + ([Code(bits: 5, code: 0, symbol: 0), Code(bits: 5, code: 16, symbol: 1), Code(bits: 5, code: 8, symbol: 2), Code(bits: 5, code: 24, symbol: 3), Code(bits: 5, code: 4, symbol: 4), Code(bits: 5, code: 20, symbol: 5), Code(bits: 5, code: 12, symbol: 6), Code(bits: 5, code: 28, symbol: 7), @@ -172,7 +172,7 @@ extension Deflate { Code(bits: 5, code: 3, symbol: 24), Code(bits: 5, code: 19, symbol: 25), Code(bits: 5, code: 11, symbol: 26), Code(bits: 5, code: 27, symbol: 27), Code(bits: 5, code: 7, symbol: 28), Code(bits: 5, code: 23, symbol: 29), - Code(bits: 5, code: 15, symbol: 30), Code(bits: 5, code: 31, symbol: 31)] + Code(bits: 5, code: 15, symbol: 30), Code(bits: 5, code: 31, symbol: 31)], 5) static let codeLengthOrders: [Int] = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15] diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index c49a3504..60c2970a 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -77,8 +77,8 @@ public class Deflate: DecompressionAlgorithm { if blockType == 1 { // Static Huffman // In this case codes for literals and distances are fixed. // Initialize trees from bootstraps. - mainLiterals = DecodingTree(codes: Constants.staticHuffmanLiteralCodes, maxBits: 9, bitReader) - mainDistances = DecodingTree(codes: Constants.staticHuffmanDistanceCodes, maxBits: 5, bitReader) + mainLiterals = DecodingTree(Constants.staticHuffmanLiteralCodes, bitReader) + mainDistances = DecodingTree(Constants.staticHuffmanDistanceCodes, bitReader) } else { // Dynamic Huffman // In this case there are Huffman codes for two alphabets in data right after block header. // Each code defined by a sequence of code lengths (which are compressed themselves with Huffman). @@ -102,8 +102,7 @@ public class Deflate: DecompressionAlgorithm { } let dynamicCodes = Code.huffmanCodes(from: Deflate.lengths(from: orderedCodeLengths)) /// Huffman tree for code lengths. Each code in the main alphabets is coded with this tree. - let dynamicCodeTree = DecodingTree(codes: dynamicCodes.codes, maxBits: dynamicCodes.maxBits, - bitReader) + let dynamicCodeTree = DecodingTree(dynamicCodes, bitReader) // Now we need to read codes (code lengths) for two main alphabets (trees). var codeLengths: [Int] = [] @@ -154,10 +153,10 @@ public class Deflate: DecompressionAlgorithm { // We have read codeLengths for both trees at once. // Now we need to split them and make corresponding trees. let literalCodes = Code.huffmanCodes(from: Deflate.lengths(from: Array(codeLengths[0.. Date: Wed, 18 Feb 2026 00:12:55 +0800 Subject: [PATCH 061/106] [Huffman] Remove codes argument label from EncodingTree.init --- Sources/BZip2/BZip2+Compress.swift | 4 ++-- Sources/Common/CodingTree/EncodingTree.swift | 2 +- Sources/Deflate/Deflate+Compress.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index 20d117bf..ba016be6 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -122,9 +122,9 @@ extension BZip2: CompressionAlgorithm { // Otherwise, let's create a new table and check if it gives us better results. // First, we calculate code lengths and codes for our current stats. let lengths = BZip2.lengths(from: stats) - let codes = Code.huffmanCodes(from: lengths) + let codes = Code.huffmanCodes(from: lengths).codes // Then, using these codes, we create a new Huffman tree. - let table = EncodingTree(codes: codes.codes, bitWriter) + let table = EncodingTree(codes, bitWriter) if table.bitSize(for: stats) < minimumSize { tables.append(table) tablesLengths.append(lengths.sorted { $0.symbol < $1.symbol }.map { $0.codeLength }) diff --git a/Sources/Common/CodingTree/EncodingTree.swift b/Sources/Common/CodingTree/EncodingTree.swift index 9c5125d4..a68e991b 100644 --- a/Sources/Common/CodingTree/EncodingTree.swift +++ b/Sources/Common/CodingTree/EncodingTree.swift @@ -19,7 +19,7 @@ final class EncodingTree { private let codingIndices: [CodingIndex] - init(codes: [Code], _ bitWriter: BitWriter, reverseCodes: Bool = false) { + init(_ codes: [Code], _ bitWriter: BitWriter, reverseCodes: Bool = false) { self.bitWriter = bitWriter var codingIndices = Array(repeating: CodingIndex(treeCode: -1, bitSize: -1), count: codes.count) diff --git a/Sources/Deflate/Deflate+Compress.swift b/Sources/Deflate/Deflate+Compress.swift index b665d115..e434ed57 100644 --- a/Sources/Deflate/Deflate+Compress.swift +++ b/Sources/Deflate/Deflate+Compress.swift @@ -106,9 +106,9 @@ extension Deflate: CompressionAlgorithm { // Constructing Huffman trees for the case of block with preset alphabets. // In this case codes for literals and distances are fixed. /// Huffman tree for literal and length symbols/codes. - let mainLiterals = EncodingTree(codes: Constants.staticHuffmanLiteralCodes.codes, bitWriter, reverseCodes: true) + let mainLiterals = EncodingTree(Constants.staticHuffmanLiteralCodes.codes, bitWriter, reverseCodes: true) /// Huffman tree for backward distance symbols/codes. - let mainDistances = EncodingTree(codes: Constants.staticHuffmanDistanceCodes.codes, bitWriter, reverseCodes: true) + let mainDistances = EncodingTree(Constants.staticHuffmanDistanceCodes.codes, bitWriter, reverseCodes: true) for code in bldCodes { switch code { From 273b6ed9e92929082090df8d57a9cbd339ed896d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 12:21:57 +0800 Subject: [PATCH 062/106] Remove redundant imports --- Sources/7-Zip/7zCoder+Equatable.swift | 2 -- Sources/7-Zip/7zCoder.swift | 1 - Sources/7-Zip/7zCoderInfo.swift | 1 - Sources/7-Zip/7zError.swift | 2 -- Sources/7-Zip/7zPackInfo.swift | 1 - Sources/7-Zip/7zProperty.swift | 1 - Sources/7-Zip/7zStreamInfo.swift | 1 - Sources/7-Zip/7zSubstreamInfo.swift | 1 - Sources/7-Zip/CompressionMethod+7z.swift | 2 -- Sources/7-Zip/MsbBitReader+7z.swift | 1 - Sources/BZip2/BZip2+BlockSize.swift | 2 -- Sources/BZip2/BZip2+Lengths.swift | 2 -- Sources/BZip2/BurrowsWheeler.swift | 2 -- Sources/BZip2/SuffixArray.swift | 2 -- Sources/Common/CodingTree/Code.swift | 2 -- Sources/Common/CodingTree/CodeLength.swift | 2 -- Sources/Common/CodingTree/DecodingTree.swift | 1 - Sources/Common/CodingTree/EncodingTree.swift | 1 - Sources/Common/CompressionMethod.swift | 2 -- Sources/Common/FileSystemType.swift | 2 -- Sources/Deflate/Deflate+Constants.swift | 2 -- Sources/Deflate/Deflate+Lengths.swift | 2 -- Sources/Deflate/DeflateError.swift | 2 -- Sources/GZip/FileSystemType+Gzip.swift | 2 -- Sources/GZip/GzipError.swift | 2 -- Sources/LZ4/LZ4+Compress.swift | 1 - Sources/LZMA/LZMABitTreeDecoder.swift | 2 -- Sources/LZMA/LZMAConstants.swift | 2 -- Sources/LZMA/LZMADecoder.swift | 1 - Sources/LZMA/LZMAError.swift | 2 -- Sources/LZMA/LZMALenDecoder.swift | 2 -- Sources/LZMA2/LZMA2Decoder.swift | 1 - Sources/LZMA2/LZMA2Error.swift | 2 -- Sources/TAR/ContainerEntryType+Tar.swift | 2 -- Sources/TAR/LittleEndianByteReader+Tar.swift | 1 - Sources/TAR/TarContainer.swift | 1 - Sources/TAR/TarError.swift | 2 -- Sources/XZ/LittleEndianByteReader+XZ.swift | 1 - Sources/XZ/XZStreamHeader.swift | 1 - Sources/ZIP/CompressionMethod+Zip.swift | 2 -- Sources/ZIP/FileSystemType+Zip.swift | 2 -- Sources/ZIP/ZipCentralDirectoryEntry.swift | 1 - Sources/ZIP/ZipEndOfCentralDirectory.swift | 1 - Sources/ZIP/ZipEntryInfoHelper.swift | 1 - Sources/ZIP/ZipError.swift | 2 -- Sources/ZIP/ZipLocalHeader.swift | 1 - 46 files changed, 72 deletions(-) diff --git a/Sources/7-Zip/7zCoder+Equatable.swift b/Sources/7-Zip/7zCoder+Equatable.swift index 96ed9f68..619dd0f0 100644 --- a/Sources/7-Zip/7zCoder+Equatable.swift +++ b/Sources/7-Zip/7zCoder+Equatable.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension SevenZipCoder: Equatable { static func == (lhs: SevenZipCoder, rhs: SevenZipCoder) -> Bool { diff --git a/Sources/7-Zip/7zCoder.swift b/Sources/7-Zip/7zCoder.swift index fc41c480..483eb068 100644 --- a/Sources/7-Zip/7zCoder.swift +++ b/Sources/7-Zip/7zCoder.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData class SevenZipCoder { diff --git a/Sources/7-Zip/7zCoderInfo.swift b/Sources/7-Zip/7zCoderInfo.swift index 98ad29c9..d160080a 100644 --- a/Sources/7-Zip/7zCoderInfo.swift +++ b/Sources/7-Zip/7zCoderInfo.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData class SevenZipCoderInfo { diff --git a/Sources/7-Zip/7zError.swift b/Sources/7-Zip/7zError.swift index 4b3492a0..29fce7b8 100644 --- a/Sources/7-Zip/7zError.swift +++ b/Sources/7-Zip/7zError.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened while processing a 7-Zip container. It may indicate that either container is damaged or it might not be 7-Zip container at all. diff --git a/Sources/7-Zip/7zPackInfo.swift b/Sources/7-Zip/7zPackInfo.swift index fe8bb595..6ccd01b5 100644 --- a/Sources/7-Zip/7zPackInfo.swift +++ b/Sources/7-Zip/7zPackInfo.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData class SevenZipPackInfo { diff --git a/Sources/7-Zip/7zProperty.swift b/Sources/7-Zip/7zProperty.swift index e5751078..c26aa9dc 100644 --- a/Sources/7-Zip/7zProperty.swift +++ b/Sources/7-Zip/7zProperty.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct SevenZipProperty { diff --git a/Sources/7-Zip/7zStreamInfo.swift b/Sources/7-Zip/7zStreamInfo.swift index b7e82a19..a978efd5 100644 --- a/Sources/7-Zip/7zStreamInfo.swift +++ b/Sources/7-Zip/7zStreamInfo.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData class SevenZipStreamInfo { diff --git a/Sources/7-Zip/7zSubstreamInfo.swift b/Sources/7-Zip/7zSubstreamInfo.swift index 442ea2b0..eed68239 100644 --- a/Sources/7-Zip/7zSubstreamInfo.swift +++ b/Sources/7-Zip/7zSubstreamInfo.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData class SevenZipSubstreamInfo { diff --git a/Sources/7-Zip/CompressionMethod+7z.swift b/Sources/7-Zip/CompressionMethod+7z.swift index f5168bc9..aeff7fa0 100644 --- a/Sources/7-Zip/CompressionMethod+7z.swift +++ b/Sources/7-Zip/CompressionMethod+7z.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension CompressionMethod { init(_ coderID: [UInt8]) { diff --git a/Sources/7-Zip/MsbBitReader+7z.swift b/Sources/7-Zip/MsbBitReader+7z.swift index 70044388..2a22679e 100644 --- a/Sources/7-Zip/MsbBitReader+7z.swift +++ b/Sources/7-Zip/MsbBitReader+7z.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData extension MsbBitReader { diff --git a/Sources/BZip2/BZip2+BlockSize.swift b/Sources/BZip2/BZip2+BlockSize.swift index 215edbc0..a477bbef 100644 --- a/Sources/BZip2/BZip2+BlockSize.swift +++ b/Sources/BZip2/BZip2+BlockSize.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension BZip2 { /// Represents the size of the blocks in which data is split during BZip2 compression. diff --git a/Sources/BZip2/BZip2+Lengths.swift b/Sources/BZip2/BZip2+Lengths.swift index 799507f8..4d52359e 100644 --- a/Sources/BZip2/BZip2+Lengths.swift +++ b/Sources/BZip2/BZip2+Lengths.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - // BZip2 specific function for generation of HuffmanLength array from stats. extension BZip2 { diff --git a/Sources/BZip2/BurrowsWheeler.swift b/Sources/BZip2/BurrowsWheeler.swift index 80cae086..a3029e4b 100644 --- a/Sources/BZip2/BurrowsWheeler.swift +++ b/Sources/BZip2/BurrowsWheeler.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - enum BurrowsWheeler { static func transform(bytes: [Int]) -> ([Int], Int) { diff --git a/Sources/BZip2/SuffixArray.swift b/Sources/BZip2/SuffixArray.swift index 6c7018d1..f1d22647 100644 --- a/Sources/BZip2/SuffixArray.swift +++ b/Sources/BZip2/SuffixArray.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /// Suffix Array by Induced Sorting enum SuffixArray { diff --git a/Sources/Common/CodingTree/Code.swift b/Sources/Common/CodingTree/Code.swift index f580cf9b..adaad351 100644 --- a/Sources/Common/CodingTree/Code.swift +++ b/Sources/Common/CodingTree/Code.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - typealias HuffmanCodes = (codes: [Code], maxBits: Int) struct Code { diff --git a/Sources/Common/CodingTree/CodeLength.swift b/Sources/Common/CodingTree/CodeLength.swift index 21bf5ff6..348c3b23 100644 --- a/Sources/Common/CodingTree/CodeLength.swift +++ b/Sources/Common/CodingTree/CodeLength.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - struct CodeLength: Equatable { let symbol: Int diff --git a/Sources/Common/CodingTree/DecodingTree.swift b/Sources/Common/CodingTree/DecodingTree.swift index 65e1e615..0f60ef09 100644 --- a/Sources/Common/CodingTree/DecodingTree.swift +++ b/Sources/Common/CodingTree/DecodingTree.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData final class DecodingTree { diff --git a/Sources/Common/CodingTree/EncodingTree.swift b/Sources/Common/CodingTree/EncodingTree.swift index a68e991b..ca53a27d 100644 --- a/Sources/Common/CodingTree/EncodingTree.swift +++ b/Sources/Common/CodingTree/EncodingTree.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData fileprivate struct CodingIndex { diff --git a/Sources/Common/CompressionMethod.swift b/Sources/Common/CompressionMethod.swift index cede6f79..10127da9 100644 --- a/Sources/Common/CompressionMethod.swift +++ b/Sources/Common/CompressionMethod.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /// Represents a (de)compression method. public enum CompressionMethod { /// BZip2. diff --git a/Sources/Common/FileSystemType.swift b/Sources/Common/FileSystemType.swift index 7763f5ce..de6de79c 100644 --- a/Sources/Common/FileSystemType.swift +++ b/Sources/Common/FileSystemType.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents the type of the file system on which an archive or container was created. File system determines the meaning of file attributes. diff --git a/Sources/Deflate/Deflate+Constants.swift b/Sources/Deflate/Deflate+Constants.swift index eb30d3f3..4d0176ee 100644 --- a/Sources/Deflate/Deflate+Constants.swift +++ b/Sources/Deflate/Deflate+Constants.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension Deflate { struct Constants { diff --git a/Sources/Deflate/Deflate+Lengths.swift b/Sources/Deflate/Deflate+Lengths.swift index 815d7f5d..dd2425f3 100644 --- a/Sources/Deflate/Deflate+Lengths.swift +++ b/Sources/Deflate/Deflate+Lengths.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - // Deflate specific functions for generation of HuffmanLength arrays from different inputs. extension Deflate { diff --git a/Sources/Deflate/DeflateError.swift b/Sources/Deflate/DeflateError.swift index 439e39aa..aa47df8a 100644 --- a/Sources/Deflate/DeflateError.swift +++ b/Sources/Deflate/DeflateError.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened during Deflate compression or decompression. It may indicate that either the data is damaged or it might not be compressed with Deflate at all. diff --git a/Sources/GZip/FileSystemType+Gzip.swift b/Sources/GZip/FileSystemType+Gzip.swift index d9a98280..4b8d8048 100644 --- a/Sources/GZip/FileSystemType+Gzip.swift +++ b/Sources/GZip/FileSystemType+Gzip.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension FileSystemType { init(_ gzipOS: UInt8) { diff --git a/Sources/GZip/GzipError.swift b/Sources/GZip/GzipError.swift index 2622dc4f..ab4fb129 100644 --- a/Sources/GZip/GzipError.swift +++ b/Sources/GZip/GzipError.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened while processing a GZip archive. It may indicate that either archive is damaged or it might not be GZip archive at all. diff --git a/Sources/LZ4/LZ4+Compress.swift b/Sources/LZ4/LZ4+Compress.swift index b0edcb4e..5ddd4ed0 100644 --- a/Sources/LZ4/LZ4+Compress.swift +++ b/Sources/LZ4/LZ4+Compress.swift @@ -4,7 +4,6 @@ // See LICENSE for license information import Foundation -import BitByteData extension LZ4: CompressionAlgorithm { diff --git a/Sources/LZMA/LZMABitTreeDecoder.swift b/Sources/LZMA/LZMABitTreeDecoder.swift index 65ac172a..5089dad1 100644 --- a/Sources/LZMA/LZMABitTreeDecoder.swift +++ b/Sources/LZMA/LZMABitTreeDecoder.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /// Used to decode symbols that need several bits for storing. struct LZMABitTreeDecoder { diff --git a/Sources/LZMA/LZMAConstants.swift b/Sources/LZMA/LZMAConstants.swift index e686c5a4..770219c3 100644 --- a/Sources/LZMA/LZMAConstants.swift +++ b/Sources/LZMA/LZMAConstants.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - enum LZMAConstants { static let topValue: UInt32 = 1 << 24 diff --git a/Sources/LZMA/LZMADecoder.swift b/Sources/LZMA/LZMADecoder.swift index 095c1e14..fa8fac9b 100644 --- a/Sources/LZMA/LZMADecoder.swift +++ b/Sources/LZMA/LZMADecoder.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct LZMADecoder { diff --git a/Sources/LZMA/LZMAError.swift b/Sources/LZMA/LZMAError.swift index 10175b84..6e40c5cf 100644 --- a/Sources/LZMA/LZMAError.swift +++ b/Sources/LZMA/LZMAError.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened during LZMA decompression. It may indicate that either data is damaged or it might not be compressed with LZMA at all. diff --git a/Sources/LZMA/LZMALenDecoder.swift b/Sources/LZMA/LZMALenDecoder.swift index 6c79a6ae..0ba1ec47 100644 --- a/Sources/LZMA/LZMALenDecoder.swift +++ b/Sources/LZMA/LZMALenDecoder.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - struct LZMALenDecoder { private var choice: Int = LZMAConstants.probInitValue diff --git a/Sources/LZMA2/LZMA2Decoder.swift b/Sources/LZMA2/LZMA2Decoder.swift index 416271c8..e6b08e78 100644 --- a/Sources/LZMA2/LZMA2Decoder.swift +++ b/Sources/LZMA2/LZMA2Decoder.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct LZMA2Decoder { diff --git a/Sources/LZMA2/LZMA2Error.swift b/Sources/LZMA2/LZMA2Error.swift index af0c3472..03fd9bf3 100644 --- a/Sources/LZMA2/LZMA2Error.swift +++ b/Sources/LZMA2/LZMA2Error.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened during LZMA2 decompression. It may indicate that either data is damaged or it might not be compressed with LZMA2 at all. diff --git a/Sources/TAR/ContainerEntryType+Tar.swift b/Sources/TAR/ContainerEntryType+Tar.swift index 3d1e5b56..788c13ce 100644 --- a/Sources/TAR/ContainerEntryType+Tar.swift +++ b/Sources/TAR/ContainerEntryType+Tar.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension ContainerEntryType { init(_ fileTypeIndicator: UInt8) { diff --git a/Sources/TAR/LittleEndianByteReader+Tar.swift b/Sources/TAR/LittleEndianByteReader+Tar.swift index 4ef2a45a..7b95959c 100644 --- a/Sources/TAR/LittleEndianByteReader+Tar.swift +++ b/Sources/TAR/LittleEndianByteReader+Tar.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData extension LittleEndianByteReader { diff --git a/Sources/TAR/TarContainer.swift b/Sources/TAR/TarContainer.swift index dc9ddaeb..61fcc593 100644 --- a/Sources/TAR/TarContainer.swift +++ b/Sources/TAR/TarContainer.swift @@ -4,7 +4,6 @@ // See LICENSE for license information import Foundation -import BitByteData /// Provides functions for work with TAR containers. public class TarContainer: Container { diff --git a/Sources/TAR/TarError.swift b/Sources/TAR/TarError.swift index 0dcce194..271d0afb 100644 --- a/Sources/TAR/TarError.swift +++ b/Sources/TAR/TarError.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened while processing a TAR container. It may indicate that either container is damaged or it might not be TAR container at all. diff --git a/Sources/XZ/LittleEndianByteReader+XZ.swift b/Sources/XZ/LittleEndianByteReader+XZ.swift index 94bd8890..5c135756 100644 --- a/Sources/XZ/LittleEndianByteReader+XZ.swift +++ b/Sources/XZ/LittleEndianByteReader+XZ.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData extension LittleEndianByteReader { diff --git a/Sources/XZ/XZStreamHeader.swift b/Sources/XZ/XZStreamHeader.swift index 333f78f7..32d8ca3b 100644 --- a/Sources/XZ/XZStreamHeader.swift +++ b/Sources/XZ/XZStreamHeader.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct XZStreamHeader { diff --git a/Sources/ZIP/CompressionMethod+Zip.swift b/Sources/ZIP/CompressionMethod+Zip.swift index c467859e..685a5477 100644 --- a/Sources/ZIP/CompressionMethod+Zip.swift +++ b/Sources/ZIP/CompressionMethod+Zip.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension CompressionMethod { init(_ compression: UInt16) { diff --git a/Sources/ZIP/FileSystemType+Zip.swift b/Sources/ZIP/FileSystemType+Zip.swift index 3ecb572f..62caadb7 100644 --- a/Sources/ZIP/FileSystemType+Zip.swift +++ b/Sources/ZIP/FileSystemType+Zip.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - extension FileSystemType { init(_ versionMadeBy: UInt16) { diff --git a/Sources/ZIP/ZipCentralDirectoryEntry.swift b/Sources/ZIP/ZipCentralDirectoryEntry.swift index b6ff0bd8..4333a038 100644 --- a/Sources/ZIP/ZipCentralDirectoryEntry.swift +++ b/Sources/ZIP/ZipCentralDirectoryEntry.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct ZipCentralDirectoryEntry { diff --git a/Sources/ZIP/ZipEndOfCentralDirectory.swift b/Sources/ZIP/ZipEndOfCentralDirectory.swift index 36da217e..f0d6efab 100644 --- a/Sources/ZIP/ZipEndOfCentralDirectory.swift +++ b/Sources/ZIP/ZipEndOfCentralDirectory.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct ZipEndOfCentralDirectory { diff --git a/Sources/ZIP/ZipEntryInfoHelper.swift b/Sources/ZIP/ZipEntryInfoHelper.swift index 4ebf00ea..2e1ce4e1 100644 --- a/Sources/ZIP/ZipEntryInfoHelper.swift +++ b/Sources/ZIP/ZipEntryInfoHelper.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData /** diff --git a/Sources/ZIP/ZipError.swift b/Sources/ZIP/ZipError.swift index b6610b2b..4d03b807 100644 --- a/Sources/ZIP/ZipError.swift +++ b/Sources/ZIP/ZipError.swift @@ -3,8 +3,6 @@ // // See LICENSE for license information -import Foundation - /** Represents an error which happened while processing a ZIP container. It may indicate that either container is damaged or it might not be ZIP container at all. diff --git a/Sources/ZIP/ZipLocalHeader.swift b/Sources/ZIP/ZipLocalHeader.swift index 37366f7d..8c420779 100644 --- a/Sources/ZIP/ZipLocalHeader.swift +++ b/Sources/ZIP/ZipLocalHeader.swift @@ -3,7 +3,6 @@ // // See LICENSE for license information -import Foundation import BitByteData struct ZipLocalHeader { From bff411d51172f84ff80f276588fb2872f692caa0 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 14:25:43 +0800 Subject: [PATCH 063/106] [BZip2] Improve encoding performance of code lengths deltas --- Sources/BZip2/BZip2+Compress.swift | 28 +++++++++++++++++----------- Sources/Common/Extensions.swift | 7 ++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index ba016be6..8293a7bd 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -190,19 +190,25 @@ extension BZip2: CompressionAlgorithm { // Delta bit lengths. for lengths in tablesLengths { // Starting length. - var currentLength = lengths[0] - bitWriter.write(number: currentLength, bitsCount: 5) + var prevLength = lengths[0] + bitWriter.write(number: prevLength, bitsCount: 5) for length in lengths { - while currentLength != length { - bitWriter.write(bit: 1) // Alter length. - if currentLength > length { - bitWriter.write(bit: 1) // Decrement length. - currentLength -= 1 - } else { - bitWriter.write(bit: 0) // Increment length. - currentLength += 1 - } + // In the worst case delta between two lengths is 19. This delta requires 19 * 2 = 38 bits to encode + // which fits into `UInt` (at least, on modern 64-bit systems), so we can use `write(unsignedNumber:)`. + if prevLength > length { + // Bits: 11 -> 11_11 -> 11_11_11 -> 11_11_11_11 -> ... + // Numbers: 3 -> 15 -> 63 -> 255 -> ... + // https://oeis.org/A024036 + let diff = prevLength - length + bitWriter.write(unsignedNumber: (1 << (2 * diff)) - 1, bitsCount: 2 * diff) + } else if prevLength < length { + // Bits: 10 -> 10_10 -> 10_10_10 -> 10_10_10_10 -> ... + // Numbers: 2 -> 10 -> 42 -> 170 -> ... + // https://oeis.org/A020988 + let diff = length - prevLength + bitWriter.write(unsignedNumber: ((1 << (2 * diff)) - 1) * 2 / 3 , bitsCount: 2 * diff) } + prevLength = length bitWriter.write(bit: 0) } } diff --git a/Sources/Common/Extensions.swift b/Sources/Common/Extensions.swift index ea8ba358..50493caf 100644 --- a/Sources/Common/Extensions.swift +++ b/Sources/Common/Extensions.swift @@ -32,10 +32,11 @@ extension Int { /// Returns an integer with reversed order of bits. func reversed(bits count: Int) -> Int { - var a = 1 << 0 - var b = 1 << (count - 1) + // Arithmetic operations amount: 3 + 8 * ceil(count / 2) + var a = 1 + var b = 1 << (count &- 1) var z = 0 - for i in Swift.stride(from: count - 1, to: -1, by: -2) { + for i in Swift.stride(from: count &- 1, to: -1, by: -2) { z |= (self >> i) & a z |= (self << i) & b a <<= 1 From 7af76764d75c40509e9d266438b98a8cc1c57da3 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 14:42:01 +0800 Subject: [PATCH 064/106] [BZip2] Use UInt8 arrays instead of Data --- Sources/BZip2/BZip2+Compress.swift | 22 +++++++++++----------- Sources/BZip2/BZip2.swift | 10 +++++----- Sources/Common/CheckSums.swift | 4 ++-- Sources/Common/Extensions.swift | 9 +++++++++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index 8293a7bd..b815a5b4 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -51,8 +51,8 @@ extension BZip2: CompressionAlgorithm { var totalCRC: UInt32 = 0 for i in stride(from: data.startIndex, to: data.endIndex, by: rawBlockSize) { - let blockData = data[i..> 31) totalCRC ^= blockCRC @@ -61,7 +61,7 @@ extension BZip2: CompressionAlgorithm { bitWriter.write(bits: blockMarker) // Block magic number. bitWriter.write(number: blockCRC.toInt(), bitsCount: 32) // Block crc32. - process(block: blockData, bitWriter) + process(block, bitWriter) } // EOS magic number. @@ -73,8 +73,8 @@ extension BZip2: CompressionAlgorithm { return bitWriter.data } - private static func process(block data: Data, _ bitWriter: MsbBitWriter) { - var out = initialRle(data) + private static func process(_ block: [UInt8], _ bitWriter: MsbBitWriter) { + var out = initialRle(block) var pointer = 0 (out, pointer) = BurrowsWheeler.transform(bytes: out) @@ -151,7 +151,7 @@ extension BZip2: CompressionAlgorithm { bitWriter.write(number: 0, bitsCount: 1) // "Randomized". bitWriter.write(number: pointer, bitsCount: 24) // Original pointer (from BWT). - var usedMap = Array(repeating: UInt8(0), count: 16) + var usedMap = Array(repeating: 0 as UInt8, count: 16) for usedByte in usedBytes { guard usedByte <= 255 else { fatalError("Incorrect used byte.") } @@ -233,16 +233,16 @@ extension BZip2: CompressionAlgorithm { } /// Initial Run Length Encoding. - private static func initialRle(_ data: Data) -> [Int] { + private static func initialRle(_ block: [UInt8]) -> [Int] { var out = [Int]() - var index = data.startIndex - while index < data.endIndex { + var index = block.startIndex + while index < block.endIndex { var runLength = 1 - while index + 1 < data.endIndex && data[index] == data[index + 1] && runLength < 255 { + while index + 1 < block.endIndex && block[index] == block[index + 1] && runLength < 255 { runLength += 1 index += 1 } - let byte = data[index].toInt() + let byte = block[index].toInt() for _ in 0..> 31) totalCRC ^= blockCRC32 @@ -69,7 +69,7 @@ public class BZip2: DecompressionAlgorithm { return out } - private static func decode(_ bitReader: MsbBitReader, _ blockSize: BlockSize) throws -> Data { + private static func decode(_ bitReader: MsbBitReader, _ blockSize: BlockSize) throws -> [UInt8] { let isRandomized = bitReader.bit() guard isRandomized == 0 else { throw BZip2Error.randomizedBlock } @@ -221,7 +221,7 @@ public class BZip2: DecompressionAlgorithm { } } - return Data(out) + return out } } diff --git a/Sources/Common/CheckSums.swift b/Sources/Common/CheckSums.swift index 047cbe7a..d61fdc98 100644 --- a/Sources/Common/CheckSums.swift +++ b/Sources/Common/CheckSums.swift @@ -27,9 +27,9 @@ enum CheckSums { return ~crc } - static func bzip2crc32(_ data: Data) -> UInt32 { + static func bzip2crc32(_ block: [UInt8]) -> UInt32 { var crc: UInt32 = 0xFFFFFFFF - for byte in data { + for byte in block { let index = UInt32(truncatingIfNeeded: byte) crc = (crc << 8) ^ CheckSums.bzip2CRC32table[((crc >> 24) ^ index).toInt()] } diff --git a/Sources/Common/Extensions.swift b/Sources/Common/Extensions.swift index 50493caf..28211283 100644 --- a/Sources/Common/Extensions.swift +++ b/Sources/Common/Extensions.swift @@ -5,6 +5,15 @@ import Foundation +extension Data { + + @inlinable @inline(__always) + func toByteArray() -> [UInt8] { + return self.withUnsafeBytes { $0.map { $0 } } + } + +} + extension UnsignedInteger { @inlinable @inline(__always) From 1e7154474b08d9a385a1ed2d64b8e8b8cc59d583 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 15:02:09 +0800 Subject: [PATCH 065/106] [BZip2] Use better versions of bit writer functions First, write(unsignedNumber:) is better than write(number:) when the argument is already UInt, since it does one fewer precondition check and does not do additional integer conversion. Secondly, using write(number:) to write one bit was very inefficient. --- Sources/BZip2/BZip2+Compress.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index b815a5b4..67a84d61 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -45,8 +45,8 @@ extension BZip2: CompressionAlgorithm { // In the worst case initial RLE causes expansion by a factor of 1.25, so 1000 / 1.25 = 800. let rawBlockSize = blockSize.sizeInKilobytes * 800 // BZip2 Header. - bitWriter.write(number: 0x425a, bitsCount: 16) // Magic number = 'BZ'. - bitWriter.write(number: 0x68, bitsCount: 8) // Version = 'h'. + bitWriter.write(unsignedNumber: 0x425a, bitsCount: 16) // Magic number = 'BZ'. + bitWriter.write(unsignedNumber: 0x68, bitsCount: 8) // Version = 'h'. bitWriter.write(number: blockSize.headerByte, bitsCount: 8) // Block size. var totalCRC: UInt32 = 0 @@ -148,7 +148,7 @@ extension BZip2: CompressionAlgorithm { // Now, we perform encoding itself. // But first, we need to finish block header. - bitWriter.write(number: 0, bitsCount: 1) // "Randomized". + bitWriter.write(bit: 0) // "Randomized". bitWriter.write(number: pointer, bitsCount: 24) // Original pointer (from BWT). var usedMap = Array(repeating: 0 as UInt8, count: 16) From 609eb29791ccc43228e4b824f766883dc78cb774 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 16:04:49 +0800 Subject: [PATCH 066/106] [BZip2] Change a couple of guard/if-checks to assertions --- Sources/BZip2/BZip2+Compress.swift | 36 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index 67a84d61..0b24c1e4 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -151,10 +151,16 @@ extension BZip2: CompressionAlgorithm { bitWriter.write(bit: 0) // "Randomized". bitWriter.write(number: pointer, bitsCount: 24) // Original pointer (from BWT). + // Encode which symbols (bytes) are used in the data. All possible 256 symbols (0...255) are split into "maps" + // of 16 consequent symbols. Each map is a sequence of 16 bits where a set bit indicates that a symbol is used. + // If a map would consist of only zero bits, then it is omitted. This is determined in the construction of `usedMap`. var usedMap = Array(repeating: 0 as UInt8, count: 16) for usedByte in usedBytes { - guard usedByte <= 255 - else { fatalError("Incorrect used byte.") } + // `usedBytes` is an output of the BW transform which does not change symbols used. The input of the BW + // transform is an output of initial RLE encoding. Since a run length value is 255 or less and the input + // data consists of normal bytes which also have values of 255 or less, `usedByte` cannot be larger than 255 + // by construction. + assert(usedByte <= 255, "Incorrect usedByte.") usedMap[usedByte / 16] = 1 } bitWriter.write(bits: usedMap) @@ -163,7 +169,7 @@ extension BZip2: CompressionAlgorithm { for i in 0..<16 { guard usedMap[i] == 1 else { continue } for j in 0..<16 { - if usedBytesIndex < usedBytes.count && i * 16 + j == usedBytes[usedBytesIndex] { + if usedBytesIndex < usedBytes.endIndex && i * 16 + j == usedBytes[usedBytesIndex] { bitWriter.write(bit: 1) usedBytesIndex += 1 } else { @@ -215,20 +221,20 @@ extension BZip2: CompressionAlgorithm { // Contents. var encoded = 0 - var selectorPointer = 0 - var t: EncodingTree? + var table = tables[selectors[selectors.startIndex]] + var selectorIndex = selectors.startIndex &+ 1 for symbol in out { - encoded -= 1 - if encoded <= 0 { - encoded = 50 - if selectorPointer == selectors.count { - fatalError("Incorrect selector.") - } else if selectorPointer < selectors.count { - t = tables[selectors[selectorPointer]] - selectorPointer += 1 - } + // New table is selected every 50 symbols. + if encoded >= 50 { + // Selectors were added every 50 symbols. So by construction `selectorIndex` can never exceed the + // amount of available selectors. + assert(selectorIndex < selectors.endIndex, "Incorrect selectorIndex.") + table = tables[selectors[selectorIndex]] + selectorIndex &+= 1 + encoded = 0 } - t?.code(symbol: symbol) + table.code(symbol: symbol) + encoded &+= 1 } } From 3686067e54a1eed3fdaa40c5eb647b1eaebb9ce7 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 18 Feb 2026 17:10:33 +0800 Subject: [PATCH 067/106] [LZMA] Remove LZMARangeDecoder.isCorrupted property According to LZMA specification this property is ignored by the reference implementation. In addition, specification suggests that other LZMA decoders should also ignore it. We weren't using this property for anything anyway, so its removal does not really change anything, except for better compliance with LZMA specification. In addition, range decoder initialization error is now thrown in accordance with reference implementation. --- Sources/LZMA/LZMARangeDecoder.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/LZMA/LZMARangeDecoder.swift b/Sources/LZMA/LZMARangeDecoder.swift index 91b896b8..26f583a9 100644 --- a/Sources/LZMA/LZMARangeDecoder.swift +++ b/Sources/LZMA/LZMARangeDecoder.swift @@ -12,7 +12,6 @@ struct LZMARangeDecoder { private var range = 0xFFFFFFFF as UInt32 private var code = 0 as UInt32 - private(set) var isCorrupted = false var isFinishedOK: Bool { return self.code == 0 @@ -27,7 +26,7 @@ struct LZMARangeDecoder { let byte = self.byteReader.byte() self.code = self.byteReader.uint32().byteSwapped - guard byte == 0 && self.code != self.range + guard byte == 0 else { throw LZMAError.rangeDecoderInitError } } @@ -53,10 +52,6 @@ struct LZMARangeDecoder { let t = 0 &- (self.code >> 31) self.code = self.code &+ (self.range & t) - if self.code == self.range { - self.isCorrupted = true - } - self.normalize() res <<= 1 From 51734eb8d3af6abdda7dbe72baaae545ed93b57d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 19 Feb 2026 10:54:56 +0800 Subject: [PATCH 068/106] [swcomp] Remove empty warmupIteration function from comp-ratio benchmarks These functions weren't called anyway since 5e8f568f6486b0a27a71c0dc77b5ef7624f5e502 commit where Benchmark protocol requirement for this function acquired Double return type. In any event, skipping of warmup iteration is better controlled by the corresponding option of "benchmark run" command. --- Sources/swcomp/Benchmarks/Benchmarks.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Sources/swcomp/Benchmarks/Benchmarks.swift b/Sources/swcomp/Benchmarks/Benchmarks.swift index 448ab359..502bfe06 100644 --- a/Sources/swcomp/Benchmarks/Benchmarks.swift +++ b/Sources/swcomp/Benchmarks/Benchmarks.swift @@ -270,8 +270,6 @@ struct CompRatioDeflate: Benchmark { } } - func warmupIteration() { } - func measure() -> Double { let outputData = Deflate.compress(data: self.data) guard outputData.count > 0 @@ -325,8 +323,6 @@ struct CompRatioBz2: Benchmark { } } - func warmupIteration() { } - func measure() -> Double { let outputData = BZip2.compress(data: self.data) guard outputData.count > 0 @@ -380,8 +376,6 @@ struct CompRatioLz4: Benchmark { } } - func warmupIteration() { } - func measure() -> Double { let outputData = LZ4.compress(data: self.data) guard outputData.count > 0 @@ -436,8 +430,6 @@ struct CompRatioLz4Bd: Benchmark { } } - func warmupIteration() { } - func measure() -> Double { let outputData = LZ4.compress(data: self.data, independentBlocks: false, blockChecksums: false, contentChecksum: true, contentSize: false, blockSize: 4 * 1024 * 1024, dictionary: nil, dictionaryID: nil) From a0c64f7f9c27c774bf5ff0883b412e156ebdc444 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 19 Feb 2026 15:29:49 +0800 Subject: [PATCH 069/106] [Deflate] Optimize creation of uncompressed blocks --- Sources/Deflate/Deflate+Compress.swift | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Sources/Deflate/Deflate+Compress.swift b/Sources/Deflate/Deflate+Compress.swift index e434ed57..6b7e5e88 100644 --- a/Sources/Deflate/Deflate+Compress.swift +++ b/Sources/Deflate/Deflate+Compress.swift @@ -73,24 +73,13 @@ extension Deflate: CompressionAlgorithm { } private static func createUncompressedBlock(_ data: Data) -> Data { - let bitWriter = LsbBitWriter() - - // Write block header. + assert(data.count <= 65535, "Cannot create uncompressed Deflate blocks larger than 65535 bytes.") + // Write block header, data's length and n-length. It is more efficient to avoid using LsbBitWriter. // Note: Only one block is supported for now. - bitWriter.write(bit: 1) - bitWriter.write(bits: [0, 0]) - - // Before writing lengths we need to discard remaining bits in current byte. - bitWriter.align() - - // Write data's length. - bitWriter.write(number: data.count, bitsCount: 16) - // Write data's n-length. - bitWriter.write(number: data.count ^ (1 << 16 - 1), bitsCount: 16) - - var out = bitWriter.data + let nLength = data.count ^ ((1 << 16) - 1) + var out = Data([1, UInt8(truncatingIfNeeded: data.count & 0xFF), UInt8(truncatingIfNeeded: (data.count >> 8) & 0xFF), + UInt8(truncatingIfNeeded: nLength & 0xFF), UInt8(truncatingIfNeeded: (nLength >> 8) & 0xFF)]) out.append(data) - return out } From ad73d8236772a95eb720117f133974fd916f2a66 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Mon, 23 Feb 2026 12:15:22 +0800 Subject: [PATCH 070/106] [BZip2] Slightly rewrite Huffman+RUNA/B+MTF decoding This piece of the code is now more logical, with less branching, and mirrors encoding counterpart. --- Sources/BZip2/BZip2.swift | 45 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index 46c8b50d..b0f4d9a9 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -156,48 +156,47 @@ public class BZip2: DecompressionAlgorithm { let tables = try computeTables() - var selectorPointer = 0 var decoded = 0 + var table = tables[selectors[selectors.startIndex]] + var selectorIndex = selectors.startIndex &+ 1 var runLength = 0 - var repeatPower = 0 - var buffer: [UInt8] = [] - var currentTable: DecodingTree? + var repeatPower = 1 + var buffer = [UInt8]() while true { - decoded -= 1 - if decoded <= 0 { - decoded = 50 - if selectorPointer == selectors.count { - throw BZip2Error.wrongSelector - } else if selectorPointer < selectors.count { - currentTable = tables[selectors[selectorPointer]] - selectorPointer += 1 - } + if decoded >= 50 { + guard selectorIndex < selectorsCount + else { throw BZip2Error.wrongSelector } + table = tables[selectors[selectorIndex]] + selectorIndex &+= 1 + decoded = 0 } - guard let symbol = currentTable?.findNextSymbol(), symbol != -1 + let symbol = table.findNextSymbol() + guard symbol != -1 else { throw BZip2Error.symbolNotFound } + decoded &+= 1 if symbol == 0 || symbol == 1 { // RUNA and RUNB symbols. - if runLength == 0 { - repeatPower = 1 - } - runLength += repeatPower << symbol + runLength &+= repeatPower << symbol repeatPower <<= 1 continue - } else if runLength > 0 { + } + if runLength > 0 { + // There might have been a repeat run right before EOS symbol. for _ in 0.. Date: Mon, 23 Feb 2026 17:59:04 +0800 Subject: [PATCH 071/106] [BZip2] Improve handling of truncated inputs during decompression --- Sources/BZip2/BZip2+Compress.swift | 4 +- Sources/BZip2/BZip2.swift | 132 +++++++++++++++++------------ 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index 0b24c1e4..95b32a6a 100644 --- a/Sources/BZip2/BZip2+Compress.swift +++ b/Sources/BZip2/BZip2+Compress.swift @@ -151,8 +151,8 @@ extension BZip2: CompressionAlgorithm { bitWriter.write(bit: 0) // "Randomized". bitWriter.write(number: pointer, bitsCount: 24) // Original pointer (from BWT). - // Encode which symbols (bytes) are used in the data. All possible 256 symbols (0...255) are split into "maps" - // of 16 consequent symbols. Each map is a sequence of 16 bits where a set bit indicates that a symbol is used. + // Encode which symbols (bytes) are used in the data. All possible 256 symbols (0...255) are split into 16 "maps" + // of 16 sequential symbols. Each map is a sequence of 16 bits where a non-zero bits indicate that used symbols. // If a map would consist of only zero bits, then it is omitted. This is determined in the construction of `usedMap`. var usedMap = Array(repeating: 0 as UInt8, count: 16) for usedByte in usedBytes { diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index b0f4d9a9..215ebda4 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -26,9 +26,9 @@ public class BZip2: DecompressionAlgorithm { } static func decompress(_ bitReader: MsbBitReader) throws -> Data { - // Valid BZip2 "archive" must contain at least 14 bytes of data: magic number (2 bytes), method (1 byte), block - // size (1 byte), block type (6 bytes), block CRC (4 bytes). - guard bitReader.bitsLeft >= 14 * 8 + // Valid BZip2 "archive" must contain at least 14 bytes of data: magic number (2 bytes), method (1 byte), and + // block size (1 byte). + guard bitReader.bitsLeft >= 32 else { throw BZip2Error.wrongMagic } /// An array for storing output data @@ -45,6 +45,9 @@ public class BZip2: DecompressionAlgorithm { var totalCRC: UInt32 = 0 while true { + // Each BZip2 block must contain block type (6 bytes) and block CRC32 (4 bytes). + guard bitReader.bitsLeft >= 80 + else { throw BZip2Error.wrongMagic } // Using `UInt64` because 48 bits may not fit into `Int` on some platforms. let blockType = bitReader.uint64(fromBits: 48) @@ -70,21 +73,31 @@ public class BZip2: DecompressionAlgorithm { } private static func decode(_ bitReader: MsbBitReader, _ blockSize: BlockSize) throws -> [UInt8] { + // Truncation checks: there is no such thing as "empty BZip2 blocks". Empty data after compression produces + // only a "CRC32 block". As such when processing a normal BZip2 block we can assume that various BZip2 + // structures (such as table selectors or code lengths) are present, and there are at least some symbols encoded. + + // Randomized bit, pointer (24 bits), and bitmap of used symbol blocks (16 bits) require 41 bits. + guard bitReader.bitsLeft >= 41 + else { throw BZip2Error.wrongMagic } + let isRandomized = bitReader.bit() guard isRandomized == 0 else { throw BZip2Error.randomizedBlock } let pointer = bitReader.int(fromBits: 24) - // Decoding which symbols are used in Huffman tables. - // The "list" of all possible 256 symbols is split into 16 blocks. - // If no symbols from a block are in use, then the block is not present. - // First, we decode which blocks are present. - let usedBlocksBitMap = UInt16(bitReader.int(fromBits: 16)) + // Decoding which symbols are used in Huffman tables. All possible 256 symbols (0...255) are split into 16 "maps" + // of 16 sequential symbols. Each map is a sequence of 16 bits where a non-zero bits indicate that used symbols. + // A map is omitteed if none of its symbols are in use. First, we decode which maps are present. + let usedBlocksBitMap = bitReader.uint16(fromBits: 16) + // For each non-zero bit in the bitmap there should be 16 bits of the block. After that there should be 3 bits + // for the number of Huffman tables and 15 bits for the selectors count. + guard bitReader.bitsLeft >= 16 * usedBlocksBitMap.nonzeroBitCount + 3 + 15 + else { throw BZip2Error.wrongMagic } + var blockMask = 1 << 15 as UInt16 var usedSymbols = [UInt8]() - // Two additional symbols are RUNA and RUNB. - var usedSymbolsCount = 2 while blockMask > 0 { if usedBlocksBitMap & blockMask > 0 { // Each block, if present, is a set of 16 bits which, if set, represent that the corresponding symbols @@ -93,7 +106,6 @@ public class BZip2: DecompressionAlgorithm { var symbolMask = 1 << 15 as UInt16 while symbolMask > 0 { if usedSymbolsBitMask & symbolMask > 0 { - usedSymbolsCount += 1 usedSymbols.append(UInt8(blockMask.leadingZeroBitCount * 16 + symbolMask.leadingZeroBitCount)) } symbolMask >>= 1 @@ -101,61 +113,75 @@ public class BZip2: DecompressionAlgorithm { } blockMask >>= 1 } + // Two additional symbols are RUNA and RUNB. + let usedSymbolsCount = 2 + usedSymbols.count - let huffmanGroups = bitReader.int(fromBits: 3) - guard huffmanGroups >= 2 && huffmanGroups <= 6 + let huffmanTablesCount = bitReader.int(fromBits: 3) + guard huffmanTablesCount >= 2 && huffmanTablesCount <= 6 else { throw BZip2Error.wrongHuffmanGroups } - func computeSelectors() throws -> [Int] { - let selectorsUsed = bitReader.int(fromBits: 15) + let selectorsCount = bitReader.int(fromBits: 15) + var mtf = Array(0.. 0 { - c += 1 - guard c < huffmanGroups - else { throw BZip2Error.wrongSelector } + // It is impossible to calculate in advance how many bits encode table selectors. At minimum, if each selector + // is encoded by a single zero bit (which means only the zeroth Huffman table is utilized), `selectorsCount` + // bits are used. At maximum, when all symbols are encoded with the last Huffman table, each selector is given + // by `huffmanTablesCount - 1` non-zero bits and one zero bit. This gives `huffmanTablesCount * selectorsCount` + // bits in total. + // Due to this unpredictability we are forced to check if there are still bits left before every read operation, + // similarly to how bit reading is done in `DecodingTree.findNextSymbol()`. + var selectors = [Int]() + // Accessing `bitsLeft` property of `bitReader` incurs a lot of overhead, so we create a local copy. + var bitsLeft = bitReader.bitsLeft + for _ in 0.. 0 { + let bit = bitReader.bit() + bitsLeft -= 1 + if bit == 0 { + break } - if c >= 0 { - let el = mtf.remove(at: c) - mtf.insert(el, at: 0) - } - selectorsList.append(mtf[0]) + c += 1 } - - return selectorsList + guard c < huffmanTablesCount + else { throw BZip2Error.wrongSelector } + let el = mtf.remove(at: c) + mtf.insert(el, at: 0) + selectors.append(el) } - let selectors = try computeSelectors() - - func computeTables() throws -> [DecodingTree] { - var tables = [DecodingTree]() - for _ in 0..= 0 && length <= 20 + // Similar to decoding selectors, code lengths are also encoded in unpredictable manner. As such, we check for + // input truncation before reading every bit. + var tables = [DecodingTree]() + for _ in 0..= 5 + else { throw BZip2Error.wrongHuffmanCodeLength } + var length = bitReader.int(fromBits: 5) + bitsLeft -= 5 + var codeLengths = [CodeLength]() + for i in 0..= 0 && length <= 20 + else { throw BZip2Error.wrongHuffmanCodeLength } + while bitsLeft > 0 { + let bit = bitReader.bit() + bitsLeft -= 1 + if bit == 0 { + break + } + guard bitsLeft > 0 else { throw BZip2Error.wrongHuffmanCodeLength } - while bitReader.bit() > 0 { - length -= (bitReader.bit().toInt() * 2 - 1) - } - if length > 0 { - lengths.append(CodeLength(symbol: i, codeLength: length)) - } + length -= (bitReader.bit().toInt() * 2 - 1) + bitsLeft -= 1 + } + if length > 0 { + codeLengths.append(CodeLength(symbol: i, codeLength: length)) } - let codes = Code.huffmanCodes(from: lengths) - let table = DecodingTree(codes, bitReader) - tables.append(table) } - - return tables + let codes = Code.huffmanCodes(from: codeLengths) + let table = DecodingTree(codes, bitReader) + tables.append(table) } - let tables = try computeTables() - var decoded = 0 var table = tables[selectors[selectors.startIndex]] var selectorIndex = selectors.startIndex &+ 1 From e8c8ccb3f229e0f67d49cfeca3262678ba9c21dc Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Mon, 23 Feb 2026 18:01:09 +0800 Subject: [PATCH 072/106] [Tests] Add a test for BZip2 truncation handling The idea here is that BZip2.decompress should not crash for any possible truncation. It should either throw an error or produce an output (if stars align such that truncated input is still a valid BZip2 "archive"). --- Tests/BZip2Tests.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/BZip2Tests.swift b/Tests/BZip2Tests.swift index 68bed62e..cc2763b1 100644 --- a/Tests/BZip2Tests.swift +++ b/Tests/BZip2Tests.swift @@ -96,4 +96,16 @@ class BZip2Tests: XCTestCase { } } + func testBZip2Truncation() throws { + for i in 1...9 { + let testName = "test\(i)" + let testData = try Constants.data(forTest: testName, withType: BZip2Tests.testType) + // It would be better to increase amount of different truncations tested, but we hit runtime limits in CI. + for _ in 0..<5 { + let truncationIndex = Int.random(in: (testData.startIndex + 1).. Date: Tue, 24 Feb 2026 00:50:23 +0800 Subject: [PATCH 073/106] [Tests] Add a more comprehensive LZ4 truncation test --- Tests/LZ4Tests.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/LZ4Tests.swift b/Tests/LZ4Tests.swift index 4a591f14..698f4514 100644 --- a/Tests/LZ4Tests.swift +++ b/Tests/LZ4Tests.swift @@ -202,4 +202,15 @@ class LZ4Tests: XCTestCase { } } + func testLZ4Truncation() throws { + for i in 1...9 { + let testName = "test\(i)" + let testData = try Constants.data(forTest: testName, withType: LZ4Tests.testType) + for _ in 0..<100 { + let truncationIndex = Int.random(in: (testData.startIndex + 1).. Date: Tue, 24 Feb 2026 00:58:49 +0800 Subject: [PATCH 074/106] [Zlib] Improve handling of truncated inputs during decompression --- Sources/Zlib/ZlibArchive.swift | 7 +++---- Sources/Zlib/ZlibHeader.swift | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/Zlib/ZlibArchive.swift b/Sources/Zlib/ZlibArchive.swift index f4b6822e..1ef7d2dc 100644 --- a/Sources/Zlib/ZlibArchive.swift +++ b/Sources/Zlib/ZlibArchive.swift @@ -23,10 +23,6 @@ public class ZlibArchive: Archive { - Returns: Unarchived data. */ public static func unarchive(archive data: Data) throws -> Data { - // Valid Zlib archive must contain at least 8 bytes of data. - guard data.count >= 8 - else { throw ZlibError.wrongCompressionMethod } - /// Object with input data which supports convenient work with bit shifts. let bitReader = LsbBitReader(data: data) @@ -35,6 +31,9 @@ public class ZlibArchive: Archive { let out = try Deflate.decompress(bitReader) bitReader.align() + guard bitReader.bytesLeft >= 4 + else { throw ZlibError.wrongAdler32(out) } + let adler32 = bitReader.uint32().byteSwapped guard CheckSums.adler32(out) == adler32 else { throw ZlibError.wrongAdler32(out) } diff --git a/Sources/Zlib/ZlibHeader.swift b/Sources/Zlib/ZlibHeader.swift index 4de8fe35..4a3256a6 100644 --- a/Sources/Zlib/ZlibHeader.swift +++ b/Sources/Zlib/ZlibHeader.swift @@ -80,11 +80,13 @@ public struct ZlibHeader { else { throw ZlibError.wrongCompressionLevel } self.compressionLevel = compressionLevel - guard (UInt(cmf) * 256 + UInt(flags)) % 31 == 0 + guard (UInt(cmf) << 8 + UInt(flags)) % 31 == 0 else { throw ZlibError.wrongFcheck } // If preset dictionary is present 4 bytes will be skipped. if fdict == 1 { + guard reader.bytesLeft >= 4 + else { throw ZlibError.wrongFcheck } reader.offset += 4 } } From fd42e30262aee854191dce26065872573c749533 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 24 Feb 2026 00:59:15 +0800 Subject: [PATCH 075/106] [Tests] Add a more comprehensive Zlib truncation test --- Tests/ZlibTests.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/ZlibTests.swift b/Tests/ZlibTests.swift index 2f9d81d1..e950c7b6 100644 --- a/Tests/ZlibTests.swift +++ b/Tests/ZlibTests.swift @@ -74,4 +74,14 @@ class ZlibTests: XCTestCase { } } + func testZlibTruncation() throws { + for testName in ["test", "random_file", "test_empty"] { + let testData = try Constants.data(forTest: testName, withType: ZlibTests.testType) + for _ in 0..<100 { + let truncationIndex = Int.random(in: (testData.startIndex + 1).. Date: Wed, 25 Feb 2026 23:44:41 +0800 Subject: [PATCH 076/106] [Tests] Add a more comprehensive GZip truncation test --- Tests/GzipTests.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/GzipTests.swift b/Tests/GzipTests.swift index daa49ee7..4c627b74 100644 --- a/Tests/GzipTests.swift +++ b/Tests/GzipTests.swift @@ -276,6 +276,18 @@ class GzipTests: XCTestCase { } } + func testGzipTruncation_Full() throws { + for i in 1...9 { + let testName = "test\(i)" + let testData = try Constants.data(forTest: testName, withType: GzipTests.testType) + // It would be better to increase amount of different truncations tested, but we hit runtime limits in CI. + for _ in 0..<5 { + let truncationIndex = Int.random(in: (testData.startIndex + 1).. Date: Thu, 26 Feb 2026 00:25:24 +0800 Subject: [PATCH 077/106] [Tests] Move deflate truncation testing into a separate file Also add test files that contain only "deflated" data. --- SWCompression.xcodeproj/project.pbxproj | 24 ++++++++++ Tests/DeflateTests.swift | 60 +++++++++++++++++++++++++ Tests/GzipTests.swift | 47 ------------------- Tests/Test Files | 2 +- 4 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 Tests/DeflateTests.swift diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 7315276f..41526b3e 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -262,6 +262,10 @@ E652FF002700A028006BC312 /* test_B6.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEF42700A028006BC312 /* test_B6.lz4 */; }; E652FF012700A028006BC312 /* test_B5.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEF52700A028006BC312 /* test_B5.lz4 */; }; E652FF022700A028006BC312 /* test_B4.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEF62700A028006BC312 /* test_B4.lz4 */; }; + E66D884E2F4F529100707F23 /* DeflateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D884D2F4F529100707F23 /* DeflateTests.swift */; }; + E66D88612F4F53AB00707F23 /* test6.deflate in Resources */ = {isa = PBXBuildFile; fileRef = E66D885D2F4F53AB00707F23 /* test6.deflate */; }; + E66D88622F4F53AB00707F23 /* test8.deflate in Resources */ = {isa = PBXBuildFile; fileRef = E66D885E2F4F53AB00707F23 /* test8.deflate */; }; + E66D88632F4F53AB00707F23 /* test9.deflate in Resources */ = {isa = PBXBuildFile; fileRef = E66D885F2F4F53AB00707F23 /* test9.deflate */; }; E66F36242538726E00076A6E /* test_empty.lzma in Resources */ = {isa = PBXBuildFile; fileRef = E66F36232538726E00076A6E /* test_empty.lzma */; }; E66F362C2538E2B700076A6E /* test_empty.zlib in Resources */ = {isa = PBXBuildFile; fileRef = E66F362B2538E2B700076A6E /* test_empty.zlib */; }; E6791E1826FD001A003852A9 /* DataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6791E1726FD001A003852A9 /* DataError.swift */; }; @@ -564,6 +568,10 @@ E652FEF42700A028006BC312 /* test_B6.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_B6.lz4; sourceTree = ""; }; E652FEF52700A028006BC312 /* test_B5.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_B5.lz4; sourceTree = ""; }; E652FEF62700A028006BC312 /* test_B4.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_B4.lz4; sourceTree = ""; }; + E66D884D2F4F529100707F23 /* DeflateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeflateTests.swift; sourceTree = ""; }; + E66D885D2F4F53AB00707F23 /* test6.deflate */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = test6.deflate; sourceTree = ""; }; + E66D885E2F4F53AB00707F23 /* test8.deflate */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = test8.deflate; sourceTree = ""; }; + E66D885F2F4F53AB00707F23 /* test9.deflate */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = test9.deflate; sourceTree = ""; }; E66F36232538726E00076A6E /* test_empty.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.lzma; sourceTree = ""; }; E66F362B2538E2B700076A6E /* test_empty.zlib */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.zlib; sourceTree = ""; }; E6791E1726FD001A003852A9 /* DataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataError.swift; sourceTree = ""; }; @@ -842,6 +850,7 @@ 06F065AC1FFB763300312A82 /* BZip2CompressionTests.swift */, 06F065A51FFB763300312A82 /* BZip2Tests.swift */, 06F065A21FFB763300312A82 /* DeflateCompressionTests.swift */, + E66D884D2F4F529100707F23 /* DeflateTests.swift */, 06F065A71FFB763300312A82 /* GzipTests.swift */, 06F065AB1FFB763300312A82 /* LzmaTests.swift */, E6B18E84270884E300F9AB99 /* LZ4CompressionTests.swift */, @@ -867,6 +876,7 @@ 06F065F61FFB763300312A82 /* 7z */, 06F065AE1FFB763300312A82 /* Answers */, 06F066081FFB763300312A82 /* BZip2 */, + E66D88602F4F53AB00707F23 /* Deflate */, 06F065B81FFB763300312A82 /* GZip */, 06F065F11FFB763300312A82 /* LZMA */, E6791E1F26FD04D0003852A9 /* LZ4 */, @@ -1069,6 +1079,16 @@ path = BZip2; sourceTree = ""; }; + E66D88602F4F53AB00707F23 /* Deflate */ = { + isa = PBXGroup; + children = ( + E66D885D2F4F53AB00707F23 /* test6.deflate */, + E66D885E2F4F53AB00707F23 /* test8.deflate */, + E66D885F2F4F53AB00707F23 /* test9.deflate */, + ); + path = Deflate; + sourceTree = ""; + }; E6791E1926FD0070003852A9 /* LZ4 */ = { isa = PBXGroup; children = ( @@ -1355,6 +1375,9 @@ 06F066581FFB763400312A82 /* full_test.tar in Resources */, E6EDD6A926F7767F00884532 /* test_minor_version_4.7z in Resources */, E652FEFB2700A028006BC312 /* test_B1234_BD.lz4 in Resources */, + E66D88612F4F53AB00707F23 /* test6.deflate in Resources */, + E66D88622F4F53AB00707F23 /* test8.deflate in Resources */, + E66D88632F4F53AB00707F23 /* test9.deflate in Resources */, 06F0662B1FFB763400312A82 /* test3.gz in Resources */, E652FEF72700A028006BC312 /* test_B7.lz4 in Resources */, 06F0664C1FFB763400312A82 /* test_pax.tar in Resources */, @@ -1482,6 +1505,7 @@ buildActionMask = 2147483647; files = ( E6522FB62789AFA600026B56 /* TarWriterTests.swift in Sources */, + E66D884E2F4F529100707F23 /* DeflateTests.swift in Sources */, 06F066151FFB763300312A82 /* ZlibTests.swift in Sources */, 06F066141FFB763300312A82 /* DeflateCompressionTests.swift in Sources */, E6791E3326FD05EC003852A9 /* LZ4Tests.swift in Sources */, diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift new file mode 100644 index 00000000..38950679 --- /dev/null +++ b/Tests/DeflateTests.swift @@ -0,0 +1,60 @@ +// Copyright (c) 2026 Timofey Solomko +// Licensed under MIT License +// +// See LICENSE for license information + +import XCTest +import SWCompression + +class DeflateTests: XCTestCase { + + private static let testType: String = "deflate" + + func testDeflateTruncation() throws { + // In this test we check that there is no crash when dealing with the truncation in the middle of the Deflate + // compressed data. The idea is to take three different types of Deflate blocks (uncompressed, static Huffman, + // and dynamic Huffman), truncate the input data manually at a random point inside it, and then test if an + // appropriate error is thrown. To make test a bit more sophisticated we generate a number of random truncations + // for each tested file. + + // This test file contains uncompressed Deflate block. + var testData = try Constants.data(forTest: "test9", withType: DeflateTests.testType) + for _ in 0..<100 { + let truncationIndex = Int.random(in: 23.. Date: Thu, 26 Feb 2026 00:52:24 +0800 Subject: [PATCH 078/106] [Deflate] Optimize decoding of code lengths for dynamic trees --- Sources/Deflate/Deflate+Compress.swift | 3 +- Sources/Deflate/Deflate.swift | 46 ++++++++++++-------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Sources/Deflate/Deflate+Compress.swift b/Sources/Deflate/Deflate+Compress.swift index 6b7e5e88..90d22e5f 100644 --- a/Sources/Deflate/Deflate+Compress.swift +++ b/Sources/Deflate/Deflate+Compress.swift @@ -30,8 +30,7 @@ extension Deflate: CompressionAlgorithm { // Static Huffman size is more complicated... let staticHuffmanBlockSize = staticHuffmanBitSize(bldCodes.stats) - // Since `length` of uncompressed block is 16-bit integer, - // there is a limitation on size of uncompressed block. + // Since `length` of uncompressed block is 16-bit integer, there is a limitation on size of uncompressed block. // Falling back to static Huffman encoding in case of big uncompressed block is a band-aid solution. if uncompBlockSize <= staticHuffmanBlockSize && uncompBlockSize <= 65535 { // If according to our calculations static huffman will not make output smaller than input, diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index 60c2970a..5ba1eac7 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -96,6 +96,10 @@ public class Deflate: DecompressionAlgorithm { guard bitReader.bitsLeft >= 3 * codeLengthsCount else { throw DeflateError.symbolNotFound } + // Code lengths of the literal/length and distance Huffman trees are encoded with another Huffman + // tree using a special alphabet. The code lengths for this latter tree are presented in a specific + // order which is stored in `Constants.codeLengthOrders`: `codeLengthOrders[i]` is the code lengths + // alphabet symbol at the i-th place in the sequence. var orderedCodeLengths = Array(repeating: 0, count: 19) for i in 0..= 0 && symbol <= 15 { // It is a raw code length. - count = 1 - what = symbol + codeLengths[n] = symbol + n += 1 } else if symbol == 16 { // Copy previous code length 3 to 6 times. // Next two bits show how many times we need to copy. guard bitReader.bitsLeft >= 2 else { throw DeflateError.symbolNotFound } - - count = bitReader.int(fromBits: 2) + 3 - what = codeLengths.last! + let copyCount = bitReader.int(fromBits: 2) + 3 + for i in 0..= 3 else { throw DeflateError.symbolNotFound } - - count = bitReader.int(fromBits: 3) + 3 - what = 0 + n += bitReader.int(fromBits: 3) + 3 } else if symbol == 18 { - // Repeat code length 0 from 11 to 138 times. - // Next seven bits show how many times we need to do this. + // Repeat code length 0 from 11 to 138 times. Next 7 bits show how many times we need to + // copy. Since `codeLengths` array was already preinitialized with zeros, we just have to + // skip ahead. guard bitReader.bitsLeft >= 7 else { throw DeflateError.symbolNotFound } - - count = bitReader.int(fromBits: 7) + 11 - what = 0 + n += bitReader.int(fromBits: 7) + 11 } else { throw DeflateError.wrongSymbol } - for _ in 0.. Date: Thu, 26 Feb 2026 16:20:54 +0800 Subject: [PATCH 079/106] [Tests] Simplify Deflate truncation test --- Tests/DeflateTests.swift | 46 +++++++++------------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift index 38950679..76a1f3b5 100644 --- a/Tests/DeflateTests.swift +++ b/Tests/DeflateTests.swift @@ -10,49 +10,23 @@ class DeflateTests: XCTestCase { private static let testType: String = "deflate" - func testDeflateTruncation() throws { + func testTruncation() throws { // In this test we check that there is no crash when dealing with the truncation in the middle of the Deflate // compressed data. The idea is to take three different types of Deflate blocks (uncompressed, static Huffman, // and dynamic Huffman), truncate the input data manually at a random point inside it, and then test if an // appropriate error is thrown. To make test a bit more sophisticated we generate a number of random truncations // for each tested file. - // This test file contains uncompressed Deflate block. - var testData = try Constants.data(forTest: "test9", withType: DeflateTests.testType) - for _ in 0..<100 { - let truncationIndex = Int.random(in: 23.. Date: Thu, 26 Feb 2026 16:34:22 +0800 Subject: [PATCH 080/106] [Deflate] Check that 16 is not the first code length symbol This symbol in the dynamic Huffman block encodes copy of the previous code length. If this symbol is the first, then there is nothing to copy. Previously, this could cause a crash. --- Sources/Deflate/Deflate.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index 5ba1eac7..8a1bf030 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -120,9 +120,10 @@ public class Deflate: DecompressionAlgorithm { // It is a raw code length. codeLengths[n] = symbol n += 1 - } else if symbol == 16 { - // Copy previous code length 3 to 6 times. - // Next two bits show how many times we need to copy. + } else if symbol == 16 && n > codeLengths.startIndex { + // Copy previous code length 3 to 6 times. Next two bits show how many times we need to copy. + // This symbol cannot be the first symbol encoding code lengths, since there is nothing to + // copy at this point. guard bitReader.bitsLeft >= 2 else { throw DeflateError.symbolNotFound } let copyCount = bitReader.int(fromBits: 2) + 3 From 07d30d604530ed2ded39003e8272ef1b31652972 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 26 Feb 2026 16:34:45 +0800 Subject: [PATCH 081/106] [Tests] Add a test for symbol 16 issue in Deflate --- Tests/DeflateTests.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift index 76a1f3b5..cd2888a0 100644 --- a/Tests/DeflateTests.swift +++ b/Tests/DeflateTests.swift @@ -31,4 +31,19 @@ class DeflateTests: XCTestCase { } } + func testSymbol16First() throws { + // Symbol 16 cannot be the first symbol encoding code lengths in the dynamic Huffman block. Previously, there + // was a crash in such situation. This test checks that error is thrown appropriately. + + // The input data was constructed manually: + // - 101: last block bit and dynamical Huffman block type, + // - 00000: minimal amount of literal codes, + // - 00000: minimal amount of distances codes, + // - 0000: minimal amount of code length codes (4), + // - 011 011 010 001: four code lenghts for code length codes (1, 2, 3, 3), + // - 0: encoded code length symbol 16 (which means to copy previous code length). + let testData = Data([0b0000_0101, 0b0000_0000, 0b1010_0010, 0b0000_1101]) + XCTAssertThrowsError(try Deflate.decompress(data: testData)) + } + } From 567878fce5ca23f3c2661bea6eaa2552a5ac579a Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 28 Feb 2026 01:17:59 +0800 Subject: [PATCH 082/106] [CI] Update to Xcode 26.3 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index eb7f3d00..5f4c5575 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,7 +37,7 @@ stages: WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' macosSwift62: imageName: 'macOS-15' - DEVELOPER_DIR: '/Applications/Xcode_26.2.app' + DEVELOPER_DIR: '/Applications/Xcode_26.3.app' IOS_SIMULATOR: 'iPhone 16' WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' pool: From 7f547c20a160a0ffa91aebff26821da1de5165e9 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 28 Feb 2026 17:36:55 +0800 Subject: [PATCH 083/106] [Huffman] Optimize DecodingTree leaf count --- Sources/Common/CodingTree/DecodingTree.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Common/CodingTree/DecodingTree.swift b/Sources/Common/CodingTree/DecodingTree.swift index 0f60ef09..b8a5faa6 100644 --- a/Sources/Common/CodingTree/DecodingTree.swift +++ b/Sources/Common/CodingTree/DecodingTree.swift @@ -15,8 +15,8 @@ final class DecodingTree { init(_ huffmanCodes: HuffmanCodes, _ bitReader: BitReader) { self.bitReader = bitReader - // Calculate maximum amount of leaves in a tree. - self.leafCount = 1 << (huffmanCodes.maxBits + 1) + // Maximum amount of leaves in a binary tree with the height equal to `huffmanCodes.maxBits`. + self.leafCount = (1 << (huffmanCodes.maxBits + 1)) - 1 var tree = Array(repeating: -1, count: leafCount) for code in huffmanCodes.codes { From a39011a00b94f0fdf1f68d10bf2d26f453858d7c Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sat, 28 Feb 2026 17:47:14 +0800 Subject: [PATCH 084/106] [Deflate] Fix potential crashes in cases of too large copy amount In a dynamic Huffman block if either previous or zero code length is copied too many times, it could cause a crash or lead to inconsistent state. These situations are checked now. --- Sources/Deflate/Deflate.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index 8a1bf030..fd52b9ea 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -127,6 +127,8 @@ public class Deflate: DecompressionAlgorithm { guard bitReader.bitsLeft >= 2 else { throw DeflateError.symbolNotFound } let copyCount = bitReader.int(fromBits: 2) + 3 + guard n + copyCount <= codeLengths.endIndex + else { throw DeflateError.wrongSymbol } for i in 0.. Date: Sat, 28 Feb 2026 17:47:46 +0800 Subject: [PATCH 085/106] [Tests] Add Deflate tests for handling over copying in dynamic Huffman blocks --- Tests/DeflateTests.swift | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift index cd2888a0..61b15e46 100644 --- a/Tests/DeflateTests.swift +++ b/Tests/DeflateTests.swift @@ -46,4 +46,38 @@ class DeflateTests: XCTestCase { XCTAssertThrowsError(try Deflate.decompress(data: testData)) } + func testCodeLengthsOverCopy() throws { + // Previously, when decoding code lengths in a dynamic Huffman block if a copy code length count was + // sufficiently large, it could lead to an inconsistent state or a crash (due to out-of-range array subscript). + // In the new version these situations are checked. + + // Repeat a zero code length too many times. The input data was constructed manually: + // - 101: last block bit and dynamical Huffman block type, + // - 00000: minimal amount of literal codes, + // - 00000: minimal amount of distances codes, + // - 0000: minimal amount of code length codes (4), + // - 011 011 010 001: four code lenghts for code length codes (1, 2, 3, 3), + // - 111: encoded code length symbol 18 (which means to repeat 0 code length), + // - 1111111: maximum repeat amount, + // - 111: encoded code length symbol 18 (which means to repeat 0 code length), + // - 1111111: maximum repeat amount. + var testData = Data([0b0000_0101, 0b0000_0000, 0b1010_0010, 0b1110_1101, 0b1111_1111, 0b1111_1111, 0b0000_0001]) + XCTAssertThrowsError(try Deflate.decompress(data: testData)) + + // Copy a previous code length too many times. The input data was constructed manually: + // - 101: last block bit and dynamical Huffman block type, + // - 00000: minimal amount of literal codes, + // - 00000: minimal amount of distances codes, + // - 0000: minimal amount of code length codes (4), + // - 011 011 010 001: four code lenghts for code length codes (1, 2, 3, 3), + // - 111: encoded code length symbol 18 (which means to repeat 0 code length), + // - 1111111: maximum repeat amount, + // - 111: encoded code length symbol 18 (which means to repeat 0 code length), + // - 1101100 repeat amount. + // - 0: encoded code length symbol 16 (which means to copy a previous code length), + // - 01: copy amount. + testData = Data([0b0000_0101, 0b0000_0000, 0b1010_0010, 0b1110_1101, 0b1111_1111, 0b1011_0011, 0b0000_0101]) + XCTAssertThrowsError(try Deflate.decompress(data: testData)) + } + } From 7e6a9fd42c3660358ddb7261287bf5de52283966 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 19:48:21 +0800 Subject: [PATCH 086/106] [Deflate] Add a check for correct number of literal/length codes --- Sources/Deflate/Deflate.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index fd52b9ea..d1f232c8 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -88,6 +88,11 @@ public class Deflate: DecompressionAlgorithm { /// Number of literals codes. let literals = bitReader.int(fromBits: 5) + 257 + // According to Deflate specification maximum amount of literal/length codes is 286. However, it + // is possible to encode a number larger than this with 5 bits. Specification seems to be silent + // about this scenario, so we treat it as a corrupted input. + guard literals <= 286 + else { throw DeflateError.wrongSymbol } /// Number of distances codes. let distances = bitReader.int(fromBits: 5) + 1 /// Number of code lengths codes. From d8c22c55da4af8046f6797ffa2c3254ea51cb77b Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 19:51:37 +0800 Subject: [PATCH 087/106] [Deflate][Huffman] Filter out zero code lengths only during code construction This should prevent crashes described in issue #57. --- SWCompression.xcodeproj/project.pbxproj | 4 ---- Sources/Common/CodingTree/Code.swift | 4 +--- Sources/Deflate/Deflate+Lengths.swift | 18 ------------------ Sources/Deflate/Deflate.swift | 4 ++++ 4 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 Sources/Deflate/Deflate+Lengths.swift diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 41526b3e..a874536f 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -64,7 +64,6 @@ 0686A6451FA4C47300E89C9E /* FileSystemType+Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0686A6441FA4C47300E89C9E /* FileSystemType+Zip.swift */; }; 068D070221368242002A6C3B /* DeltaFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068D070121368242002A6C3B /* DeltaFilter.swift */; }; 068D070421368617002A6C3B /* test_delta_filter.7z in Resources */ = {isa = PBXBuildFile; fileRef = 068D070321368617002A6C3B /* test_delta_filter.7z */; }; - 06912BD21F5C8F3D0070BB60 /* Deflate+Lengths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06912BD11F5C8F3D0070BB60 /* Deflate+Lengths.swift */; }; 0694A74D1F7C0DF00023BC99 /* BurrowsWheeler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0694A74C1F7C0DF00023BC99 /* BurrowsWheeler.swift */; }; 06955E491F65C761004D5D79 /* BZip2+Lengths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06955E481F65C761004D5D79 /* BZip2+Lengths.swift */; }; 069864CA21403CE700755D9B /* test_sha256.xz in Resources */ = {isa = PBXBuildFile; fileRef = 069864C921403CE700755D9B /* test_sha256.xz */; }; @@ -371,7 +370,6 @@ 0686A6441FA4C47300E89C9E /* FileSystemType+Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileSystemType+Zip.swift"; sourceTree = ""; }; 068D070121368242002A6C3B /* DeltaFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeltaFilter.swift; sourceTree = ""; }; 068D070321368617002A6C3B /* test_delta_filter.7z */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_delta_filter.7z; sourceTree = ""; }; - 06912BD11F5C8F3D0070BB60 /* Deflate+Lengths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deflate+Lengths.swift"; sourceTree = ""; }; 0694A74C1F7C0DF00023BC99 /* BurrowsWheeler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowsWheeler.swift; sourceTree = ""; }; 06955E481F65C761004D5D79 /* BZip2+Lengths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BZip2+Lengths.swift"; sourceTree = ""; }; 069864C921403CE700755D9B /* test_sha256.xz */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_sha256.xz; sourceTree = ""; }; @@ -648,7 +646,6 @@ children = ( 061FCE251DBCC4BE0052F7BE /* Deflate.swift */, 06CDFC9D1F111D2600292758 /* Deflate+Compress.swift */, - 06912BD11F5C8F3D0070BB60 /* Deflate+Lengths.swift */, 0631CB051F81109E00A2E0AB /* Deflate+Constants.swift */, 06CDFCA71F111D9700292758 /* DeflateError.swift */, ); @@ -1446,7 +1443,6 @@ 0631CB061F81109E00A2E0AB /* Deflate+Constants.swift in Sources */, E648151D26A889B8009261F0 /* TarHeader.swift in Sources */, 06D95AC11F4C54E0006B46AC /* BZip2+Compress.swift in Sources */, - 06912BD21F5C8F3D0070BB60 /* Deflate+Lengths.swift in Sources */, 06A9606A1F1E7E0D0078E6D1 /* 7zSubstreamInfo.swift in Sources */, E6C4150726FE230A00F9D36F /* XxHash32.swift in Sources */, 0694A74D1F7C0DF00023BC99 /* BurrowsWheeler.swift in Sources */, diff --git a/Sources/Common/CodingTree/Code.swift b/Sources/Common/CodingTree/Code.swift index adaad351..278011fc 100644 --- a/Sources/Common/CodingTree/Code.swift +++ b/Sources/Common/CodingTree/Code.swift @@ -12,7 +12,6 @@ struct Code { let code: Int let symbol: Int - /// `lengths` don't have to be sorted, but there must not be any 0 code lengths. static func huffmanCodes(from lengths: [CodeLength]) -> HuffmanCodes { // Sort `lengths` array to calculate canonical Huffman code. let sortedLengths = lengths.sorted() @@ -24,8 +23,7 @@ struct Code { var loopBits = -1 var symbol = -1 - for length in sortedLengths { - precondition(length.codeLength > 0, "Code length must not be 0 during HuffmanTree construction.") + for length in sortedLengths where length.codeLength > 0 { symbol += 1 // We sometimes need to make symbol to have length.bits bit length. let bits = length.codeLength diff --git a/Sources/Deflate/Deflate+Lengths.swift b/Sources/Deflate/Deflate+Lengths.swift deleted file mode 100644 index dd2425f3..00000000 --- a/Sources/Deflate/Deflate+Lengths.swift +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2026 Timofey Solomko -// Licensed under MIT License -// -// See LICENSE for license information - -// Deflate specific functions for generation of HuffmanLength arrays from different inputs. -extension Deflate { - - /// - Note: Skips zero codeLengths. - static func lengths(from orderedCodeLengths: [Int]) -> [CodeLength] { - var lengths = [CodeLength]() - for (i, codeLength) in orderedCodeLengths.enumerated() where codeLength > 0 { - lengths.append(CodeLength(symbol: i, codeLength: codeLength)) - } - return lengths - } - -} diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift index d1f232c8..68553e33 100644 --- a/Sources/Deflate/Deflate.swift +++ b/Sources/Deflate/Deflate.swift @@ -248,4 +248,8 @@ public class Deflate: DecompressionAlgorithm { return Data(out) } + private static func lengths(from orderedCodeLengths: [Int]) -> [CodeLength] { + return orderedCodeLengths.enumerated().map({ CodeLength(symbol: $0, codeLength: $1) }) + } + } From 8310b985f2c0a98d57a5a06d3f9cee232501792e Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 19:54:30 +0800 Subject: [PATCH 088/106] [Tests] Add tests for all zero code lengths in Deflate dynamic Huffman block These are related to situation described in issue #57. We should now either properly throw an error, or in rare cases (when only literals are inside the block) produce an output. --- Tests/DeflateTests.swift | 115 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift index 61b15e46..6878ab3b 100644 --- a/Tests/DeflateTests.swift +++ b/Tests/DeflateTests.swift @@ -4,6 +4,7 @@ // See LICENSE for license information import XCTest +import BitByteData import SWCompression class DeflateTests: XCTestCase { @@ -73,11 +74,123 @@ class DeflateTests: XCTestCase { // - 111: encoded code length symbol 18 (which means to repeat 0 code length), // - 1111111: maximum repeat amount, // - 111: encoded code length symbol 18 (which means to repeat 0 code length), - // - 1101100 repeat amount. + // - 1101100: repeat amount, // - 0: encoded code length symbol 16 (which means to copy a previous code length), // - 01: copy amount. testData = Data([0b0000_0101, 0b0000_0000, 0b1010_0010, 0b1110_1101, 0b1111_1111, 0b1011_0011, 0b0000_0101]) XCTAssertThrowsError(try Deflate.decompress(data: testData)) } + func testCodeLengthsAllZero() throws { + // Previously, if all code lengths for a particular Huffman tree in a dynamic Huffman block were zero, the + // Huffman code construction algorithm would crash. This behavior was reported as issue #57. After fixing the + // crash all zero code lengths results in an "empty" Huffman tree. If such a tree is used to read a symbol, no + // symbol will be found, and an error will be thrown appropriately. + + // Code lengths for the code lengths tree are all zero which indicates corrputed input. The input data was + // constructed manually: + // - 101: last block bit and dynamical Huffman block type, + // - 00000: minimal amount of literal codes, + // - 00000: minimal amount of distances codes, + // - 0000: minimal amount of code length codes (4), + // - 000 000 000 000: four zero code lenghts for code length codes. + let testData = Data([0b0000_0101, 0b0000_0000, 0b0000_0000, 0b0_0000]) + XCTAssertThrowsError(try Deflate.decompress(data: testData)) + + // The following inputs are based on test6 compressed data, but otherwise were constructed manually with the + // help of `LsbBitWriter` from BitByteData. + + // All code lengths are zero for the literals/lengths tree, and two non-zero distance tree code lengths. + var writer = LsbBitWriter() + // - 101: last block bit and dynamical Huffman block type, + writer.write(bits: [1, 0, 1]) + // - 11101: maximal amount of literal codes (29 + 257 = 286), + writer.write(unsignedNumber: 29, bitsCount: 5) + // - 00001: number of distances codes (1 + 1 = 2), + writer.write(unsignedNumber: 1, bitsCount: 5) + // - 1110: number of code length codes (4 + 14 = 18), + writer.write(unsignedNumber: 14, bitsCount: 4) + // - 000 011 010 000 000 000 000 000 000 000 000 000 000 010 000 011 000 010: code lenghts for code length codes, + writer.write(unsignedNumber: 0, bitsCount: 3) + writer.write(unsignedNumber: 3, bitsCount: 3) + writer.write(unsignedNumber: 2, bitsCount: 3) + for _ in 0..<10 { + writer.write(unsignedNumber: 0, bitsCount: 3) + } + writer.write(unsignedNumber: 2, bitsCount: 3) + writer.write(unsignedNumber: 0, bitsCount: 3) + writer.write(unsignedNumber: 3, bitsCount: 3) + writer.write(unsignedNumber: 0, bitsCount: 3) + writer.write(unsignedNumber: 2, bitsCount: 3) + // - 01: encoded code length symbol 18 (which means to repeat 0 code length), + writer.write(unsignedNumber: 1, bitsCount: 2) + // - 1111111: maximum repeat amount, + writer.write(unsignedNumber: 127, bitsCount: 7) + // - 01: encoded code length symbol 18 (which means to repeat 0 code length), + writer.write(unsignedNumber: 1, bitsCount: 2) + // - 1111111: maximum repeat amount, + writer.write(unsignedNumber: 127, bitsCount: 7) + // - 111: encoded code length symbol 17 (which means to repeat 0 code length), + writer.write(unsignedNumber: 7, bitsCount: 3) + // - 111: maximum repeat amount, + writer.write(unsignedNumber: 7, bitsCount: 3) + // - 00: encoded code length symbol 1 (which means code length is equal to 1), + writer.write(unsignedNumber: 0, bitsCount: 2) + // - 00: encoded code length symbol 1 (which means code length is equal to 1). + writer.write(unsignedNumber: 0, bitsCount: 2) + writer.align() + + // Assuming that there are no literals in the block we would still need both a length symbol from the + // literals/length tree and a distance symbol from the distance tree to perform a match copy. Thus, this input + // is corrupted. + XCTAssertThrowsError(try Deflate.decompress(data: writer.data)) + + // All code lengths are zero for the distance tree, and two non-zero code lengths for the literals/length tree. + writer = LsbBitWriter() + // - 101: last block bit and dynamical Huffman block type, + writer.write(bits: [1, 0, 1]) + // - 11101: maximal amount of literal codes (29 + 257 = 286), + writer.write(unsignedNumber: 29, bitsCount: 5) + // - 00001: number of distances codes (1 + 1 = 2), + writer.write(unsignedNumber: 1, bitsCount: 5) + // - 1110: number of code length codes (4 + 14 = 18), + writer.write(unsignedNumber: 14, bitsCount: 4) + // - 000 011 010 000 000 000 000 000 000 000 000 000 000 010 000 011 000 010: code lenghts for code length codes, + writer.write(unsignedNumber: 0, bitsCount: 3) + writer.write(unsignedNumber: 3, bitsCount: 3) + writer.write(unsignedNumber: 2, bitsCount: 3) + for _ in 0..<10 { + writer.write(unsignedNumber: 0, bitsCount: 3) + } + writer.write(unsignedNumber: 2, bitsCount: 3) + writer.write(unsignedNumber: 0, bitsCount: 3) + writer.write(unsignedNumber: 3, bitsCount: 3) + writer.write(unsignedNumber: 0, bitsCount: 3) + writer.write(unsignedNumber: 2, bitsCount: 3) + // - 011: encoded code length symbol 2 (which means code length is equal to 2), + writer.write(unsignedNumber: 3, bitsCount: 3) + // - 01: encoded code length symbol 18 (which means to repeat 0 code length), + writer.write(unsignedNumber: 1, bitsCount: 2) + // - 1111111: maximum repeat amount, + writer.write(unsignedNumber: 127, bitsCount: 7) + // - 01: encoded code length symbol 18 (which means to repeat 0 code length), + writer.write(unsignedNumber: 1, bitsCount: 2) + // - 1101010: repeat amount, + writer.write(unsignedNumber: 106, bitsCount: 7) + // - 011: encoded code length symbol 3 (which means code length is equal to 3), + writer.write(unsignedNumber: 2, bitsCount: 2) + // - 01: encoded code length symbol 18 (which means to repeat 0 code length), + writer.write(unsignedNumber: 1, bitsCount: 2) + // - 0010100: repeat amount, + writer.write(unsignedNumber: 20, bitsCount: 7) + // - 00: literal symbol 0, + writer.write(unsignedNumber: 0, bitsCount: 2) + // - 010: EOS symbol 256, + writer.write(unsignedNumber: 2, bitsCount: 3) + writer.align() + + // The distance tree can be empty if a block contains only literals. In this case we encoded a single byte of 0. + XCTAssertEqual(try Deflate.decompress(data: writer.data), Data([0])) + } + } From 93e2eef38d9c47a435cd08880e687266f2fee7bd Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 19:55:04 +0800 Subject: [PATCH 089/106] [BZip2] Remove redundant check for zero code lengths Such code lengths are now filtered out during Huffman code construction. --- Sources/BZip2/BZip2.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index 215ebda4..a8b7e556 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -173,9 +173,7 @@ public class BZip2: DecompressionAlgorithm { length -= (bitReader.bit().toInt() * 2 - 1) bitsLeft -= 1 } - if length > 0 { - codeLengths.append(CodeLength(symbol: i, codeLength: length)) - } + codeLengths.append(CodeLength(symbol: i, codeLength: length)) } let codes = Code.huffmanCodes(from: codeLengths) let table = DecodingTree(codes, bitReader) From 5d4b4d2493ea28bf2f5fc07b3f917aa4f22de1f2 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 19:55:32 +0800 Subject: [PATCH 090/106] [TAR] Fix code comment to refer to TarParser instead of Provider --- Sources/TAR/TarParser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/TAR/TarParser.swift b/Sources/TAR/TarParser.swift index a7bc1b23..0defb6f6 100644 --- a/Sources/TAR/TarParser.swift +++ b/Sources/TAR/TarParser.swift @@ -6,8 +6,8 @@ import Foundation import BitByteData -// While it is tempting to make Provider conform to `IteratorProtocol` and `Sequence` protocols, it is in fact -// impossible to do so, since `TarHeader.init(...)` is throwing and `IteratorProtocol.next()` cannot be throwing. +// While it is tempting to make the parser conform to `IteratorProtocol` and `Sequence` protocols, it is in fact +// impossible to do so, since `TarHeader.init(_)` is throwing and `IteratorProtocol.next()` cannot be throwing. struct TarParser { enum ParsingResult { From 567a5473d1d728db0e41f6728d31bb3503352098 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 19:58:31 +0800 Subject: [PATCH 091/106] [GZip] Move bytes left check out of the loop when reading extra fields --- Sources/GZip/GzipHeader.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/GZip/GzipHeader.swift b/Sources/GZip/GzipHeader.swift index e3ea37fc..7cdfaa4a 100644 --- a/Sources/GZip/GzipHeader.swift +++ b/Sources/GZip/GzipHeader.swift @@ -118,12 +118,11 @@ public struct GzipHeader { headerBytes.append(byte) } + // There must be least four bytes of extra fields for SI1, SI2, and 2 bytes of the length parameter + // filled with zeros (minimal variant). + guard reader.bytesLeft >= xlen && xlen >= 4 + else { throw GzipError.wrongMagic } while xlen > 0 { - // There must be least four bytes of extra fields for SI1, SI2, and 2 bytes of the length parameter - // filled with zeros (minimal variant). - guard reader.bytesLeft >= xlen && xlen >= 4 - else { throw GzipError.wrongMagic } - let si1 = reader.byte() headerBytes.append(si1) From e32887084c2f4b871029235fdad966ddab477e04 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Sun, 1 Mar 2026 20:34:51 +0800 Subject: [PATCH 092/106] [Huffman] Change Decoding/Encoding tree to structs This seems to have a positive impact on the performance of Deflate and BZip2 decompression and compression. It is unclear to me why these two were declared as classes in the first place. --- Sources/Common/CodingTree/DecodingTree.swift | 2 +- Sources/Common/CodingTree/EncodingTree.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Common/CodingTree/DecodingTree.swift b/Sources/Common/CodingTree/DecodingTree.swift index b8a5faa6..f30df948 100644 --- a/Sources/Common/CodingTree/DecodingTree.swift +++ b/Sources/Common/CodingTree/DecodingTree.swift @@ -5,7 +5,7 @@ import BitByteData -final class DecodingTree { +struct DecodingTree { private let bitReader: BitReader diff --git a/Sources/Common/CodingTree/EncodingTree.swift b/Sources/Common/CodingTree/EncodingTree.swift index ca53a27d..da922554 100644 --- a/Sources/Common/CodingTree/EncodingTree.swift +++ b/Sources/Common/CodingTree/EncodingTree.swift @@ -12,7 +12,7 @@ fileprivate struct CodingIndex { } -final class EncodingTree { +struct EncodingTree { private let bitWriter: BitWriter From 2620ed67cf5e3d48b6e7c121f9917f1a25269818 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 12 May 2026 16:04:53 +0800 Subject: [PATCH 093/106] Remove reference to download-bbd-macos command in README --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fdcf456c..d6b47037 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,6 @@ If you'd like to contribute, please [create a pull request](https://github.com/t If you want to run tests on your computer, you need to do a couple of additional steps after cloning the repository: ```bash -./utils.py download-bbd-macos git submodule update --init --recursive cd "Tests/Test Files" cp gitattributes-copy .gitattributes @@ -132,9 +131,8 @@ git lfs pull git lfs checkout ``` -The first command will download the BitByteData dependency, which requires having Carthage installed. The remaining -commands will download the files used in tests. These files are stored in a -[separate repository](https://github.com/tsolomko/SWCompression-Test-Files), using Git LFS. There are two reasons for +These commands will download the files used in tests which are stored in a +[separate repository](https://github.com/tsolomko/SWCompression-Test-Files) using Git LFS. There are two reasons for this complicated setup. Firstly, some of these files can be quite big, and it would be unfortunate if the users of SWCompression had to download them during the installation. Secondly, Swift Package Manager and contemporary versions of Xcode don't always work well with git-lfs-enabled repositories. To prevent any potential problems test files were moved From 75cc406d475758612689290b89e58064d1005bb0 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 12 May 2026 16:06:19 +0800 Subject: [PATCH 094/106] [BZip2] Add multiDecompress public function This should help with inputs consisting of multiple BZip2 archives concatenated together which were brought up in #59. --- Sources/BZip2/BZip2.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index a8b7e556..7114a2ad 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -25,6 +25,16 @@ public class BZip2: DecompressionAlgorithm { return try decompress(bitReader) } + public static func multiDecompress(data: Data) throws -> [Data] { + let reader = MsbBitReader(data: data) + var result = [Data]() + while !reader.isFinished { + result.append(try decompress(reader)) + reader.align() + } + return result + } + static func decompress(_ bitReader: MsbBitReader) throws -> Data { // Valid BZip2 "archive" must contain at least 14 bytes of data: magic number (2 bytes), method (1 byte), and // block size (1 byte). From 8c389295c4feb60c3ec110db2d4b77cddfb13cda Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 12 May 2026 16:06:43 +0800 Subject: [PATCH 095/106] [Tests] Add tests for BZip2.multiDecompress --- Tests/BZip2Tests.swift | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Tests/BZip2Tests.swift b/Tests/BZip2Tests.swift index cc2763b1..6d59f7ba 100644 --- a/Tests/BZip2Tests.swift +++ b/Tests/BZip2Tests.swift @@ -108,4 +108,42 @@ class BZip2Tests: XCTestCase { } } + func testBZip2MultiDecompress() throws { + var input = Data() + for i in 1...5 { + let testName = "test\(i)" + let testData = try Constants.data(forTest: testName, withType: BZip2Tests.testType) + input.append(testData) + } + let output = try BZip2.multiDecompress(data: input) + for i in 1...5 { + let testName = "test\(i)" + let answerData = try Constants.data(forAnswer: testName) + XCTAssertEqual(output[i - 1], answerData) + } + } + + func testBZip2MultiDecompressSingleArchive() throws { + for i in 1...5 { + let testName = "test\(i)" + let testData = try Constants.data(forTest: testName, withType: BZip2Tests.testType) + let answerData = try Constants.data(forAnswer: testName) + let output = try BZip2.multiDecompress(data: testData) + XCTAssertEqual(output.first!, answerData) + } + } + + func testBZip2MultiDecompressTruncated() throws { + var input = Data() + for i in 1...5 { + let testName = "test\(i)" + let testData = try Constants.data(forTest: testName, withType: BZip2Tests.testType) + input.append(testData) + } + for _ in 0..<5 { + let truncationIndex = Int.random(in: (input.startIndex + 1).. Date: Tue, 12 May 2026 16:07:43 +0800 Subject: [PATCH 096/106] [swcomp] Add --multi-decompress option to bz2 command This option allows to decompress multiple BZip2 archives concatenated together. --- Sources/swcomp/Archives/BZip2Command.swift | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Sources/swcomp/Archives/BZip2Command.swift b/Sources/swcomp/Archives/BZip2Command.swift index dd17dbd3..5a1daeed 100644 --- a/Sources/swcomp/Archives/BZip2Command.swift +++ b/Sources/swcomp/Archives/BZip2Command.swift @@ -23,12 +23,15 @@ final class BZip2Command: Command { @Flag("-d", "--decompress", description: "Decompress a BZip2 archive") var decompress: Bool + @Flag("-m", "--multi-decompress", description: "Decompress multiple BZip2 archives concatenated together") + var multiDecompress: Bool + @Key("-b", "--block-size", description: "Set the block size for compression to a multiple of 100k bytes; possible " + "values are from '1' (default) to '9'") var blockSize: BZip2.BlockSize? var optionGroups: [OptionGroup] { - return [.exactlyOne($compress, $decompress)] + return [.exactlyOne($compress, $decompress, $multiDecompress)] } @Param var input: String @@ -50,6 +53,28 @@ final class BZip2Command: Command { let fileData = try Data(contentsOf: inputURL, options: .mappedIfSafe) let decompressedData = try BZip2.decompress(data: fileData) try decompressedData.write(to: outputURL) + } else if multiDecompress { + let inputURL = URL(fileURLWithPath: self.input) + + let outputURL: URL + if let outputPath = output { + outputURL = URL(fileURLWithPath: outputPath) + } else if inputURL.pathExtension == "bz2" { + outputURL = inputURL.deletingPathExtension() + } else { + swcompExit(.noOutputPath) + } + + let fileData = try Data(contentsOf: inputURL, options: .mappedIfSafe) + let output = try BZip2.multiDecompress(data: fileData) + + try "".write(to: outputURL, atomically: true, encoding: .utf8) + let handle = try FileHandle(forWritingTo: outputURL) + for data in output { + try handle.write(contentsOf: data) + try handle.synchronize() + } + try handle.close() } else if compress { let inputURL = URL(fileURLWithPath: self.input) From 85c8fd60aaedffd32e67915b38ddcfb11453122f Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Tue, 12 May 2026 17:13:23 +0800 Subject: [PATCH 097/106] [docs] Update BZip2 docs for multiDecompress functionality --- Sources/BZip2/BZip2.swift | 12 ++++++++++++ Sources/BZip2/BZip2Error.swift | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index 7114a2ad..2fad0276 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -25,6 +25,18 @@ public class BZip2: DecompressionAlgorithm { return try decompress(bitReader) } + /** + Decompresses one or more concatenated BZip2 archives found in `data`. + + - Parameter data: Data that may contain one or more concatenated BZip2 archives. + + - Throws: `BZip2Error` if an unexpected byte (bit) sequence was encountered or a checksum mismatch is detected for + any archive. It may indicate that the input is damaged or it might not be compressed with BZip2 at all. If + `BZip2Error.wrongCRC` is thrown, its associated value contains only the bytes decompressed for the archive that + failed the checksum. + + - Returns: An array of decompressed data, one element per archive, in the same order as they appear in the input. + */ public static func multiDecompress(data: Data) throws -> [Data] { let reader = MsbBitReader(data: data) var result = [Data]() diff --git a/Sources/BZip2/BZip2Error.swift b/Sources/BZip2/BZip2Error.swift index c58b90c9..476686b6 100644 --- a/Sources/BZip2/BZip2Error.swift +++ b/Sources/BZip2/BZip2Error.swift @@ -29,8 +29,16 @@ public enum BZip2Error: Error { /// Symbol wasn't found in Huffman tree. case symbolNotFound /** - Computed checksum of uncompressed data doesn't match the value stored in archive. - Associated value of the error contains already decompressed data. + Computed checksum of the uncompressed data does not match the value stored in the archive. + Associated value contains the data that were successfully decompressed up to the point where the mismatch was + detected. + + - When using `BZip2.decompress(data:)`: The associated value comes from a single BZip2 archive. If the mismatch + happens at the final checksum verification, this is usually the entire decompressed output. + + - When using `BZip2.multiDecompress(data:)`: The input may contain several concatenated BZip2 archives. The error + is thrown for the current archive that fails the checksum. The associated value contains only the data decompressed + for that archive. Results from earlier archives are not included and are not returned once this error is thrown. */ case wrongCRC(Data) } From a6419940c1c7e7a5d3a4015fd364318185f61a27 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 14 May 2026 12:39:37 +0800 Subject: [PATCH 098/106] [CI] Add Swift 6.3, update iOS simulator for Swift 6.2 --- azure-pipelines.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5f4c5575..0c6eaf1d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,8 +38,13 @@ stages: macosSwift62: imageName: 'macOS-15' DEVELOPER_DIR: '/Applications/Xcode_26.3.app' - IOS_SIMULATOR: 'iPhone 16' + IOS_SIMULATOR: 'iPhone 17' WATCHOS_SIMULATOR: 'Apple Watch Series 10 (42mm)' + macosSwift63: + imageName: 'macOS-26' + DEVELOPER_DIR: '/Applications/Xcode_26.5.app' + IOS_SIMULATOR: 'iPhone 17' + WATCHOS_SIMULATOR: 'Apple Watch Series 11 (42mm)' pool: vmImage: $(imageName) variables: @@ -95,6 +100,9 @@ stages: linuxSwift62: imageName: 'ubuntu-24.04' containerImage: 'swift:6.2.3-noble' + linuxSwift63: + imageName: 'ubuntu-24.04' + containerImage: 'swift:6.3.2-noble' pool: vmImage: $(imageName) container: $[ variables['containerImage'] ] @@ -123,6 +131,9 @@ stages: windowsSwift62: imageName: 'windows-2022' containerImage: 'swift:6.2.3-windowsservercore-ltsc2022' + windowsSwift63: + imageName: 'windows-2022' + containerImage: 'swift:6.3.2-windowsservercore-ltsc2022' pool: vmImage: $(imageName) container: $[ variables['containerImage'] ] From 735795a7e150c84d0cd8eadbdb94f3bb4ae3f917 Mon Sep 17 00:00:00 2001 From: nekonya Date: Mon, 15 Dec 2025 03:11:07 +0900 Subject: [PATCH 099/106] [7-Zip] Fix LZMA dictionary size parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LZMA dictionary size was being read from only 3 bytes (indices 1-3) instead of the full 4 bytes (indices 1-4) specified in the LZMA format. This caused decompression to fail with LZMAError.notEnoughToRepeat for archives using dictionary sizes >= 16MB (e.g., LZMA:24 = 2^24 = 16MB), because the high byte was not read and the dictionary size defaulted to the minimum 4096 bytes. Change: `for i in 1..<4` → `for i in 1..<5` --- Sources/7-Zip/7zFolder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/7-Zip/7zFolder.swift b/Sources/7-Zip/7zFolder.swift index edcfdd7c..b7fc9b6f 100644 --- a/Sources/7-Zip/7zFolder.swift +++ b/Sources/7-Zip/7zFolder.swift @@ -164,7 +164,7 @@ class SevenZipFolder { else { throw LZMAError.wrongProperties } var dictionarySize = 0 - for i in 1..<4 { + for i in 1..<5 { dictionarySize |= properties[i].toInt() << (8 * (i - 1)) } let lzmaProperties = try LZMAProperties(lzmaByte: properties[0], dictionarySize) From a41fe890765f28531a02226201deefd0bc0961ae Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 14 May 2026 21:33:33 +0800 Subject: [PATCH 100/106] [Tests] Add a test for 7-zip container with large LZMA dictionary size --- SWCompression.xcodeproj/project.pbxproj | 4 ++++ Sources/LZMA/LZMAProperties.swift | 2 +- Tests/SevenZipTests.swift | 11 +++++++++++ Tests/Test Files | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index a874536f..d14c33a6 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -221,6 +221,7 @@ E609D7B6271AFAA400068E79 /* test_small_dict_B5_BD.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E609D7B3271AFAA400068E79 /* test_small_dict_B5_BD.lz4 */; }; E609D7B7271AFAA400068E79 /* test_small_dict_B5.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E609D7B4271AFAA400068E79 /* test_small_dict_B5.lz4 */; }; E60CBA8A26AF379200A20130 /* test_only_dir_header.tar in Resources */ = {isa = PBXBuildFile; fileRef = E60CBA8926AF379200A20130 /* test_only_dir_header.tar */; }; + E61055CF2FB3546600A684F6 /* test_lzma_big_dict.7z in Resources */ = {isa = PBXBuildFile; fileRef = E61055CE2FB3546600A684F6 /* test_lzma_big_dict.7z */; }; E631055927084D5D006EACC3 /* LZ4+Compress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E631055827084D5D006EACC3 /* LZ4+Compress.swift */; }; E6421E8F271B54C6000359B6 /* test_7z_lz4.7z in Resources */ = {isa = PBXBuildFile; fileRef = E6421E8E271B54C6000359B6 /* test_7z_lz4.7z */; }; E644E11E2833AB2400EEFBAD /* minimal.gz in Resources */ = {isa = PBXBuildFile; fileRef = E644E11D2833AB2400EEFBAD /* minimal.gz */; }; @@ -525,6 +526,7 @@ E609D7B3271AFAA400068E79 /* test_small_dict_B5_BD.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_small_dict_B5_BD.lz4; sourceTree = ""; }; E609D7B4271AFAA400068E79 /* test_small_dict_B5.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_small_dict_B5.lz4; sourceTree = ""; }; E60CBA8926AF379200A20130 /* test_only_dir_header.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = test_only_dir_header.tar; sourceTree = ""; }; + E61055CE2FB3546600A684F6 /* test_lzma_big_dict.7z */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_lzma_big_dict.7z; sourceTree = ""; }; E631055827084D5D006EACC3 /* LZ4+Compress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LZ4+Compress.swift"; sourceTree = ""; }; E631055A27086132006EACC3 /* SWCompression.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = SWCompression.xctestplan; path = SWCompression.xcodeproj/SWCompression.xctestplan; sourceTree = SOURCE_ROOT; }; E6421E8E271B54C6000359B6 /* test_7z_lz4.7z */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_7z_lz4.7z; sourceTree = ""; }; @@ -1046,6 +1048,7 @@ 06F066011FFB763300312A82 /* test_7z_bzip2.7z */, 06F066021FFB763300312A82 /* test_7z_deflate.7z */, E6421E8E271B54C6000359B6 /* test_7z_lz4.7z */, + E61055CE2FB3546600A684F6 /* test_lzma_big_dict.7z */, 06F066041FFB763300312A82 /* test_empty_file.7z */, 06F066051FFB763300312A82 /* test_empty_dir.7z */, 06F066061FFB763300312A82 /* test_win.7z */, @@ -1285,6 +1288,7 @@ 06F066671FFB763400312A82 /* test2.7z in Resources */, 062D591421248758003543BD /* test_delta_filter.xz in Resources */, 0698B10B2104E11200A7C551 /* test_gnu_inc_format.tar in Resources */, + E61055CF2FB3546600A684F6 /* test_lzma_big_dict.7z in Resources */, 06F0661F1FFB763300312A82 /* test3.answer in Resources */, 06F066281FFB763400312A82 /* test5.gz in Resources */, 064D01AC20FD077D00CAE058 /* test_unicode_ustar.tar in Resources */, diff --git a/Sources/LZMA/LZMAProperties.swift b/Sources/LZMA/LZMAProperties.swift index 340a586f..b47003c2 100644 --- a/Sources/LZMA/LZMAProperties.swift +++ b/Sources/LZMA/LZMAProperties.swift @@ -18,7 +18,7 @@ public struct LZMAProperties { public var pb: Int = 2 /** - Size of the dictionary. Default value is 1 << 24. + Size of the dictionary. Default value is `1 << 24`. - Note: Dictionary size cannot be less than 4096. In case of attempt to set it to the value less than 4096 it will be automatically set to 4096 instead. diff --git a/Tests/SevenZipTests.swift b/Tests/SevenZipTests.swift index 41fa5e89..ba3a9ad3 100644 --- a/Tests/SevenZipTests.swift +++ b/Tests/SevenZipTests.swift @@ -343,6 +343,17 @@ class SevenZipTests: XCTestCase { XCTAssertEqual(entries[1].data, "Hello, Windows!".data(using: .utf8)) } + func test7z_LzmaBigDict() throws { + // Verifying the issue fixed by PR #61. + // Previously, there was a crash, if LZMA dictionary size was encoded using all 4 bytes. + let testData = try Constants.data(forTest: "test_lzma_big_dict", withType: SevenZipTests.testType) + let entries = try SevenZipContainer.open(container: testData) + XCTAssertEqual(entries.count, 1) + XCTAssertEqual(entries[0].info.name, "data") + XCTAssertEqual(entries[0].info.type, .regular) + XCTAssertEqual(entries[0].info.size, 16 * 1024 * 1024) + } + func testEmptyFile() throws { let testData = try Constants.data(forTest: "test_empty_file", withType: SevenZipTests.testType) let entries = try SevenZipContainer.open(container: testData) diff --git a/Tests/Test Files b/Tests/Test Files index ceb9715d..fae11e64 160000 --- a/Tests/Test Files +++ b/Tests/Test Files @@ -1 +1 @@ -Subproject commit ceb9715d24770ec80a80340ea7af2603a17b3a10 +Subproject commit fae11e64d12cf7c4e0788f6dbe7fadd19005ae8b From 462b4433248e9635db2c2c42336efc6702d78d29 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 21 May 2026 16:57:02 +0800 Subject: [PATCH 101/106] Update BBD version to 2.1.0 --- Package.swift | 2 +- SWCompression.xcodeproj/project.pbxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 27e2c996..b85bfb70 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ var package = Package( targets: ["SWCompression"]), ], dependencies: [ - .package(url: "https://github.com/tsolomko/BitByteData", from: "2.0.0"), + .package(url: "https://github.com/tsolomko/BitByteData", from: "2.1.0"), ], targets: [ .target( diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index d14c33a6..e293c111 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1743,7 +1743,7 @@ repositoryURL = "https://github.com/tsolomko/BitByteData"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.0.4; + minimumVersion = 2.1.0; }; }; /* End XCRemoteSwiftPackageReference section */ From 0da66b6da60b1419dc416b519ba411869fce262d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 21 May 2026 16:58:27 +0800 Subject: [PATCH 102/106] [CI] Upgrade deployment macOS version, remove DEVELOPER_DIR variable Removing DEVELOPER_DIR variable should make CI use the default version of Xcode. Since recently AZP became more aggressive with removing older versions of Xcode from its CI images, relying on default version of Xcode will simplify maintenance of the CI configuration. --- azure-pipelines.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0c6eaf1d..431a8235 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -152,9 +152,7 @@ stages: - job: ghPages displayName: 'Publish API docs to GH Pages' pool: - vmImage: 'macOS-14' - variables: - DEVELOPER_DIR: '/Applications/Xcode_15.2.app' + vmImage: 'macOS-26' steps: - script: | set -e -o xtrace From 0bba830a3d6f92405e96940baf50bd52accfc06d Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Fri, 22 May 2026 14:51:24 +0800 Subject: [PATCH 103/106] [CI] Increase timeout for macOS jobs Since we have to download simulators for some configurations every time, we hit the default timeout of 60 minutes. Increasing the timeout to 90 minutes should help. --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 431a8235..a434cb29 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ stages: displayName: 'Build & Test' jobs: - job: macos + timeoutInMinutes: 90 strategy: matrix: macosSwift59: From 3e78ae765b9f97bb8a4b6b54e0807d7f8208ad36 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Fri, 22 May 2026 17:00:32 +0800 Subject: [PATCH 104/106] Update benchmark results due to a significant hardware update --- Tests/Results.md | 108 +++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 61 deletions(-) diff --git a/Tests/Results.md b/Tests/Results.md index be70fba7..1df39d66 100644 --- a/Tests/Results.md +++ b/Tests/Results.md @@ -1,13 +1,15 @@ # Test Results -In this document you can find the results of benchmarking which was performed on Macbook Air, Retina, 13-inch, Early -2020 with 1,1 GHz Quad-Core Intel Core i5 CPU. The main purpose of these results is to track progress from version to -version. +In this document you can find the results of benchmarking which was performed on Macbook Air M4, 15-inch, 2025. The main +purpose of these results is to track progress from version to version. ## Changelog +__May 2026.__ Replaced the results due to a significant hardware upgrade. + __October 2021.__ Added uncertainties for the results where they were missing; added LZ4 compression and decompression -sections; compression results now also list compression ratio in brackets. +sections; compression results now also list compression ratio in brackets (for the default compression settings unless +indicated otherwise). __September 2021.__ The results are now listed in terms of speed (e.g. MB/s); the benchmarks for Deflate, BZip2 compression and TAR container creation have been added; all previous results have been removed since newer hardware is @@ -36,7 +38,7 @@ in the repository. All tests were run using swcomp's "benchmark" command. SWCompression (and swcomp) were compiled using "Release" configuration. -__Note:__ External commands used to create compressed files were run using their default sets of options. +__Note:__ External commands used to create compressed files were executed with default options. __Note:__ All results are averages over 10 iterations. @@ -44,28 +46,22 @@ __Note:__ All results are averages over 10 iterations. |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|2.366 ± 0.092 MB/s|2.569 ± 0.113 MB/s|2.106 ± 0.088 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|2.552 ± 0.080 MB/s|2.894 ± 0.052 MB/s|2.391 ± 0.062 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|2.764 ± 0.037 MB/s|2.973 ± 0.088 MB/s|2.410 ± 0.088 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|3.279 ± 0.429 MB/s|4.361 ± 0.306 MB/s|3.169 ± 0.180 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|5.225 ± 0.032 MB/s|5.997 ± 0.009 MB/s|5.172 ± 0.021 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|5.495 ± 0.277 MB/s|6.538 ± 0.093 MB/s|5.455 ± 0.138 MB/s| ## XZ Unarchive (LZMA/LZMA2 Decompress) |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|5.006 ± 0.304 MB/s|5.359 ± 0.130 MB/s|5.095 ± 0.220 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|5.210 ± 0.198 MB/s|5.436 ± 0.124 MB/s|5.224 ± 0.176 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|5.246 ± 0.279 MB/s|5.543 ± 0.143 MB/s|5.322 ± 0.260 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|6.015 ± 0.319 MB/s|6.374 ± 0.143 MB/s|5.841 ± 0.212 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|13.722 ± 0.042 MB/s|14.311 ± 0.023 MB/s|13.447 ± 0.162 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|12.712 ± 0.888 MB/s|14.273 ± 0.313 MB/s|14.009 ± 0.047 MB/s| ## GZip Unarchive (Deflate Decompress) |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|4.982 ± 0.128 MB/s|4.994 ± 0.122 MB/s|4.899 ± 0.326 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|5.912 ± 0.232 MB/s|5.997 ± 0.100 MB/s|6.004 ± 0.259 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|5.946 ± 0.432 MB/s|6.034 ± 0.175 MB/s|6.071 ± 0.250 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|8.562 ± 0.388 MB/s|9.908 ± 0.204 MB/s|8.286 ± 0.250 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|11.899 ± 0.067 MB/s|11.487 ± 0.078 MB/s|11.806 ± 0.094 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|13.570 ± 0.015 MB/s|13.392 ± 0.014 MB/s|13.373 ± 0.068 MB/s| ## LZ4 Decompress @@ -76,33 +72,29 @@ this setting may significantly affect performance. |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|17.079 ± 0.943 MB/s|22.253 ± 0.872 MB/s|16.650 ± 0.406 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|17.496 ± 0.817 MB/s|22.480 ± 0.524 MB/s|16.751 ± 0.902 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|54.521 ± 1.030 MB/s|70.316 ± 0.602 MB/s|50.517 ± 0.515 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|54.635 ± 0.315 MB/s|70.268 ± 0.562 MB/s|51.792 ± 0.396 MB/s| ### Dependent blocks |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|17.235 ± 0.720 MB/s|21.056 ± 0.884 MB/s|15.950 ± 0.773 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|17.065 ± 1.109 MB/s|22.508 ± 0.568 MB/s|16.735 ± 0.605 MB/s| - -## BZip2 Compress - -|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| -|:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|3.450 ± 0.088 MB/s (3.493)|2.793 ± 0.034 MB/s (2.635)|4.182 ± 0.084 MB/s (3.875)| -|4.7.0|2.0.1|12.0.1|5.5.1|3.466 ± 0.048 MB/s (3.493)|2.767 ± 0.013 MB/s (2.635)|4.077 ± 0.069 MB/s (3.875)| -|4.6.0|2.0.1|11.5.2|5.4.2|3.540 ± 0.055 MB/s|2.862 ± 0.013 MB/s|4.253 ± 0.077 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|3.816 ± 0.078 MB/s|3.060 ± 0.028 MB/s|4.647 ± 0.085 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|52.712 ± 0.644 MB/s|69.514 ± 0.935 MB/s|49.180 ± 0.818 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|53.845 ± 0.799 MB/s|70.483 ± 0.431 MB/s|51.268 ± 0.401 MB/s| ## Deflate Compress |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|15.354 ± 0.330 MB/s (2.540)|11.420 ± 0.138 MB/s (2.266)|17.306 ± 0.614 MB/s (2.831)| -|4.7.0|2.0.1|12.0.1|5.5.1|15.703 ± 0.538 MB/s (2.540)|11.592 ± 0.219 MB/s (2.266)|17.547 ± 0.731 MB/s (2.831)| -|4.6.0|2.0.1|11.5.2|5.4.2|12.177 ± 0.259 MB/s|8.809 ± 0.088 MB/s|13.594 ± 0.355 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|12.914 ± 0.355 MB/s|9.361 ± 0.078 MB/s|15.020 ± 0.277 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|36.484 ± 0.519 MB/s (2.540)|28.093 ± 0.478 MB/s (2.266)|39.824 ± 1.121 MB/s (2.831)| +|4.9.0|2.1.0|26.5|6.3.2|66.732 ± 0.256 MB/s (2.540)|46.724 ± 1.092 MB/s (2.266)|73.479 ± 0.286 MB/s (2.831)| + +## BZip2 Compress + +|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| +|:---:|:---:|:---:|:---:|---|---|---| +|4.8.6|2.0.4|26.2|6.2.3|8.133 ± 0.146 MB/s (3.493)|5.878 ± 0.031 MB/s (2.635)|9.690 ± 0.098 MB/s (3.875)| +|4.9.0|2.1.0|26.5|6.3.2|9.235 ± 0.162 MB/s (3.493)|6.594 ± 0.017 MB/s (2.635)|11.074 ± 0.009 MB/s (3.875)| ## LZ4 Compress @@ -113,60 +105,54 @@ this setting may significantly affect performance. |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|32.921 ± 0.923 MB/s (2.620)|22.635 ± 0.368 MB/s (2.278)|35.672 ± 2.053 MB/s (2.981)| -|4.7.0|2.0.1|12.0.1|5.5.1|32.692 ± 1.990 MB/s (2.620)|22.702 ± 0.423 MB/s (2.278)|35.405 ± 2.119 MB/s (2.981)| +|4.8.6|2.0.4|26.2|6.2.3|92.564 ± 0.606 MB/s (2.620)|68.459 ± 0.367 MB/s (2.278)|96.908 ± 0.787 MB/s (2.981)| +|4.9.0|2.1.0|26.5|6.3.2|105.739 ± 1.363 MB/s (2.620)|76.388 ± 0.923 MB/s (2.278)|111.100 ± 6.260 MB/s (2.981)| ### Dependent blocks |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|31.991 ± 1.434 MB/s (2.622)|21.973 ± 0.433 MB/s (2.280)|35.187 ± 1.315 MB/s (2.983)| -|4.7.0|2.0.1|12.0.1|5.5.1|32.234 ± 1.673 MB/s (2.622)|21.960 ± 0.531 MB/s (2.280)|34.729 ± 1.996 MB/s (2.983)| +|4.8.6|2.0.4|26.2|6.2.3|91.005 ± 0.527 MB/s (2.622)|67.450 ± 0.180 MB/s (2.280)|95.715 ± 0.621 MB/s (2.983)| +|4.9.0|2.1.0|26.5|6.3.2|104.768 ± 1.075 MB/s (2.622)|75.345 ± 0.304 MB/s (2.280)|111.121 ± 1.698 MB/s (2.983)| ## 7-Zip Info Function |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|89.018 ± 10.089 MB/s|125.155 ± 10.977 MB/s|42.353 ± 1.286 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|86.216 ± 11.562 MB/s|135.372 ± 12.394 MB/s|43.499 ± 1.771 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|77.072 ± 6.625 MB/s|111.246 ± 4.855 MB/s|38.721 ± 2.681 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|82.367 ± 7.381 MB/s|116.923 ± 3.812 MB/s|38.519 ± 2.988 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|805.462 ± 4.777 MB/s|1.714 ± 0.004 GB/s|418.213 ± 1.013 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|835.139 ± 4.655 MB/s|1.818 ± 0.007 GB/s|438.974 ± 1.137 MB/s| ## TAR Info Function |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|953.555 ± 113.704 MB/s|1.016 ± 0.086 GB/s|223.068 ± 15.446 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|963.424 ± 107.448 MB/s|1.035 ± 0.078 GB/s|224.862 ± 8.902 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|967.792 ± 58.436 MB/s|1.006 ± 0.069 GB/s|217.082 ± 19.783 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|1.073 ± 0.115 GB/s|1.053 ± 0.071 GB/s|246.854 ± 6.763 MB/s| - -## TAR Reader - -|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| -|:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|467.082 ± 27.789 MB/s|526.799 ± 47.046 MB/s|139.247 ± 8.510 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|3.520 ± 0.059 GB/s|4.333 ± 0.017 GB/s|936.204 ± 8.830 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|3.647 ± 0.038 GB/s|4.465 ± 0.061 GB/s|953.367 ± 6.691 MB/s| ## ZIP Info Function |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|615.038 ± 90.624 MB/s|1.888 ± 0.224 GB/s|377.717 ± 48.155 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|587.776 ± 159.763 MB/s|2.150 ± 0.148 GB/s|389.327 ± 104.806 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|597.002 ± 75.017 MB/s|1.941 ± 0.135 GB/s|389.626 ± 22.006 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|685.266 ± 53.895 MB/s|2.147 ± 0.097 GB/s|420.461 ± 15.156 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|2.223 ± 0.017 GB/s|6.524 ± 0.031 GB/s|1.373 ± 0.007 GB/s| +|4.9.0|2.1.0|26.5|6.3.2|2.134 ± 0.017 GB/s|6.272 ± 0.075 GB/s|1.327 ± 0.008 GB/s| ## TAR Create Function |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|145.817 ± 10.452 MB/s|486.193 ± 24.090 MB/s|237.893 ± 17.378 MB/s| -|4.7.0|2.0.1|12.0.1|5.5.1|143.668 ± 9.892 MB/s|476.701 ± 26.328 MB/s|233.040 ± 14.406 MB/s| -|4.6.0|2.0.1|11.5.2|5.4.2|139.649 ± 8.310 MB/s|446.101 ± 21.476 MB/s|215.556 ± 17.5 MB/s| -|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|142.681 ± 5.664 MB/s|459.403 ± 17.862 MB/s|220.238 ± 3.476 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|525.215 ± 2.978 MB/s|831.375 ± 8.157 MB/s|1.823 ± 0.063 GB/s| +|4.9.0|2.1.0|26.5|6.3.2|602.061 ± 2.170 MB/s|962.294 ± 2.548 MB/s|2.134 ± 0.083 GB/s| + +## TAR Reader + +|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| +|:---:|:---:|:---:|:---:|---|---|---| +|4.8.6|2.0.4|26.2|6.2.3|1.586 ± 0.017 GB/s|2.063 ± 0.020 GB/s|498.778 ± 3.265 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|1.695 ± 0.022 GB/s|2.200 ± 0.011 GB/s|530.854 ± 2.667 MB/s| ## TAR Writer |SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3| |:---:|:---:|:---:|:---:|---|---|---| -|4.8.0|2.0.1|12.1|5.5.2|19.881 ± 0.766 MB/s|83.901 ± 4.690 MB/s|28.614 ± 2.938 MB/s| +|4.8.6|2.0.4|26.2|6.2.3|36.198 ± 29.341 MB/s|61.920 ± 35.293 MB/s|196.107 ± 64.376 MB/s| +|4.9.0|2.1.0|26.5|6.3.2|46.998 ± 26.850 MB/s|72.109 ± 29.039 MB/s|216.273 ± 23.042 MB/s| From e3c41ca9a06dba65a315341ba58a60a5543e9113 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Fri, 22 May 2026 17:16:29 +0800 Subject: [PATCH 105/106] [BZip2] Fix grammar in comments --- Sources/BZip2/BZip2.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index 2fad0276..c783930a 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -110,11 +110,11 @@ public class BZip2: DecompressionAlgorithm { let pointer = bitReader.int(fromBits: 24) // Decoding which symbols are used in Huffman tables. All possible 256 symbols (0...255) are split into 16 "maps" - // of 16 sequential symbols. Each map is a sequence of 16 bits where a non-zero bits indicate that used symbols. - // A map is omitteed if none of its symbols are in use. First, we decode which maps are present. + // of 16 sequential symbols. Each map is a sequence of 16 bits where non-zero bits indicate used symbols. A map + // is omitted if none of its symbols are in use. First, we decode which maps are present. let usedBlocksBitMap = bitReader.uint16(fromBits: 16) - // For each non-zero bit in the bitmap there should be 16 bits of the block. After that there should be 3 bits - // for the number of Huffman tables and 15 bits for the selectors count. + // For each non-zero bit in the bitmap there should be 16 bits of used symbols. After that there should be 3 + // bits for the number of Huffman tables and 15 bits for selectors count. guard bitReader.bitsLeft >= 16 * usedBlocksBitMap.nonzeroBitCount + 3 + 15 else { throw BZip2Error.wrongMagic } From b2178ac126550b3c7a20b1c803c7861dd6a05e23 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Fri, 22 May 2026 19:44:49 +0800 Subject: [PATCH 106/106] Prepare for 4.9.0 release --- .jazzy.yaml | 4 +-- CHANGELOG.md | 34 +++++++++++++++++-- README.md | 2 +- SWCompression.xcodeproj/SWCompression.plist | 4 +-- .../TestSWCompression.plist | 4 +-- SWCompression.xcodeproj/project.pbxproj | 8 ++--- Sources/swcomp/main.swift | 2 +- 7 files changed, 44 insertions(+), 14 deletions(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 0f17bfc9..96f38b96 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -3,11 +3,11 @@ sourcekitten_sourcefile: docs.json clean: false author: Timofey Solomko module: SWCompression -module_version: 4.8.7 +module_version: 4.9.0 copyright: '© 2026 Timofey Solomko' readme: README.md github_url: https://github.com/tsolomko/SWCompression -github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.8.7 +github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.9.0 theme: fullwidth custom_categories: diff --git a/CHANGELOG.md b/CHANGELOG.md index fbbedec5..008c23a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 4.9.0 + +- Swift versions from 5.3 up to and including 5.8 are no longer supported. +- Cocoapods and Carthage installation methods are no longer supported. +- Enabled visionOS platform for SPM. +- Increased minimum deployment versions for Darwin platforms: macOS from 10.13 to 14.0, iOS from 11.0 to 17.0, tvOS from +11.0 to 17.0, and watchOS from 4.0 to 10.0. +- Increased the lowest required version of BitByteData dependency to 2.1. +- Added `BZip2.multiDecompress` function. +- Improved performance of BZip2 compression and decompression. +- Slighlty improved performance of LZMA/LZMA2 decompression. +- Improved performance of Deflate compression and decompression. +- Fixed potential crashes when processing truncated BZip2 archives. +- Fixed potential crashes when processing truncated Zlib archives. +- Fixed potential crashes when processing truncated or corrupted data compressed with Deflate. +- Fixed incorrect parsing of LZMA dictionary sizes larger than 224-1 in 7-Zip containers (PR #61 by @analytical-engines and @nekonya). +- swcomp changes: + - SPM target is now enabled only on macOS. + - Added `-m`, `--multi-decompress` option to `bz2` command. + - Benchmark metadatas are now printed in a standard order. + - Benchmarking warmup iteration results are now printed and saved into a file. + - Benchmarking raw data is now saved into a file. + - Improved benchmarking output for multi-way comparsions. + - Added `--print-uuid` option to `benchmark show` command. + - Added `--metadata-only` option to `benchmark show` command. + - Added `--self-compare` option to `benchmark show` command. + - Added `benchmark remove-run` command. + - Updated benchmark results file format. + - Added `benchmark convert` command. + ## 4.8.7 - Pinned BitByteData version to 2.0.4. @@ -7,8 +37,8 @@ ## 4.8.6 - Swift 5.2 is no longer supported. -- Enable visionOS deployment target (only for Carthage users). -- Add PrivacyInfo file. +- Enabled visionOS deployment target (only for Carthage users). +- Added PrivacyInfo file. - Fixed a documentation error in `TarEntryInfo.deviceMinorNumber`. ## 4.8.5 diff --git a/README.md b/README.md index d6b47037..1ee5abfc 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ let package = Package( name: "PackageName", dependencies: [ .package(name: "SWCompression", url: "https://github.com/tsolomko/SWCompression.git", - from: "4.8.0") + from: "4.9.0") ], targets: [ .target( diff --git a/SWCompression.xcodeproj/SWCompression.plist b/SWCompression.xcodeproj/SWCompression.plist index 5b7ab03b..6f786191 100644 --- a/SWCompression.xcodeproj/SWCompression.plist +++ b/SWCompression.xcodeproj/SWCompression.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 4.8.7 + 4.9.0 CFBundleVersion - 92 + 93 NSHumanReadableCopyright Copyright © 2026 Timofey Solomko diff --git a/SWCompression.xcodeproj/TestSWCompression.plist b/SWCompression.xcodeproj/TestSWCompression.plist index 232a95cd..2de87aba 100644 --- a/SWCompression.xcodeproj/TestSWCompression.plist +++ b/SWCompression.xcodeproj/TestSWCompression.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 4.8.7 + 4.9.0 CFBundleVersion - 92 + 93 diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 733c8a99..3e3f8614 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1566,7 +1566,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CURRENT_PROJECT_VERSION = 92; + CURRENT_PROJECT_VERSION = 93; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; EAGER_LINKING = YES; @@ -1620,7 +1620,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CURRENT_PROJECT_VERSION = 92; + CURRENT_PROJECT_VERSION = 93; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; EAGER_LINKING = YES; @@ -1653,7 +1653,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 92; + DYLIB_CURRENT_VERSION = 93; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; @@ -1672,7 +1672,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 92; + DYLIB_CURRENT_VERSION = 93; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; diff --git a/Sources/swcomp/main.swift b/Sources/swcomp/main.swift index c75176f0..71edf6d1 100644 --- a/Sources/swcomp/main.swift +++ b/Sources/swcomp/main.swift @@ -7,7 +7,7 @@ import Foundation import SWCompression import SwiftCLI -let _SWC_VERSION = "4.8.7" +let _SWC_VERSION = "4.9.0" let cli = CLI(name: "swcomp", version: _SWC_VERSION, description: """