Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0f5c312
Add explicit Bionic/Android imports for non-Foundation POSIX symbols
odrobnik May 4, 2026
cf7a75a
Guard fwrite empty-buffer case for Bionic's non-optional pointer
odrobnik May 4, 2026
aaed90e
Split MemoryFile funopen stubs for Bionic's strict callback signature
odrobnik May 4, 2026
455963b
Route Android MemoryFile through fopencookie instead of funopen
odrobnik May 4, 2026
3e806c2
Use #include instead of #import in CZLib shim for Windows compatibility
odrobnik May 4, 2026
30a9c29
Guard CoreFoundation import + codepage437 for Windows compatibility
odrobnik May 4, 2026
450b420
Windows POSIX-compat shims: mode_t / S_IF* / fseeko / timegm / zip_off_t
odrobnik May 4, 2026
c7c5c27
fix: import platform libc in PosixCompat for non-Windows zip_off_t
odrobnik May 4, 2026
3d4e519
fix(windows): typed FILE pointer + lstat-free typeForItem
odrobnik May 4, 2026
5c8784e
fix(windows): add ftello shim mapping to _ftelli64
odrobnik May 4, 2026
90823c7
fix(windows): link `zlib` (vcpkg) instead of `z` (libz.so) on Windows
odrobnik May 4, 2026
a88eaa9
fix(android): tmpfile() fallback for MemoryFile
odrobnik May 4, 2026
1e09521
fix(android): unwrap Optional before fwrite in MemoryFile fallback
odrobnik May 4, 2026
63ac29b
experiment(android): drop -lz on Android
odrobnik May 4, 2026
d4a3f6c
Revert "experiment(android): drop -lz on Android"
odrobnik May 4, 2026
fc5a039
fix(memory): disable in-memory archives on Windows/Android
odrobnik May 6, 2026
5b99984
fix(spm): apply zlib link mapping in Package@swift-5.9.swift too
odrobnik May 6, 2026
ab5e2dd
chore: address swiftlint --strict violations
odrobnik May 6, 2026
4a86ab7
fix(tests): gate setSymlinkModificationDate error-condition test to A…
odrobnik May 15, 2026
bf30155
fix(windows,android): align gating around in-memory archives
odrobnik May 15, 2026
e57f0e7
refactor(android): use Android module exclusively, drop Bionic shim
odrobnik May 15, 2026
bfa390f
Make Windows zlib fix >=5.9 only
weichsel May 15, 2026
a8eeb74
Inline Windows attribute retrieval shim
weichsel May 15, 2026
9a9b14c
Move CP437 helper
weichsel May 15, 2026
d7cca17
Avoid negation in compilation conditionals
weichsel May 15, 2026
e19fa59
Cleanup headers
weichsel May 15, 2026
ec2a0d6
Rename shim
weichsel May 15, 2026
a123ed7
Fix Android test compilation
weichsel May 15, 2026
7c16303
Rename attribute retrieval function
weichsel May 15, 2026
c0e0694
Merge pull request #1 from weichsel/fix/android-windows-imports-cleanup
odrobnik May 15, 2026
1f22311
Codestyle
weichsel May 15, 2026
71b4af1
Rename Shims file
weichsel May 15, 2026
09ebc2c
Add explanatory comment
weichsel May 15, 2026
4882a1b
Make Windows shim types internal
weichsel May 15, 2026
1d24e8c
Revert attribute retrieval to prior behavior
weichsel May 15, 2026
a7ef03f
Whitespace
weichsel May 15, 2026
dbb92da
Notify about unsupported platforms in shim
weichsel May 15, 2026
c4f3892
Use shim type in ZIP64 tests
weichsel May 15, 2026
fe938b7
Make `isContained` internal
weichsel May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ let targets: [Target] = [
]
#else
let targets: [Target] = [
.systemLibrary(name: "CZLib", pkgConfig: "zlib", providers: [.brew(["zlib"]), .apt(["zlib"])]),
.target(name: "ZIPFoundation", dependencies: ["CZLib"], cSettings: [.define("_GNU_SOURCE", to: "1")]),
.systemLibrary(
name: "CZLib",
pkgConfig: "zlib",
providers: [.brew(["zlib"]), .apt(["zlib"])]),
.target(
name: "ZIPFoundation",
dependencies: ["CZLib"],
cSettings: [.define("_GNU_SOURCE", to: "1")],
linkerSettings: [.linkedLibrary("z")]),
.testTarget(name: "ZIPFoundationTests", dependencies: ["ZIPFoundation"])
]
#endif
Expand Down
40 changes: 24 additions & 16 deletions Package@swift-5.9.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
// swift-tools-version:5.9
import PackageDescription

