Skip to content

Commit c0c4208

Browse files
committed
Workaround in ZipArchiver when posix_spawn_file_actions_addchdir_np is unavailable
Motivation: `swift package-registry publish` tool doesn't work in Amazon Linux 2 because it has an older version of Glibc that doesn't support `posix_spawn_file_actions_addchdir_np`. Modifications: Add workaround in `ZipArchiver` that does `cd <working directory> && zip ...` when `posix_spawn_file_actions_addchdir_np` is not available.
1 parent 6995433 commit c0c4208

File tree

2 files changed

+117
-20
lines changed

2 files changed

+117
-20
lines changed

Sources/Basics/Archiver/ZipArchiver.swift

Lines changed: 104 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -11,9 +11,18 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Dispatch
14+
import class Foundation.Pipe
15+
import class Foundation.Process
16+
import struct Foundation.URL
1417
import struct TSCBasic.FileSystemError
1518
import class TSCBasic.Process
1619

20+
#if USE_IMPL_ONLY_IMPORTS
21+
@_implementationOnly import TSCclibc
22+
#else
23+
import TSCclibc
24+
#endif
25+
1726
/// An `Archiver` that handles ZIP archives using the command-line `zip` and `unzip` tools.
1827
public struct ZipArchiver: Archiver, Cancellable {
1928
public var supportedExtensions: Set<String> { ["zip"] }
@@ -90,32 +99,108 @@ public struct ZipArchiver: Archiver, Cancellable {
9099
arguments: ["tar.exe", "-a", "-c", "-f", destinationPath.pathString, directory.basename],
91100
workingDirectory: directory.parentDirectory.underlying
92101
)
102+
try self.launchAndWait(process: process, completion: completion)
103+
#elseif os(Linux)
104+
// This is to work around `swift package-registry publish` tool failing on
105+
// Amazon Linux 2 due to it having an earlier Glibc version (rdar://116370323)
106+
// and therefore posix_spawn_file_actions_addchdir_np is unavailable.
107+
// Instead of TSC.Process, we shell out to Foundation.Process and do `cd`
108+
// explicitly before `zip`.
109+
if SPM_posix_spawn_file_actions_addchdir_np_supported() {
110+
try self.compress_zip(
111+
directory: directory,
112+
destinationPath: destinationPath,
113+
completion: completion
114+
)
115+
} else {
116+
let process = Foundation.Process()
117+
process.executableURL = URL(fileURLWithPath: "/bin/sh")
118+
process.arguments = [
119+
"-c",
120+
"cd \(directory.parentDirectory.underlying.pathString) && zip -r \(destinationPath.pathString) \(directory.basename)",
121+
]
122+
123+
let stdoutPipe = Pipe()
124+
let stderrPipe = Pipe()
125+
process.standardOutput = stdoutPipe
126+
process.standardError = stderrPipe
127+
128+
try self.launchAndWait(
129+
process: process,
130+
stdoutPipe: stdoutPipe,
131+
stderrPipe: stderrPipe,
132+
completion: completion
133+
)
134+
}
93135
#else
94-
let process = TSCBasic.Process(
95-
arguments: ["zip", "-r", destinationPath.pathString, directory.basename],
96-
workingDirectory: directory.parentDirectory.underlying
136+
try self.compress_zip(
137+
directory: directory,
138+
destinationPath: destinationPath,
139+
completion: completion
97140
)
98141
#endif
99-
100-
guard let registrationKey = self.cancellator.register(process) else {
101-
throw CancellationError.failedToRegisterProcess(process)
102-
}
103-
104-
DispatchQueue.sharedConcurrent.async {
105-
defer { self.cancellator.deregister(registrationKey) }
106-
completion(.init(catching: {
107-
try process.launch()
108-
let processResult = try process.waitUntilExit()
109-
guard processResult.exitStatus == .terminated(code: 0) else {
110-
throw try StringError(processResult.utf8stderrOutput())
111-
}
112-
}))
113-
}
114142
} catch {
115143
return completion(.failure(error))
116144
}
117145
}
118146

147+
private func compress_zip(
148+
directory: AbsolutePath,
149+
destinationPath: AbsolutePath,
150+
completion: @escaping (Result<Void, Error>) -> Void
151+
) throws {
152+
let process = TSCBasic.Process(
153+
arguments: ["zip", "-r", destinationPath.pathString, directory.basename],
154+
workingDirectory: directory.parentDirectory.underlying
155+
)
156+
try self.launchAndWait(process: process, completion: completion)
157+
}
158+
159+
private func launchAndWait(
160+
process: TSCBasic.Process,
161+
completion: @escaping (Result<Void, Error>) -> Void
162+
) throws {
163+
guard let registrationKey = self.cancellator.register(process) else {
164+
throw CancellationError.failedToRegisterProcess(process)
165+
}
166+
167+
DispatchQueue.sharedConcurrent.async {
168+
defer { self.cancellator.deregister(registrationKey) }
169+
completion(.init(catching: {
170+
try process.launch()
171+
let processResult = try process.waitUntilExit()
172+
guard processResult.exitStatus == .terminated(code: 0) else {
173+
throw try StringError(processResult.utf8stderrOutput())
174+
}
175+
}))
176+
}
177+
}
178+
179+
#if os(Linux)
180+
private func launchAndWait(
181+
process: Foundation.Process,
182+
stdoutPipe: Pipe,
183+
stderrPipe: Pipe,
184+
completion: @escaping (Result<Void, Error>) -> Void
185+
) throws {
186+
guard let registrationKey = self.cancellator.register(process) else {
187+
throw CancellationError.failedToRegisterProcess(process)
188+
}
189+
190+
DispatchQueue.sharedConcurrent.async {
191+
defer { self.cancellator.deregister(registrationKey) }
192+
completion(.init(catching: {
193+
try process.run()
194+
process.waitUntilExit()
195+
guard process.terminationStatus == 0 else {
196+
let stderr = stderrPipe.fileHandleForReading.readDataToEndOfFile()
197+
throw StringError(String(decoding: stderr, as: UTF8.self))
198+
}
199+
}))
200+
}
201+
}
202+
#endif
203+
119204
public func validate(path: AbsolutePath, completion: @escaping (Result<Bool, Error>) -> Void) {
120205
do {
121206
guard self.fileSystem.exists(path) else {

Sources/Basics/Cancellator.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -211,6 +211,18 @@ public struct CancellationError: Error, CustomStringConvertible {
211211
"""
212212
)
213213
}
214+
215+
#if !os(iOS) && !os(watchOS) && !os(tvOS)
216+
static func failedToRegisterProcess(_ process: Foundation.Process) -> Self {
217+
Self(
218+
description: """
219+
failed to register a cancellation handler for this process invocation `\(
220+
process.arguments?.joined(separator: " ") ?? ""
221+
)`
222+
"""
223+
)
224+
}
225+
#endif
214226
}
215227

216228
extension TSCBasic.Process {

0 commit comments

Comments
 (0)