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/ 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/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/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/Package.swift b/Package.swift index 2e2020db..27e2c996 100644 --- a/Package.swift +++ b/Package.swift @@ -1,15 +1,14 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 import PackageDescription -let package = Package( +var package = Package( name: "SWCompression", platforms: [ - .macOS(.v10_13), - .iOS(.v11), - .tvOS(.v11), - .watchOS(.v4), - // TODO: Enable after upgrading to Swift 5.9. - // .visionOS(.v1) + .macOS(.v14), + .iOS(.v17), + .tvOS(.v17), + .watchOS(.v10), + .visionOS(.v1) ], products: [ .library( @@ -17,10 +16,7 @@ let package = Package( targets: ["SWCompression"]), ], 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"), + .package(url: "https://github.com/tsolomko/BitByteData", from: "2.0.0"), ], targets: [ .target( @@ -30,12 +26,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(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 diff --git a/README.md b/README.md index ce180397..fdcf456c 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.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) @@ -32,11 +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. -### Swift Package Manager +__Note:__ SWCompression versions 4.8.6 and earlier were also made available via CocoaPods or Carthage. -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 @@ -58,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 @@ -157,7 +105,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. @@ -171,9 +119,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: @@ -187,14 +132,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 diff --git a/SWCompression.podspec b/SWCompression.podspec deleted file mode 100644 index 562de57b..00000000 --- a/SWCompression.podspec +++ /dev/null @@ -1,95 +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 = "11.0" - s.osx.deployment_target = "10.13" - s.tvos.deployment_target = "11.0" - s.watchos.deployment_target = "4.0" - # s.visionos.deployment_target = "1.0" - - s.swift_versions = ["5"] - - s.dependency "BitByteData", "~> 2.0" - - 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 - -end 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/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 3e4b7fc1..7315276f 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -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 */; }; @@ -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 */; }; @@ -276,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 */; }; @@ -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 = ""; }; @@ -531,7 +531,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 = ""; }; @@ -594,6 +593,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E6DCC9D52F30DA020042AE2E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E6DCC9D82F30DA080042AE2E /* BitByteData in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -676,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; @@ -695,9 +702,9 @@ 06BE1ABE1DB410F100EE0F59 = { isa = PBXGroup; children = ( - E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */, 06BE1ACA1DB410F100EE0F59 /* Sources */, 06F065A01FFB763300312A82 /* Tests */, + E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */, E631055A27086132006EACC3 /* SWCompression.xctestplan */, 06BE1AC91DB410F100EE0F59 /* Products */, ); @@ -844,7 +851,6 @@ E6522FB52789AFA600026B56 /* TarWriterTests.swift */, 06F065A61FFB763300312A82 /* TarTests.swift */, E6522FB32789AF9600026B56 /* TarReaderTests.swift */, - E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */, 06F065A91FFB763300312A82 /* XzTests.swift */, 06F065A41FFB763300312A82 /* ZipTests.swift */, 06D42DA62067B71000C1A98B /* TestZipExtraField.swift */, @@ -1128,10 +1134,12 @@ buildPhases = ( 06BE1AC31DB410F100EE0F59 /* Sources */, E6E49D512BD5081700BFC756 /* CopyFiles */, + E6DCC9D52F30DA020042AE2E /* Frameworks */, ); buildRules = ( ); dependencies = ( + E6DCC9DA2F30DB4E0042AE2E /* PBXTargetDependency */, ); name = SWCompression; productName = SWCompression; @@ -1145,7 +1153,6 @@ 06F065911FFB761600312A82 /* Sources */, 06F065921FFB761600312A82 /* Frameworks */, 06F065931FFB761600312A82 /* Resources */, - 062BE32F1FFBADB300343CAD /* Copy BitByteData.framework into test bundle */, ); buildRules = ( ); @@ -1165,7 +1172,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 2620; ORGANIZATIONNAME = "Timofey Solomko"; TargetAttributes = { 06BE1AC71DB410F100EE0F59 = { @@ -1179,7 +1186,7 @@ }; }; buildConfigurationList = 06BE1AC21DB410F100EE0F59 /* Build configuration list for PBXProject "SWCompression" */; - compatibilityVersion = "Xcode 11.4"; + compatibilityVersion = "Xcode 15.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -1187,6 +1194,9 @@ Base, ); mainGroup = 06BE1ABE1DB410F100EE0F59; + packageReferences = ( + E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */, + ); productRefGroup = 06BE1AC91DB410F100EE0F59 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1358,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-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"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 06BE1AC31DB410F100EE0F59 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1455,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 */, @@ -1506,7 +1497,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 */, @@ -1521,6 +1511,10 @@ target = 06BE1AC71DB410F100EE0F59 /* SWCompression */; targetProxy = 06F0659B1FFB761600312A82 /* PBXContainerItemProxy */; }; + E6DCC9DA2F30DB4E0042AE2E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = E6DCC9D92F30DB4E0042AE2E /* BitByteData */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -1554,19 +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_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=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\""; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1575,22 +1557,18 @@ 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 = 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"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 17.6; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 4.0; - XROS_DEPLOYMENT_TARGET = 1.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Debug; }; @@ -1623,19 +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_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=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\""; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; @@ -1643,21 +1609,17 @@ 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; - OTHER_CODE_SIGN_FLAGS = "--deep"; - OTHER_LDFLAGS = ( - "-framework", - BitByteData, - ); + IPHONEOS_DEPLOYMENT_TARGET = 17.6; + MACOSX_DEPLOYMENT_TARGET = 14.6; 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; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 17.6; VERSIONING_SYSTEM = "apple-generic"; - WATCHOS_DEPLOYMENT_TARGET = 4.0; - XROS_DEPLOYMENT_TARGET = 1.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; + XROS_DEPLOYMENT_TARGET = 1.3; }; name = Release; }; @@ -1672,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; @@ -1702,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; @@ -1726,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)"; }; @@ -1743,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)"; }; @@ -1786,6 +1712,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 */; } diff --git a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme index 7232fa36..1b98aff8 100644 --- a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme +++ b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme @@ -1,6 +1,6 @@ > 4): adds to saltSize +/// - lower nibble (b1 & 0x0F): adds to ivSize +/// - saltSize = ((b0 >> 7) & 1) + (b1 >> 4) +/// - ivSize = ((b0 >> 6) & 1) + (b1 & 0x0F) +/// - Remaining bytes: salt (saltSize bytes) + IV (ivSize bytes) +/// +/// Key derivation (from CalcKey): +/// buf = salt + password_as_raw_bytes + 8_zero_bytes (counter placeholder) +/// For each round (0..<2^numCyclesPower): +/// write round counter as UInt32 LE at buf[saltSize+passwordSize..saltSize+passwordSize+4] +/// (upper 4 bytes of 8-byte counter remain zero since numRounds fits in UInt32) +/// SHA256.update(buf) +/// key = SHA256.finalize() +enum SevenZipAESDecryptor { + + /// Decrypt data using 7z AES-256-CBC with the given password and coder properties. + static func decrypt(data: Data, properties: [UInt8], password: String) throws -> Data { + guard !properties.isEmpty else { + throw SevenZipError.internalStructureError + } + + // Parse properties - matching 7-Zip's SetDecoderProperties2 + let b0 = properties[0] + let numCyclesPower = Int(b0 & 0x3F) + + var saltSize = 0 + var ivSize = 0 + + if (b0 & 0xC0) != 0 { + // Has salt/IV size info + guard properties.count > 1 else { + throw SevenZipError.internalStructureError + } + let b1 = properties[1] + saltSize = Int((b0 >> 7) & 1) + Int(b1 >> 4) + ivSize = Int((b0 >> 6) & 1) + Int(b1 & 0x0F) + + guard properties.count == 2 + saltSize + ivSize else { + throw SevenZipError.internalStructureError + } + } + + // Extract salt + var salt = Data() + if saltSize > 0 { + salt = Data(properties[2..<(2 + saltSize)]) + } + + // Extract IV (pad to 16 bytes with zeros) + var iv = Data(count: 16) + if ivSize > 0 { + let ivStart = 2 + saltSize + for i in 0.. Data + { + if numCyclesPower == 0x3F { + // Special case: direct key from salt + password (no hashing) + var key = Data(count: 32) + var pos = 0 + for i in 0..> 8) & 0xFF) + buf[counterOffset + 2] = UInt8((round >> 16) & 0xFF) + buf[counterOffset + 3] = UInt8((round >> 24) & 0xFF) + + buf.withUnsafeBytes { ptr in + _ = CC_SHA256_Update(&context, ptr.baseAddress, CC_LONG(bufSize)) + } + } + + var hash = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) + hash.withUnsafeMutableBytes { ptr in + _ = CC_SHA256_Final(ptr.bindMemory(to: UInt8.self).baseAddress, &context) + } + + return hash + } + + /// AES-256-CBC decryption using CommonCrypto. + private static func aes256CBCDecrypt(data: Data, key: Data, iv: Data) throws -> Data { + let outputLength = data.count + kCCBlockSizeAES128 + var outputData = Data(count: outputLength) + var numBytesDecrypted: size_t = 0 + + let status = outputData.withUnsafeMutableBytes { outputPtr in + data.withUnsafeBytes { dataPtr in + key.withUnsafeBytes { keyPtr in + iv.withUnsafeBytes { ivPtr in + CCCrypt( + CCOperation(kCCDecrypt), + CCAlgorithm(kCCAlgorithmAES), + CCOptions(0), // No padding — 7z handles padding via data trimming + keyPtr.baseAddress, key.count, + ivPtr.baseAddress, + dataPtr.baseAddress, data.count, + outputPtr.baseAddress, outputLength, + &numBytesDecrypted + ) + } + } + } + } + + guard status == kCCSuccess else { + throw SevenZipError.internalStructureError + } + + outputData.count = numBytesDecrypted + return outputData + } +} diff --git a/Sources/7-Zip/7zCoder+Equatable.swift b/Sources/7-Zip/7zCoder+Equatable.swift index 466713f1..619dd0f0 100644 --- a/Sources/7-Zip/7zCoder+Equatable.swift +++ b/Sources/7-Zip/7zCoder+Equatable.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 19b76995..483eb068 100644 --- a/Sources/7-Zip/7zCoder.swift +++ b/Sources/7-Zip/7zCoder.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 44255eb1..d160080a 100644 --- a/Sources/7-Zip/7zCoderInfo.swift +++ b/Sources/7-Zip/7zCoderInfo.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData class SevenZipCoderInfo { diff --git a/Sources/7-Zip/7zContainer.swift b/Sources/7-Zip/7zContainer.swift index 0f251711..e0f49fa1 100644 --- a/Sources/7-Zip/7zContainer.swift +++ b/Sources/7-Zip/7zContainer.swift @@ -1,10 +1,10 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData +import Foundation /// Provides functions for work with 7-Zip containers. public class SevenZipContainer: Container { @@ -13,23 +13,28 @@ public class SevenZipContainer: Container { /** Processes 7-Zip container and returns an array of `SevenZipEntry` with information and data for all entries. - + - Important: The order of entries is defined by 7-Zip container and, particularly, by the creator of a given 7-Zip container. It is likely that directories will be encountered earlier than files stored in those directories, but no particular order is guaranteed. - + - Parameter container: 7-Zip container's data. - + - Throws: `SevenZipError` or any other error associated with compression type depending on the type of the problem. It may indicate that either container is damaged or it might not be 7-Zip container at all. - + - Returns: Array of `SevenZipEntry`. */ public static func open(container data: Data) throws -> [SevenZipEntry] { + return try open(container: data, password: nil) + } + + /// Processes 7-Zip container with an optional password for encrypted archives. + public static func open(container data: Data, password: String?) throws -> [SevenZipEntry] { var entries = [SevenZipEntry]() - guard let header = try readHeader(data), + guard let header = try readHeader(data, password: password), let files = header.fileInfo?.files - else { return [] } + else { return [] } /// Total count of non-empty files. Used to iterate over SubstreamInfo. var nonEmptyFileIndex = 0 @@ -56,7 +61,9 @@ public class SevenZipContainer: Container { for file in files { if file.isEmptyStream { - let info = file.isEmptyFile && !file.isAntiFile ? SevenZipEntryInfo(file, 0) : SevenZipEntryInfo(file) + let info = + file.isEmptyFile && !file.isAntiFile + ? SevenZipEntryInfo(file, 0) : SevenZipEntryInfo(file) let data = file.isEmptyFile && !file.isAntiFile ? Data() : nil entries.append(SevenZipEntry(info, data)) continue @@ -65,7 +72,7 @@ public class SevenZipContainer: Container { // Without `SevenZipStreamInfo` and `SevenZipPackInfo` we cannot find file data in container. guard let streamInfo = header.mainStreams, let packInfo = streamInfo.packInfo - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } // SubstreamInfo is required to get files' data, and without it we can only return files' info. guard let substreamInfo = streamInfo.substreamInfo else { @@ -75,7 +82,7 @@ public class SevenZipContainer: Container { // Check if there is enough folders. guard folderIndex < streamInfo.coderInfo.numFolders - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } /// Folder which contains current file. let folder = streamInfo.coderInfo.folders[folderIndex] @@ -89,7 +96,7 @@ public class SevenZipContainer: Container { // enountered in the same order, as they are placed in the container. Thus, we have to start moving // to stream's offset from the beginning. // TODO: Is this correct or the order of streams is guaranteed? - byteReader.offset = signatureHeaderSize + packInfo.packPosition // Pack offset. + byteReader.offset = signatureHeaderSize + packInfo.packPosition // Pack offset. if streamIndex != 0 { for i in 0..= folder.numUnpackSubstreams { // If we read all files in folder... + if folderFileIndex >= folder.numUnpackSubstreams { // If we read all files in folder... // Check folder's unpacked size as well as its CRC32 (if it is available). guard folderUnpackSize == folder.unpackSize() - else { throw SevenZipError.wrongSize } + else { throw SevenZipError.wrongSize } if let storedFolderCRC = folder.crc { guard folderCRC == storedFolderCRC - else { throw SevenZipError.wrongCRC } + else { throw SevenZipError.wrongCRC } } // Reset folder's unpack size and CRC32. folderCRC = CheckSums.crc32(Data()) @@ -165,31 +174,38 @@ public class SevenZipContainer: Container { /** Processes 7-Zip container and returns an array of `SevenZipEntryInfo` with information about entries in this container. - + - Important: The order of entries is defined by 7-Zip container and, particularly, by the creator of a given 7-Zip container. It is likely that directories will be encountered earlier than files stored in those directories, but no particular order is guaranteed. - + - Parameter container: 7-Zip container's data. - + - Throws: `SevenZipError` or any other error associated with compression type depending on the type of the problem. It may indicate that either container is damaged or it might not be 7-Zip container at all. - + - Returns: Array of `SevenZipEntryInfo`. */ public static func info(container data: Data) throws -> [SevenZipEntryInfo] { + return try info(container: data, password: nil) + } + + /// Processes 7-Zip container info with an optional password for encrypted archives. + public static func info(container data: Data, password: String?) throws -> [SevenZipEntryInfo] { var entries = [SevenZipEntryInfo]() - guard let header = try readHeader(data), + guard let header = try readHeader(data, password: password), let files = header.fileInfo?.files - else { return [] } + else { return [] } var nonEmptyFileIndex = 0 for file in files { if !file.isEmptyStream, let substreamInfo = header.mainStreams?.substreamInfo { - let size = nonEmptyFileIndex < substreamInfo.unpackSizes.count ? - substreamInfo.unpackSizes[nonEmptyFileIndex] : nil - let crc = nonEmptyFileIndex < substreamInfo.digests.count ? - substreamInfo.digests[nonEmptyFileIndex] : nil + let size = + nonEmptyFileIndex < substreamInfo.unpackSizes.count + ? substreamInfo.unpackSizes[nonEmptyFileIndex] : nil + let crc = + nonEmptyFileIndex < substreamInfo.digests.count + ? substreamInfo.digests[nonEmptyFileIndex] : nil entries.append(SevenZipEntryInfo(file, size, crc)) nonEmptyFileIndex += 1 } else { @@ -201,10 +217,11 @@ public class SevenZipContainer: Container { return entries } - private static func readHeader(_ data: Data) throws -> SevenZipHeader? { + private static func readHeader(_ data: Data, password: String? = nil) throws -> SevenZipHeader? + { // Valid 7-Zip container must contain at least a SignatureHeader, which is 32 bytes long. guard data.count >= 32 - else { throw SevenZipError.wrongSignature } + else { throw SevenZipError.wrongSignature } let bitReader = MsbBitReader(data: data) @@ -212,13 +229,13 @@ public class SevenZipContainer: Container { // Check signature. guard bitReader.bytes(count: 6) == [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C] - else { throw SevenZipError.wrongSignature } + else { throw SevenZipError.wrongSignature } // Check archive version. let majorVersion = bitReader.byte() let minorVersion = bitReader.byte() guard majorVersion == 0 && minorVersion > 0 && minorVersion <= 4 - else { throw SevenZipError.wrongFormatVersion } + else { throw SevenZipError.wrongFormatVersion } let startHeaderCRC = bitReader.uint32() @@ -229,12 +246,12 @@ public class SevenZipContainer: Container { bitReader.offset -= 20 guard CheckSums.crc32(bitReader.bytes(count: 20)) == startHeaderCRC - else { throw SevenZipError.wrongCRC } + else { throw SevenZipError.wrongCRC } // **Header** bitReader.offset += nextHeaderOffset if bitReader.isFinished { - return nil // In case of completely empty container. + return nil // In case of completely empty container. } let headerStartIndex = bitReader.offset @@ -245,7 +262,8 @@ public class SevenZipContainer: Container { if type == 0x17 { let packedHeaderStreamInfo = try SevenZipStreamInfo(bitReader) headerEndIndex = bitReader.offset - header = try SevenZipHeader(bitReader, using: packedHeaderStreamInfo) + header = try SevenZipHeader( + bitReader, using: packedHeaderStreamInfo, password: password) } else if type == 0x01 { header = try SevenZipHeader(bitReader) headerEndIndex = bitReader.offset @@ -255,12 +273,12 @@ public class SevenZipContainer: Container { // Check header size guard headerEndIndex - headerStartIndex == nextHeaderSize - else { throw SevenZipError.wrongSize } + else { throw SevenZipError.wrongSize } // Check header CRC bitReader.offset = headerStartIndex guard CheckSums.crc32(bitReader.bytes(count: nextHeaderSize)) == nextHeaderCRC - else { throw SevenZipError.wrongCRC } + else { throw SevenZipError.wrongCRC } return header } 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..29fce7b8 100644 --- a/Sources/7-Zip/7zError.swift +++ b/Sources/7-Zip/7zError.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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/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..64e176c1 100644 --- a/Sources/7-Zip/7zFolder.swift +++ b/Sources/7-Zip/7zFolder.swift @@ -1,10 +1,10 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData +import Foundation class SevenZipFolder { @@ -53,7 +53,7 @@ class SevenZipFolder { } guard totalOutputStreams != 0 - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } numBindPairs = totalOutputStreams - 1 if numBindPairs > 0 { @@ -63,7 +63,7 @@ class SevenZipFolder { } guard totalInputStreams >= numBindPairs - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } numPackedStreams = totalInputStreams - numBindPairs if numPackedStreams == 1 { @@ -135,11 +135,11 @@ class SevenZipFolder { return 0 } - func unpack(data: Data) throws -> Data { + func unpack(data: Data, password: String? = nil) throws -> Data { var decodedData = data for coder in self.orderedCoders() { guard coder.numInStreams == 1 || coder.numOutStreams == 1 - else { throw SevenZipError.multiStreamNotSupported } + else { throw SevenZipError.multiStreamNotSupported } let unpackSize = self.unpackSize(for: coder) @@ -162,30 +162,33 @@ class SevenZipFolder { // Dictionary size is stored in coder's properties. guard let properties = coder.properties, properties.count == 1 - else { throw LZMA2Error.wrongDictionarySize } + else { throw LZMA2Error.wrongDictionarySize } - decodedData = try LZMA2.decompress(LittleEndianByteReader(data: decodedData), properties[0]) + decodedData = try LZMA2.decompress( + LittleEndianByteReader(data: decodedData), properties[0]) case .lzma: // Both properties' byte (lp, lc, pb) and dictionary size are stored in coder's properties. guard let properties = coder.properties, properties.count == 5 - else { throw LZMAError.wrongProperties } + else { throw LZMAError.wrongProperties } var dictionarySize = 0 for i in 1..<4 { dictionarySize |= properties[i].toInt() << (8 * (i - 1)) } let lzmaProperties = try LZMAProperties(lzmaByte: properties[0], dictionarySize) - decodedData = try LZMA.decompress(data: decodedData, - properties: lzmaProperties, - uncompressedSize: unpackSize) + decodedData = try LZMA.decompress( + data: decodedData, + properties: lzmaProperties, + uncompressedSize: unpackSize) default: if coder.id == [0x03] { guard let properties = coder.properties, properties.count == 1 - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } - decodedData = DeltaFilter.decode(LittleEndianByteReader(data: decodedData), (properties[0] &+ 1).toInt()) + 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) @@ -193,14 +196,29 @@ class SevenZipFolder { throw SevenZipError.compressionNotSupported #endif } else if coder.isEncryptionMethod { - throw SevenZipError.encryptionNotSupported + guard let password = password, !password.isEmpty else { + throw SevenZipError.encryptionNotSupported + } + guard let properties = coder.properties else { + throw SevenZipError.internalStructureError + } + decodedData = try SevenZipAESDecryptor.decrypt( + data: decodedData, + properties: properties, + password: password + ) + // AES-CBC output may have padding; trim to expected unpack size. + if decodedData.count > unpackSize { + decodedData = decodedData.prefix(unpackSize) + } + continue // Skip the size check below since we already trimmed. } else { throw SevenZipError.compressionNotSupported } } guard decodedData.count == unpackSize - else { throw SevenZipError.wrongSize } + else { throw SevenZipError.wrongSize } } return decodedData } diff --git a/Sources/7-Zip/7zHeader.swift b/Sources/7-Zip/7zHeader.swift index 1a50cdba..587c2408 100644 --- a/Sources/7-Zip/7zHeader.swift +++ b/Sources/7-Zip/7zHeader.swift @@ -1,10 +1,10 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData +import Foundation class SevenZipHeader { @@ -36,31 +36,33 @@ class SevenZipHeader { } guard type == 0x00 - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } } - convenience init(_ bitReader: MsbBitReader, using streamInfo: SevenZipStreamInfo) throws { + convenience init( + _ bitReader: MsbBitReader, using streamInfo: SevenZipStreamInfo, password: String? = nil + ) throws { let folder = streamInfo.coderInfo.folders[0] guard let packInfo = streamInfo.packInfo - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } let folderOffset = SevenZipContainer.signatureHeaderSize + packInfo.packPosition bitReader.offset = folderOffset let packedHeaderData = Data(bitReader.bytes(count: packInfo.packSizes[0])) - let headerData = try folder.unpack(data: packedHeaderData) + let headerData = try folder.unpack(data: packedHeaderData, password: password) guard headerData.count == folder.unpackSize() - else { throw SevenZipError.wrongSize } + else { throw SevenZipError.wrongSize } if let crc = folder.crc { guard CheckSums.crc32(headerData) == crc - else { throw SevenZipError.wrongCRC } + else { throw SevenZipError.wrongCRC } } let headerBitReader = MsbBitReader(data: headerData) guard headerBitReader.byte() == 0x01 - else { throw SevenZipError.internalStructureError } + else { throw SevenZipError.internalStructureError } try self.init(headerBitReader) } diff --git a/Sources/7-Zip/7zPackInfo.swift b/Sources/7-Zip/7zPackInfo.swift index 9998bcd6..6ccd01b5 100644 --- a/Sources/7-Zip/7zPackInfo.swift +++ b/Sources/7-Zip/7zPackInfo.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 b817adc4..c26aa9dc 100644 --- a/Sources/7-Zip/7zProperty.swift +++ b/Sources/7-Zip/7zProperty.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 f8bca63b..a978efd5 100644 --- a/Sources/7-Zip/7zStreamInfo.swift +++ b/Sources/7-Zip/7zStreamInfo.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 1bcc715d..eed68239 100644 --- a/Sources/7-Zip/7zSubstreamInfo.swift +++ b/Sources/7-Zip/7zSubstreamInfo.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 103775ad..aeff7fa0 100644 --- a/Sources/7-Zip/CompressionMethod+7z.swift +++ b/Sources/7-Zip/CompressionMethod+7z.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 55d4bfbf..2a22679e 100644 --- a/Sources/7-Zip/MsbBitReader+7z.swift +++ b/Sources/7-Zip/MsbBitReader+7z.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 63b310aa..a477bbef 100644 --- a/Sources/BZip2/BZip2+BlockSize.swift +++ b/Sources/BZip2/BZip2+BlockSize.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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+Compress.swift b/Sources/BZip2/BZip2+Compress.swift index 676e971e..5de8c9e7 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 @@ -41,17 +41,18 @@ 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'. - 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 for i in stride(from: data.startIndex, to: data.endIndex, by: rawBlockSize) { - let blockData = data[i..> 31) totalCRC ^= blockCRC @@ -60,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. @@ -72,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) @@ -121,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 }) @@ -147,13 +148,19 @@ 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: UInt8(0), count: 16) + // 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) @@ -162,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 { @@ -174,10 +181,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.. 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 lastLength > length { + // Bits: 11 -> 11_11 -> 11_11_11 -> 11_11_11_11 -> ... + // Numbers: 3 -> 15 -> 63 -> 255 -> ... + // https://oeis.org/A024036 + let diff = lastLength - length + bitWriter.write(unsignedNumber: (1 << (2 * diff)) - 1, bitsCount: 2 * diff) + } else if lastLength < length { + // Bits: 10 -> 10_10 -> 10_10_10 -> 10_10_10_10 -> ... + // Numbers: 2 -> 10 -> 42 -> 170 -> ... + // https://oeis.org/A020988 + let diff = length - lastLength + bitWriter.write(unsignedNumber: ((1 << (2 * diff)) - 1) * 2 / 3 , bitsCount: 2 * diff) } + lastLength = length bitWriter.write(bit: 0) } } // 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 } } /// 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.. [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.. 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,15 +45,15 @@ 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) if blockType == 0x314159265359 { - let blockData = try decode(bitReader, blockSize) - out.append(blockData) - guard CheckSums.bzip2crc32(blockData) == blockCRC32 + let block = try decode(bitReader, blockSize) + out.append(Data(block)) + guard CheckSums.bzip2crc32(block) == blockCRC32 else { throw BZip2Error.wrongCRC(out) } totalCRC = (totalCRC << 1) | (totalCRC >> 31) totalCRC ^= blockCRC32 @@ -68,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 } @@ -146,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) } @@ -220,7 +221,7 @@ public class BZip2: DecompressionAlgorithm { } } - return Data(out) + return out } } 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..a3029e4b 100644 --- a/Sources/BZip2/BurrowsWheeler.swift +++ b/Sources/BZip2/BurrowsWheeler.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 5b1c0f26..f1d22647 100644 --- a/Sources/BZip2/SuffixArray.swift +++ b/Sources/BZip2/SuffixArray.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation - /// Suffix Array by Induced Sorting enum SuffixArray { 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..d61fdc98 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 @@ -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/CodingTree/Code.swift b/Sources/Common/CodingTree/Code.swift index 8992126f..adaad351 100644 --- a/Sources/Common/CodingTree/Code.swift +++ b/Sources/Common/CodingTree/Code.swift @@ -1,9 +1,9 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation +typealias HuffmanCodes = (codes: [Code], maxBits: Int) struct Code { @@ -13,7 +13,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/CodeLength.swift b/Sources/Common/CodingTree/CodeLength.swift index eabfa41a..348c3b23 100644 --- a/Sources/Common/CodingTree/CodeLength.swift +++ b/Sources/Common/CodingTree/CodeLength.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 a502baa1..0f60ef09 100644 --- a/Sources/Common/CodingTree/DecodingTree.swift +++ b/Sources/Common/CodingTree/DecodingTree.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData final class DecodingTree { @@ -13,14 +12,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/Common/CodingTree/EncodingTree.swift b/Sources/Common/CodingTree/EncodingTree.swift index 3fb51133..ca53a27d 100644 --- a/Sources/Common/CodingTree/EncodingTree.swift +++ b/Sources/Common/CodingTree/EncodingTree.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData fileprivate struct CodingIndex { @@ -19,7 +18,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/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..10127da9 100644 --- a/Sources/Common/CompressionMethod.swift +++ b/Sources/Common/CompressionMethod.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation - /// Represents a (de)compression method. public enum CompressionMethod { /// BZip2. 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..28211283 100644 --- a/Sources/Common/Extensions.swift +++ b/Sources/Common/Extensions.swift @@ -1,10 +1,19 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information import Foundation +extension Data { + + @inlinable @inline(__always) + func toByteArray() -> [UInt8] { + return self.withUnsafeBytes { $0.map { $0 } } + } + +} + extension UnsignedInteger { @inlinable @inline(__always) @@ -32,10 +41,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 diff --git a/Sources/Common/FileSystemType.swift b/Sources/Common/FileSystemType.swift index 97a2dc55..de6de79c 100644 --- a/Sources/Common/FileSystemType.swift +++ b/Sources/Common/FileSystemType.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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+Compress.swift b/Sources/Deflate/Deflate+Compress.swift index f7842601..6b7e5e88 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 @@ -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 } @@ -106,9 +95,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(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(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 0c4c9ff3..4d0176ee 100644 --- a/Sources/Deflate/Deflate+Constants.swift +++ b/Sources/Deflate/Deflate+Constants.swift @@ -1,17 +1,15 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation - 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 +152,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 +170,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+Lengths.swift b/Sources/Deflate/Deflate+Lengths.swift index dd265c6b..dd2425f3 100644 --- a/Sources/Deflate/Deflate+Lengths.swift +++ b/Sources/Deflate/Deflate+Lengths.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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/Deflate.swift b/Sources/Deflate/Deflate.swift index 56b6cf4c..60c2970a 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 @@ -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.. (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..> 31) self.code = self.code &+ (self.range & t) - if self.code == self.range { - self.isCorrupted = true - } - self.normalize() res <<= 1 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..e6b08e78 100644 --- a/Sources/LZMA2/LZMA2Decoder.swift +++ b/Sources/LZMA2/LZMA2Decoder.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData struct LZMA2Decoder { diff --git a/Sources/LZMA2/LZMA2Error.swift b/Sources/LZMA2/LZMA2Error.swift index 636b513c..03fd9bf3 100644 --- a/Sources/LZMA2/LZMA2Error.swift +++ b/Sources/LZMA2/LZMA2Error.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 1602d3be..788c13ce 100644 --- a/Sources/TAR/ContainerEntryType+Tar.swift +++ b/Sources/TAR/ContainerEntryType+Tar.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation - extension ContainerEntryType { init(_ fileTypeIndicator: UInt8) { 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..7b95959c 100644 --- a/Sources/TAR/LittleEndianByteReader+Tar.swift +++ b/Sources/TAR/LittleEndianByteReader+Tar.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData extension LittleEndianByteReader { diff --git a/Sources/TAR/TarContainer.swift b/Sources/TAR/TarContainer.swift index 8c2db113..61fcc593 100644 --- a/Sources/TAR/TarContainer.swift +++ b/Sources/TAR/TarContainer.swift @@ -1,10 +1,9 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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/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..271d0afb 100644 --- a/Sources/TAR/TarError.swift +++ b/Sources/TAR/TarError.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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/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 565ce4b7..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 @@ -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..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 @@ -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/XZ/ByteReader+XZ.swift b/Sources/XZ/LittleEndianByteReader+XZ.swift similarity index 92% rename from Sources/XZ/ByteReader+XZ.swift rename to Sources/XZ/LittleEndianByteReader+XZ.swift index b8de7d96..5c135756 100644 --- a/Sources/XZ/ByteReader+XZ.swift +++ b/Sources/XZ/LittleEndianByteReader+XZ.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData extension LittleEndianByteReader { 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..32d8ca3b 100644 --- a/Sources/XZ/XZStreamHeader.swift +++ b/Sources/XZ/XZStreamHeader.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData struct XZStreamHeader { 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..685a5477 100644 --- a/Sources/ZIP/CompressionMethod+Zip.swift +++ b/Sources/ZIP/CompressionMethod+Zip.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 a0e93e83..62caadb7 100644 --- a/Sources/ZIP/FileSystemType+Zip.swift +++ b/Sources/ZIP/FileSystemType+Zip.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation - extension FileSystemType { init(_ versionMadeBy: UInt16) { 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..4333a038 100644 --- a/Sources/ZIP/ZipCentralDirectoryEntry.swift +++ b/Sources/ZIP/ZipCentralDirectoryEntry.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData struct ZipCentralDirectoryEntry { diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift index 8d555b30..92300590 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 @@ -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 } diff --git a/Sources/ZIP/ZipEndOfCentralDirectory.swift b/Sources/ZIP/ZipEndOfCentralDirectory.swift index 4a7c135b..f0d6efab 100644 --- a/Sources/ZIP/ZipEndOfCentralDirectory.swift +++ b/Sources/ZIP/ZipEndOfCentralDirectory.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData struct ZipEndOfCentralDirectory { 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..2e1ce4e1 100644 --- a/Sources/ZIP/ZipEntryInfoHelper.swift +++ b/Sources/ZIP/ZipEntryInfoHelper.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData /** diff --git a/Sources/ZIP/ZipError.swift b/Sources/ZIP/ZipError.swift index be19107f..4d03b807 100644 --- a/Sources/ZIP/ZipError.swift +++ b/Sources/ZIP/ZipError.swift @@ -1,10 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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/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..8c420779 100644 --- a/Sources/ZIP/ZipLocalHeader.swift +++ b/Sources/ZIP/ZipLocalHeader.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import BitByteData struct ZipLocalHeader { 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..695b9d3c 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 @@ -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/BenchmarkGroup.swift b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift index 4e9707fd..40eb0aa8 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 @@ -14,7 +14,9 @@ final class BenchmarkGroup: CommandGroup { let children: [Routable] = [ RunBenchmarkCommand(), - ShowBenchmarkCommand() + ShowBenchmarkCommand(), + RemoveRunCommand(), + ConvertCommand() ] } 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..d26c2902 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 @@ -12,6 +12,8 @@ struct BenchmarkResult: Codable { var iterCount: Int 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/Benchmarks.swift b/Sources/swcomp/Benchmarks/Benchmarks.swift index 98ab8f51..502bfe06 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 @@ -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) @@ -594,7 +586,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 +621,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/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/OldSaveFile.swift b/Sources/swcomp/Benchmarks/OldSaveFile.swift new file mode 100644 index 00000000..dbaba7a3 --- /dev/null +++ b/Sources/swcomp/Benchmarks/OldSaveFile.swift @@ -0,0 +1,26 @@ +// Copyright (c) 2026 Timofey Solomko +// Licensed under MIT License +// +// See LICENSE for license information + +import Foundation + +struct OldSaveFile: Codable { + + struct Run: Codable { + + var metadataUUID: UUID + var results: [BenchmarkResult] + + } + + var metadatas: [UUID: BenchmarkMetadata] + var runs: [Run] + + static func load(from path: String) throws -> OldSaveFile { + let decoder = JSONDecoder() + let data = try Data(contentsOf: URL(fileURLWithPath: path)) + return try decoder.decode(OldSaveFile.self, from: data) + } + +} diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift new file mode 100644 index 00000000..c2d89e04 --- /dev/null +++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift @@ -0,0 +1,33 @@ +// Copyright (c) 2026 Timofey Solomko +// Licensed under MIT License +// +// See LICENSE for license information + +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) } + 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.") + } + } + +} diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift index ae9c95f5..ffa16220 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 @@ -8,7 +8,6 @@ #endif import Foundation -import SWCompression import SwiftCLI final class RunBenchmarkCommand: Command { @@ -32,7 +31,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") @@ -45,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 SaveFile.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: { $0.value < $1.value }) { - 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 }) - } + 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)) @@ -77,10 +63,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 @@ -90,6 +80,7 @@ final class RunBenchmarkCommand: Command { #if !os(Linux) fflush(__stdoutp) #endif + var iterations = [Double]() for i in 1...iterationCount { if i > 1 { print(", ", terminator: "") @@ -101,18 +92,23 @@ 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) + 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))") - for (other, baseUUID) in baseResults { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") - result.printComparison(with: other) + 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(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std))") + } + result.printComparison(with: baseResult) } } else { print("\nAverage = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))") @@ -129,32 +125,41 @@ 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) } saveFile = try SaveFile.load(from: savePath) - var uuid: UUID - if let foundUUID = saveFile.metadatas.first(where: { $0.value == metadata })?.key { - uuid = foundUUID + 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(SaveFile.Run(metadataUUID: uuid, results: newResults)) } else { let uuid = UUID() - saveFile = SaveFile(metadatas: [uuid: metadata], runs: [SaveFile.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() - encoder.outputFormatting = .prettyPrinted - + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let data = try encoder.encode(saveFile) try data.write(to: URL(fileURLWithPath: savePath)) } } + 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 6dd7a434..2226d052 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 @@ -9,19 +9,63 @@ struct SaveFile: Codable { struct Run: Codable { - var metadataUUID: UUID + var uuid: UUID + var metadata: BenchmarkMetadata var results: [BenchmarkResult] } - var metadatas: [UUID: BenchmarkMetadata] - + 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 }))) + } + } + + init(runs: [SaveFile.Run]) { + self.runs = runs + } + 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) + } + } + + 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 bf023699..473e2677 100644 --- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift +++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift @@ -1,12 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -#if os(Linux) - import CoreFoundation -#endif - import Foundation import SwiftCLI @@ -18,64 +14,92 @@ 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 + + @Flag("--metadata-only", description: "Prints only metadata of saved benchmark runs") + var metadataOnly: Bool + @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] = "" - } - for (metadataUUID, index) in newMetadatas.sorted(by: { $0.value < $1.value }) { - print("NEW\(index) Metadata") - print("---------------") - newSaveFile.metadatas[metadataUUID]!.print() - } - - var newResults = [String: [(BenchmarkResult, UUID)]]() - for newRun in newSaveFile.runs { - newResults.merge(Dictionary(grouping: newRun.results.map { ($0, newRun.metadataUUID) }, by: { $0.0.id }), - uniquingKeysWith: { $0 + $1 }) + var newRuns: [SaveFile.Run] + if let newUUIDString = self.selfCompare { + guard let newUUID = UUID(uuidString: newUUIDString) + else { swcompExit(.benchmarkBadUUID) } + guard let newRun = newSaveFile.runs.first(where: { $0.uuid == newUUID} ) + else { swcompExit(.benchmarkNoUUID) } + newRuns = [newRun] + } else { + newRuns = newSaveFile.runs } + self.printMetadatas(runs: newRuns, name: "NEW") - var baseResults = [String: [(BenchmarkResult, UUID)]]() - var baseMetadatas = [UUID: String]() + var baseRuns = [SaveFile.Run]() if let comparePath = comparePath { let baseSaveFile = try SaveFile.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: { $0.value < $1.value }) { - 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 }) - } + baseRuns = baseSaveFile.runs + } else if let newUUIDString = self.selfCompare { + // 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 }) } - - for resultId in newResults.keys.sorted() { - let results = newResults[resultId]! - for (result, metadataUUID) in results { + self.printMetadatas(runs: baseRuns, name: "BASE") + + guard !self.metadataOnly + else { return } + + // 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.first!.1.name) => \(results.first!.1.input), iterations = \(results.first!.1.iterCount)") + print() + for (index, result) in results { 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 { - print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))") - result.printComparison(with: other) + if let warmup = result.warmup { + print("NEW(\(index + 1)): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std)), warmup = \(benchmark.format(warmup))") + } else { + print("NEW(\(index + 1)): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))") + } + 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(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std))") + } + result.printComparison(with: baseResult) } } + } + } + } - print() + 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() } } 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..89db6a9d 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 @@ -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 +} 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 a01b6645..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 @@ -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/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..5a3f66d3 100644 --- a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 345f27b0..e885adad 100644 --- a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import SWCompression extension ContainerEntryInfo where Self: CustomStringConvertible { diff --git a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift b/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift deleted file mode 100644 index 941930f8..00000000 --- a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2024 Timofey Solomko -// Licensed under MIT License -// -// See LICENSE for license informations - -import Foundation - -extension FileHandle { - - func closeCompat() throws { - #if compiler(<5.2) - 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 - } - -} diff --git a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift index 4a2cb0d0..6c0f40c8 100644 --- a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // 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 a4631594..a0cc5fa2 100644 --- a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift +++ b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import SWCompression extension GzipHeader: CustomStringConvertible { 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..5d1e9427 100644 --- a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift +++ b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift @@ -1,9 +1,8 @@ -// Copyright (c) 2024 Timofey Solomko +// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information -import Foundation import SwiftCLI import SWCompression diff --git a/Sources/swcomp/SwcompError.swift b/Sources/swcomp/SwcompError.swift index 5a88f023..33b740d6 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 @@ -19,6 +19,12 @@ enum SwcompError { case benchmarkReaderTarNoInputSize(String) case benchmarkCannotGetSubcommandPathWindows case benchmarkCannotAppendToDirectory + case benchmarkBadUUID + case benchmarkNoUUID + case benchmarkUnrecognizedSaveFile + case benchmarkUnrecognizedFormatVersion + case benchmarkUnsupportedFormatVersion(Int) + case benchmarkOldFormatNoUUIDMetadata(UUID) case containerSymLinkDestPath(String) case containerHardLinkDestPath(String) case containerNoEntryData(String) @@ -54,6 +60,18 @@ enum SwcompError { return 206 case .benchmarkCannotAppendToDirectory: return 207 + case .benchmarkBadUUID: + return 208 + case .benchmarkNoUUID: + return 218 + case .benchmarkUnrecognizedSaveFile: + return 209 + case .benchmarkUnrecognizedFormatVersion: + return 219 + case .benchmarkUnsupportedFormatVersion: + return 229 + case .benchmarkOldFormatNoUUIDMetadata: + return 239 case .containerSymLinkDestPath: return 301 case .containerHardLinkDestPath: @@ -99,6 +117,18 @@ 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 .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 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): 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 12d89bf7..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 @@ -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 { 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/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/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 2bf525cd..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 @@ -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/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 b19de81a..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 @@ -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) } 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 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3d5dc588..eb7f3d00 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,55 +15,31 @@ 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' - 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' + imageName: 'macOS-14' DEVELOPER_DIR: '/Applications/Xcode_15.2.app' - IOS_SIMULATOR: 'iPhone 14' - WATCHOS_ACTIONS: 'clean test' + IOS_SIMULATOR: 'iPhone 15' 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)' + macosSwift510: + imageName: 'macOS-14' + DEVELOPER_DIR: '/Applications/Xcode_15.4.app' + IOS_SIMULATOR: 'iPhone 15' + WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' + macosSwift60: + imageName: 'macOS-15' + 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: @@ -90,8 +66,13 @@ stages: git lfs pull git lfs checkout displayName: 'Download or update test files' - - script: ./utils.py download-bbd-macos - displayName: 'Download BitByteData' + - 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 ci script-macos displayName: 'Build & Test' - script: swift build -c release @@ -99,30 +80,21 @@ 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' - linuxSwift56: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.6.3-focal' - linuxSwift57: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.7.3-focal' - linuxSwift58: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.8.1-focal' linuxSwift59: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.9.2-focal' + imageName: 'ubuntu-22.04' + containerImage: 'swift:5.9.2-jammy' linuxSwift510: - imageName: 'ubuntu-20.04' - containerImage: 'swift:5.10-focal' + imageName: 'ubuntu-24.04' + containerImage: 'swift:5.10.1-noble' + 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'] ] @@ -136,62 +108,29 @@ 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' - ICU_PATH: 'C:\Library\icu-67\usr\bin' - SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin' - windowsSwift56: - imageName: 'windows-2019' - SWIFT_VERSION: '5.6.3' - ICU_PATH: 'C:\Library\icu-69.1\usr\bin' - SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin' - 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' - 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' 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' - # TODO: There seems to be an unknown problem with the Swift installation step. + imageName: 'windows-2022' + containerImage: 'swift:5.9.2-windowsservercore-ltsc2022' # windowsSwift510: - # imageName: 'windows-2019' - # SWIFT_VERSION: '5.10' - # ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' - # SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' + # imageName: 'windows-2022' + # containerImage: 'swift:5.10.1-windowsservercore-ltsc2022' + 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) - 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' + container: $[ variables['containerImage'] ] 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%;%PATH% swift.exe --version - swift.exe build --target SWCompression %SWIFTFLAGS% - swift.exe build -c release --target SWCompression %SWIFTFLAGS% + swift.exe build + swift.exe build -c release displayName: 'Build SPM Debug & Release' - stage: deploy displayName: Deploy @@ -202,9 +141,9 @@ stages: - job: ghPages displayName: 'Publish API docs to GH Pages' pool: - vmImage: 'macOS-12' + vmImage: 'macOS-14' variables: - DEVELOPER_DIR: '/Applications/Xcode_14.2.app' + DEVELOPER_DIR: '/Applications/Xcode_15.2.app' steps: - script: | set -e -o xtrace diff --git a/utils.py b/utils.py index aef6e96d..d582adeb 100755 --- a/utils.py +++ b/utils.py @@ -23,9 +23,9 @@ 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"] + "'"], [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: @@ -43,40 +43,16 @@ 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"]) -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.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]) - 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() @@ -114,15 +90,6 @@ def action_pr(args): 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") -parser_dbm.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")