#if canImport(Compression)
let targets: [Target] = [
.target(name: "ZIPFoundation",
resources: [
.copy("Resources/PrivacyInfo.xcprivacy")
]),
.testTarget(name: "ZIPFoundationTests", dependencies: ["ZIPFoundation"])
]
#else
let targets: [Target] = [
.systemLibrary(name: "CZLib", pkgConfig: "zlib", providers: [.brew(["zlib"]), .apt(["zlib"])]),
.target(name: "ZIPFoundation", dependencies: ["CZLib"], cSettings: [.define("_GNU_SOURCE", to: "1")]),
.testTarget(name: "ZIPFoundationTests", dependencies: ["ZIPFoundation"])
]
#endif
let zlibPlatforms: [Platform] = [.linux, .android, .windows]
let unixZlibPlatforms: [Platform] = [.linux, .android]

let package = Package(
name: "ZIPFoundation",
Expand All @@ -25,6 +12,27 @@ let package = Package(
products: [
.library(name: "ZIPFoundation", targets: ["ZIPFoundation"])
],
targets: targets,
targets: [
.target(
name: "ZIPFoundation",
dependencies: [
.target(name: "CZLib", condition: .when(platforms: zlibPlatforms))
],
resources: [
.copy("Resources/PrivacyInfo.xcprivacy")
],
cSettings: [
.define("_GNU_SOURCE", to: "1", .when(platforms: unixZlibPlatforms))
],
linkerSettings: [
.linkedLibrary("z", .when(platforms: unixZlibPlatforms)),
.linkedLibrary("zlib", .when(platforms: [.windows]))
]),
.systemLibrary(
name: "CZLib",
pkgConfig: "zlib",
providers: [.brew(["zlib"]), .apt(["zlib"])]),
.testTarget(name: "ZIPFoundationTests", dependencies: ["ZIPFoundation"])
],
swiftLanguageVersions: [.v4, .v4_2, .v5]
)
1 change: 0 additions & 1 deletion Sources/CZLib/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@

module CZlib {
header "shim.h"
link "z"
export *
}
10 changes: 7 additions & 3 deletions Sources/CZLib/shim.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
//

#ifndef zlib_shim_h
#ifndef zlib_shim_h
#define zlib_shim_h

#import <stdio.h>
#import <zlib.h>
// `#include` instead of `#import`: clang on MSVC parses `#import` as
// the Microsoft type-library directive (not the Apple-style header
// once-include) and rejects it. `#include` works identically here on
// every other clang flavour.
#include <stdio.h>
#include <zlib.h>

// [zlib] provide 64-bit offset functions if _LARGEFILE64_SOURCE defined
#ifndef _LARGEFILE64_SOURCE
Expand Down
7 changes: 5 additions & 2 deletions Sources/ZIPFoundation/Archive+BackingConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@
//

import Foundation
#if canImport(Android)
import Android
#endif

extension Archive {

struct BackingConfiguration {
let file: FILEPointer
let endOfCentralDirectoryRecord: EndOfCentralDirectoryRecord
let zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory?
#if swift(>=5.0)
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))
let memoryFile: MemoryFile?

init(file: FILEPointer,
Expand Down Expand Up @@ -82,7 +85,7 @@ extension Archive {
}
}

#if swift(>=5.0)
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))
static func makeBackingConfiguration(for data: Data, mode: AccessMode) throws
-> BackingConfiguration {
let memoryFile = MemoryFile(data: data)
Expand Down
7 changes: 5 additions & 2 deletions Sources/ZIPFoundation/Archive+Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// Archive+Deprecated.swift
// ZIPFoundation
//
// Created by Thomas Zoechling on 06.02.23.
// Copyright © 2017-2026 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
// Released under the MIT License.
//
// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
//

import Foundation
Expand All @@ -14,7 +17,7 @@ public extension Archive {
try? self.init(url: url, accessMode: mode, pathEncoding: preferredEncoding)
}

#if swift(>=5.0)
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))
@available(*, deprecated, message: "Please use the throwing initializer.")
convenience init?(data: Data = Data(), accessMode mode: AccessMode, preferredEncoding: String.Encoding? = nil) {
try? self.init(data: data, accessMode: mode, pathEncoding: preferredEncoding)
Expand Down
9 changes: 6 additions & 3 deletions Sources/ZIPFoundation/Archive+MemoryFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ extension Archive {
var isMemoryArchive: Bool { return self.url.scheme == memoryURLScheme }
}

#if swift(>=5.0)
// In-memory archives rely on a `FILE*`-shaped userspace stream API:
// `funopen` on Apple, `fopencookie` on Linux glibc. File-backed archives
// continue to work on platforms without a compatible userspace stream API.
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))

extension Archive {

Expand All @@ -29,7 +32,7 @@ extension Archive {

func open(mode: AccessMode) throws -> FILEPointer {
let cookie = Unmanaged.passRetained(self)
#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Android)
#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS)
guard let result = mode.isWritable
? funopen(cookie.toOpaque(), readStub, writeStub, seekStub, closeStub)
: funopen(cookie.toOpaque(), readStub, nil, seekStub, closeStub)
Expand Down Expand Up @@ -106,7 +109,7 @@ private func closeStub(_ cookie: UnsafeMutableRawPointer?) -> Int32 {
return 0
}

#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Android)
#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS)

private func readStub(_ cookie: UnsafeMutableRawPointer?,
_ bytePtr: UnsafeMutablePointer<Int8>?,
Expand Down
5 changes: 4 additions & 1 deletion Sources/ZIPFoundation/Archive+Reading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
//

import Foundation
#if canImport(Android)
import Android
#endif

extension Archive {
/// Read a ZIP `Entry` from the receiver and write it to `url`.
Expand Down Expand Up @@ -93,7 +96,7 @@ extension Archive {
var checksum = CRC32(0)
let localFileHeader = entry.localFileHeader
guard entry.dataOffset <= .max else { throw ArchiveError.invalidLocalHeaderDataOffset }
fseeko(self.archiveFile, off_t(entry.dataOffset), SEEK_SET)
fseeko(self.archiveFile, zip_off_t(entry.dataOffset), SEEK_SET)
progress?.totalUnitCount = self.totalUnitCountForReading(entry)
switch entry.type {
case .file:
Expand Down
21 changes: 12 additions & 9 deletions Sources/ZIPFoundation/Archive+Writing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
//

import Foundation
#if canImport(Android)
import Android
#endif

extension Archive {
enum ModifyOperation: Int {
Expand Down Expand Up @@ -124,10 +127,10 @@ extension Archive {
let (eocdRecord, zip64EOCD) = (self.endOfCentralDirectoryRecord, self.zip64EndOfCentralDirectory)
guard self.offsetToStartOfCentralDirectory <= .max else { throw ArchiveError.invalidCentralDirectoryOffset }
var startOfCD = Int64(self.offsetToStartOfCentralDirectory)
fseeko(self.archiveFile, off_t(startOfCD), SEEK_SET)
fseeko(self.archiveFile, zip_off_t(startOfCD), SEEK_SET)
let existingSize = self.sizeOfCentralDirectory
let existingData = try Data.readChunk(of: Int(existingSize), from: self.archiveFile)
fseeko(self.archiveFile, off_t(startOfCD), SEEK_SET)
fseeko(self.archiveFile, zip_off_t(startOfCD), SEEK_SET)
let fileHeaderStart = Int64(ftello(self.archiveFile))
guard fileHeaderStart >= 0 else { throw ArchiveError.unwritableArchive }

Expand All @@ -146,12 +149,12 @@ extension Archive {
guard startOfCD >= 0 else { throw ArchiveError.unwritableArchive }

// Write the local file header a second time. Now with compressedSize (if applicable) and a valid checksum.
fseeko(self.archiveFile, off_t(fileHeaderStart), SEEK_SET)
fseeko(self.archiveFile, zip_off_t(fileHeaderStart), SEEK_SET)
localFileHeader = try self.writeLocalFileHeader(path: path, compressionMethod: compressionMethod,
size: (UInt64(uncompressedSize), UInt64(written)),
checksum: checksum, modificationDateTime: modDateTime)
// Central Directory
fseeko(self.archiveFile, off_t(startOfCD), SEEK_SET)
fseeko(self.archiveFile, zip_off_t(startOfCD), SEEK_SET)
_ = try Data.writeLargeChunk(existingData, size: existingSize, bufferSize: bufferSize, to: archiveFile)
let permissions = permissions ?? (type == .directory ? defaultDirectoryPermissions : defaultFilePermissions)
let externalAttributes = FileManager.externalFileAttributesForEntry(of: type, permissions: permissions)
Expand Down Expand Up @@ -191,7 +194,7 @@ extension Archive {
let cds = currentEntry.centralDirectoryStructure
if currentEntry != entry {
let entryStart = cds.effectiveRelativeOffsetOfLocalHeader
fseeko(self.archiveFile, off_t(entryStart), SEEK_SET)
fseeko(self.archiveFile, zip_off_t(entryStart), SEEK_SET)
let provider: Provider = { (_, chunkSize) -> Data in
return try Data.readChunk(of: chunkSize, from: self.archiveFile)
}
Expand Down Expand Up @@ -230,7 +233,7 @@ extension Archive {

func replaceCurrentArchive(with archive: Archive) throws {
if self.isMemoryArchive {
#if swift(>=5.0)
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))
guard let data = archive.data else {
throw ArchiveError.unwritableArchive
}
Expand Down Expand Up @@ -280,8 +283,8 @@ private extension Archive {
_ bufferSize: Int, _ endOfCentralDirRecord: EndOfCentralDirectoryRecord,
_ zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory?) throws {
fflush(self.archiveFile)
ftruncate(fileno(self.archiveFile), off_t(localFileHeaderStart))
fseeko(self.archiveFile, off_t(localFileHeaderStart), SEEK_SET)
ftruncate(fileno(self.archiveFile), zip_off_t(localFileHeaderStart))
fseeko(self.archiveFile, zip_off_t(localFileHeaderStart), SEEK_SET)
_ = try Data.writeLargeChunk(existingCentralDirectory.data, size: existingCentralDirectory.size,
bufferSize: bufferSize, to: archiveFile)
_ = try Data.write(chunk: existingCentralDirectory.data, to: self.archiveFile)
Expand All @@ -295,7 +298,7 @@ private extension Archive {
var archive: Archive
var url: URL?
if self.isMemoryArchive {
#if swift(>=5.0)
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))
archive = try Archive(data: Data(), accessMode: .create,
pathEncoding: self.pathEncoding)
#else
Expand Down
7 changes: 5 additions & 2 deletions Sources/ZIPFoundation/Archive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
//

import Foundation
#if canImport(Android)
import Android
#endif

/// The default chunk size when reading entry data from an archive.
public let defaultReadChunkSize = Int(16*1024)
Expand Down Expand Up @@ -187,7 +190,7 @@ public final class Archive: Sequence {
setvbuf(self.archiveFile, nil, _IOFBF, Int(defaultPOSIXBufferSize))
}

#if swift(>=5.0)
#if swift(>=5.0) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux))
var memoryFile: MemoryFile?

/// Initializes a new in-memory ZIP `Archive`.
Expand Down Expand Up @@ -287,7 +290,7 @@ public final class Archive: Sequence {
guard archiveLength >= 0 else { return nil }

while eocdOffset == 0 && index < maxDirectoryEndOffset && index <= archiveLength {
fseeko(file, off_t(archiveLength - index), SEEK_SET)
fseeko(file, zip_off_t(archiveLength - index), SEEK_SET)
var potentialDirectoryEndTag: UInt32 = UInt32()
fread(&potentialDirectoryEndTag, 1, MemoryLayout<UInt32>.size, file)
if potentialDirectoryEndTag == UInt32(endOfCentralDirectoryStructSignature) {
Expand Down
8 changes: 7 additions & 1 deletion Sources/ZIPFoundation/Data+Serialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
//

import Foundation
#if canImport(Android)
import Android
#endif

// The Swift Android SDK imports `FILE` as opaque; everywhere else
// (including Windows MSVC, where it's `_iobuf`) it's a typed struct so
// we can take a typed pointer.
#if os(Android)
public typealias FILEPointer = OpaquePointer
#else
Expand Down Expand Up @@ -37,7 +43,7 @@ extension Data {
static func readStruct<T>(from file: FILEPointer, at offset: UInt64)
-> T? where T: DataSerializable {
guard offset <= .max else { return nil }
fseeko(file, off_t(offset), SEEK_SET)
fseeko(file, zip_off_t(offset), SEEK_SET)
guard let data = try? self.readChunk(of: T.size, from: file) else {
return nil
}
Expand Down
12 changes: 11 additions & 1 deletion Sources/ZIPFoundation/Date+ZIP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
// Date+ZIP.swift
// ZIPFoundation
//
// Created by Thomas Zoechling on 20.12.22.
// Copyright © 2017-2026 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
// Released under the MIT License.
//
// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
//

import Foundation
#if canImport(Android)
import Android
#endif

extension Date {

Expand Down Expand Up @@ -55,12 +61,14 @@ extension Date {
self = Date(timeIntervalSince1970: TimeInterval(time))
}

#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux) || os(Android)
init(timespec: timespec) {
let seconds = 1.0e-9 * Double(timespec.tv_nsec)
let timeIntervalSince1970 = TimeInterval(timespec.tv_sec)
let absoluteTimeIntervalSince1970 = Constants.absoluteTimeIntervalSince1970
self.init(timeIntervalSinceReferenceDate: (timeIntervalSince1970 - absoluteTimeIntervalSince1970) + seconds)
}
#endif
}

private extension Date {
Expand All @@ -74,6 +82,7 @@ private extension Date {
}
}

#if os(macOS) || os(iOS) || os(tvOS) || os(visionOS) || os(watchOS) || os(Linux) || os(Android)
extension stat {

var lastAccessDate: Date {
Expand All @@ -92,3 +101,4 @@ extension timeval {
self.init(tv_sec: time_t(integral), tv_usec: suseconds_t(1.0e6 * fractional))
}
}
#endif
Loading
Loading