Skip to content

Commit 636d64f

Browse files
committed
removed ShellExecutor
1 parent bb88c3d commit 636d64f

6 files changed

Lines changed: 155 additions & 142 deletions

File tree

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,52 @@
11
import Foundation
2+
import Subprocess
3+
4+
#if canImport(System)
5+
import System
6+
#else
7+
import SystemPackage
8+
#endif
29

310
struct ArchiveExtractor: Sendable {
4-
let executor: ShellExecutor
511
let logger: Logger
612

713
/// Extract a zip archive to the specified directory
814
func extractZip(_ archivePath: URL, to destinationDir: URL) async throws {
915
logger.log("Extracting zip: \(archivePath.path) to \(destinationDir.path)", level: 1)
1016

11-
let result = try await executor.run([
12-
"/usr/bin/unzip",
13-
"-q",
14-
archivePath.path,
15-
"-d", destinationDir.path
16-
])
17+
let arguments = ["/usr/bin/unzip", "-q", archivePath.path, "-d", destinationDir.path]
18+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
19+
20+
let result = try await Subprocess.run(
21+
.path(FilePath(arguments[0])),
22+
arguments: Arguments(Array(arguments.dropFirst())),
23+
output: .string(limit: .max),
24+
error: .string(limit: .max)
25+
)
1726

18-
guard result.exitCode == 0 else {
19-
throw QuickPkgError.archiveExtractionFailed("unzip failed (\(result.exitCode)): \(result.stderr)")
27+
guard result.terminationStatus.isSuccess else {
28+
throw QuickPkgError.archiveExtractionFailed(result.standardError ?? "unzip failed")
2029
}
2130
}
2231

2332
/// Extract a xip archive to the specified directory
2433
func extractXip(_ archivePath: URL, to destinationDir: URL) async throws {
2534
logger.log("Extracting xip: \(archivePath.path) to \(destinationDir.path)", level: 1)
2635

36+
let arguments = ["/usr/bin/xip", "--expand", archivePath.path]
37+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
38+
2739
// xip --expand extracts to the current directory, so we need to run it from the destination
28-
let result = try await executor.run(
29-
["/usr/bin/xip", "--expand", archivePath.path],
30-
workingDirectory: destinationDir
40+
let result = try await Subprocess.run(
41+
.path(FilePath(arguments[0])),
42+
arguments: Arguments(Array(arguments.dropFirst())),
43+
workingDirectory: FilePath(destinationDir.path),
44+
output: .string(limit: .max),
45+
error: .string(limit: .max)
3146
)
3247

33-
guard result.exitCode == 0 else {
34-
throw QuickPkgError.archiveExtractionFailed("xip failed (\(result.exitCode)): \(result.stderr)")
48+
guard result.terminationStatus.isSuccess else {
49+
throw QuickPkgError.archiveExtractionFailed(result.standardError ?? "xip failed")
3550
}
3651
}
3752
}

Sources/quickpkg/DMGManager.swift

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
import Foundation
2+
import Subprocess
3+
4+
#if canImport(System)
5+
import System
6+
#else
7+
import SystemPackage
8+
#endif
29

310
actor DMGManager {
4-
private let executor: ShellExecutor
511
private let logger: Logger
612
private var wasMounted: [URL: Bool] = [:]
713
private var mountedVolumes: [URL] = []
814

9-
init(
10-
executor: ShellExecutor,
11-
logger: Logger
12-
) {
13-
self.executor = executor
15+
init(logger: Logger) {
1416
self.logger = logger
1517
}
1618

1719
/// Check if a DMG has a Software License Agreement
1820
func hasSLA(at path: URL) async throws -> Bool {
19-
let result = try await executor.runOrThrow([
20-
"/usr/bin/hdiutil", "imageinfo", path.path, "-plist"
21-
])
21+
let arguments = ["/usr/bin/hdiutil", "imageinfo", path.path, "-plist"]
22+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
23+
24+
let result = try await Subprocess.run(
25+
.path(FilePath(arguments[0])),
26+
arguments: Arguments(Array(arguments.dropFirst())),
27+
output: .string(limit: .max),
28+
error: .string(limit: .max)
29+
)
30+
31+
guard result.terminationStatus.isSuccess else {
32+
throw QuickPkgError.dmgMountFailed(result.standardError ?? "hdiutil imageinfo failed")
33+
}
2234

23-
let plist = try PlistHandler.parse(Data(result.stdout.utf8))
35+
let plist = try PlistHandler.parse(Data((result.standardOutput ?? "").utf8))
2436
if let properties = plist["Properties"] as? [String: Any],
2537
let hasSLA = properties["Software License Agreement"] as? Bool {
2638
return hasSLA
@@ -30,9 +42,21 @@ actor DMGManager {
3042

3143
/// Check if DMG is already mounted and return mount points
3244
func existingMountPoints(for dmgPath: URL) async throws -> [URL]? {
33-
let result = try await executor.runOrThrow(["/usr/bin/hdiutil", "info", "-plist"])
45+
let arguments = ["/usr/bin/hdiutil", "info", "-plist"]
46+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
47+
48+
let result = try await Subprocess.run(
49+
.path(FilePath(arguments[0])),
50+
arguments: Arguments(Array(arguments.dropFirst())),
51+
output: .string(limit: .max),
52+
error: .string(limit: .max)
53+
)
54+
55+
guard result.terminationStatus.isSuccess else {
56+
throw QuickPkgError.dmgMountFailed(result.standardError ?? "hdiutil info failed")
57+
}
3458

35-
let plistData = try PlistHandler.extractFirstPlist(from: Data(result.stdout.utf8))
59+
let plistData = try PlistHandler.extractFirstPlist(from: Data((result.standardOutput ?? "").utf8))
3660
let info = try PlistHandler.parse(plistData)
3761

3862
guard let images = info["images"] as? [[String: Any]] else {
@@ -93,14 +117,41 @@ actor DMGManager {
93117
"-nobrowse"
94118
]
95119

96-
let result = try await executor.run(arguments, input: sla ? "Y\n" : nil)
120+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
97121

98-
guard result.exitCode == 0 else {
99-
throw QuickPkgError.dmgMountFailed("(\(result.exitCode)) \(result.stderr)")
122+
let terminationStatus: TerminationStatus
123+
let standardOutput: String?
124+
let standardError: String?
125+
126+
if sla {
127+
let result = try await Subprocess.run(
128+
.path(FilePath(arguments[0])),
129+
arguments: Arguments(Array(arguments.dropFirst())),
130+
input: .string("Y\n"),
131+
output: .string(limit: .max),
132+
error: .string(limit: .max)
133+
)
134+
terminationStatus = result.terminationStatus
135+
standardOutput = result.standardOutput
136+
standardError = result.standardError
137+
} else {
138+
let result = try await Subprocess.run(
139+
.path(FilePath(arguments[0])),
140+
arguments: Arguments(Array(arguments.dropFirst())),
141+
output: .string(limit: .max),
142+
error: .string(limit: .max)
143+
)
144+
terminationStatus = result.terminationStatus
145+
standardOutput = result.standardOutput
146+
standardError = result.standardError
147+
}
148+
149+
guard terminationStatus.isSuccess else {
150+
throw QuickPkgError.dmgMountFailed(standardError ?? "hdiutil attach failed")
100151
}
101152

102153
// Parse the plist output to get mount points
103-
let plistData = try PlistHandler.extractFirstPlist(from: Data(result.stdout.utf8))
154+
let plistData = try PlistHandler.extractFirstPlist(from: Data((standardOutput ?? "").utf8))
104155
let attachResult = try PlistHandler.parse(plistData)
105156

106157
var mountPoints: [URL] = []
@@ -133,10 +184,18 @@ actor DMGManager {
133184

134185
guard mountPoint.fileExists else { return }
135186

136-
let result = try await executor.run(["/usr/bin/hdiutil", "detach", mountPoint.path])
187+
let arguments = ["/usr/bin/hdiutil", "detach", mountPoint.path]
188+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
189+
190+
let result = try await Subprocess.run(
191+
.path(FilePath(arguments[0])),
192+
arguments: Arguments(Array(arguments.dropFirst())),
193+
output: .string(limit: .max),
194+
error: .string(limit: .max)
195+
)
137196

138-
if result.exitCode != 0 {
139-
logger.log("Warning: Failed to detach \(mountPoint.path): \(result.stderr)", level: 1)
197+
if !result.terminationStatus.isSuccess {
198+
logger.log("Warning: Failed to detach \(mountPoint.path): \(result.standardError ?? "")", level: 1)
140199
} else {
141200
logger.log("Detached: \(mountPoint.path)", level: 2)
142201
}

Sources/quickpkg/PackageBuilder.swift

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import Foundation
2+
import Subprocess
3+
4+
#if canImport(System)
5+
import System
6+
#else
7+
import SystemPackage
8+
#endif
29

310
struct PackageBuilder: Sendable {
4-
let executor: ShellExecutor
511
let logger: Logger
612

713
/// Analyze the payload and create a component plist
@@ -22,8 +28,18 @@ struct PackageBuilder: Sendable {
2228
outputPlist.path
2329
]
2430

25-
let result = try await executor.runOrThrow(arguments)
26-
logger.log(result.stdout, level: 1)
31+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
32+
33+
let result = try await Subprocess.run(
34+
.path(FilePath(arguments[0])),
35+
arguments: Arguments(Array(arguments.dropFirst())),
36+
output: .string(limit: .max),
37+
error: .string(limit: .max)
38+
)
39+
40+
guard result.terminationStatus.isSuccess else {
41+
throw QuickPkgError.pkgbuildFailed(result.standardError ?? "pkgbuild --analyze failed")
42+
}
2743
}
2844

2945
/// Build the package
@@ -62,9 +78,14 @@ struct PackageBuilder: Sendable {
6278

6379
// Remove quarantine extended attributes from payload
6480
logger.log("Removing quarantine attributes from payload", level: 1)
65-
_ = try await executor.run([
66-
"/usr/bin/xattr", "-dr", "com.apple.quarantine", payloadDir.path
67-
])
81+
let xattrArgs = ["/usr/bin/xattr", "-dr", "com.apple.quarantine", payloadDir.path]
82+
logger.log("Executing: \(xattrArgs.joined(separator: " "))", level: 3)
83+
_ = try await Subprocess.run(
84+
.path(FilePath(xattrArgs[0])),
85+
arguments: Arguments(Array(xattrArgs.dropFirst())),
86+
output: .discarded,
87+
error: .discarded
88+
)
6889

6990
// Determine output path for pkgbuild (temp location for distribution, final for component)
7091
let pkgbuildOutput: String
@@ -118,12 +139,17 @@ struct PackageBuilder: Sendable {
118139
arguments.append(pkgbuildOutput)
119140

120141
logger.log("Building component package: \(pkgbuildOutput)", level: 1)
121-
let result = try await executor.run(arguments)
142+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
122143

123-
logger.log(result.stdout, level: 1)
144+
let result = try await Subprocess.run(
145+
.path(FilePath(arguments[0])),
146+
arguments: Arguments(Array(arguments.dropFirst())),
147+
output: .string(limit: .max),
148+
error: .string(limit: .max)
149+
)
124150

125-
if result.exitCode != 0 {
126-
throw QuickPkgError.pkgbuildFailed("(\(result.exitCode)) \(result.stderr)")
151+
guard result.terminationStatus.isSuccess else {
152+
throw QuickPkgError.pkgbuildFailed(result.standardError ?? "pkgbuild failed")
127153
}
128154

129155
// For distribution packages, run productbuild
@@ -172,12 +198,17 @@ struct PackageBuilder: Sendable {
172198
arguments.append(outputPath)
173199

174200
logger.log("Building distribution package: \(outputPath)", level: 1)
175-
let result = try await executor.run(arguments)
201+
logger.log("Executing: \(arguments.joined(separator: " "))", level: 3)
176202

177-
logger.log(result.stdout, level: 1)
203+
let result = try await Subprocess.run(
204+
.path(FilePath(arguments[0])),
205+
arguments: Arguments(Array(arguments.dropFirst())),
206+
output: .string(limit: .max),
207+
error: .string(limit: .max)
208+
)
178209

179-
if result.exitCode != 0 {
180-
throw QuickPkgError.pkgbuildFailed("productbuild failed (\(result.exitCode)) \(result.stderr)")
210+
guard result.terminationStatus.isSuccess else {
211+
throw QuickPkgError.pkgbuildFailed(result.standardError ?? "productbuild failed")
181212
}
182213
}
183214
}

Sources/quickpkg/QuickPkg.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,8 @@ struct QuickPkg: AsyncParsableCommand {
105105
}
106106
}
107107

108-
let executor = ShellExecutor(logger: logger)
109-
let dmgManager = DMGManager(executor: executor, logger: logger)
110-
let archiveExtractor = ArchiveExtractor(executor: executor, logger: logger)
108+
let dmgManager = DMGManager(logger: logger)
109+
let archiveExtractor = ArchiveExtractor(logger: logger)
111110

112111
// Capture clean flag for use in async cleanup
113112
let shouldClean = clean
@@ -149,7 +148,7 @@ struct QuickPkg: AsyncParsableCommand {
149148
let scriptsDir = try prepareScripts(tempDir: tempDir, logger: logger)
150149

151150
// Build the package
152-
let packageBuilder = PackageBuilder(executor: executor, logger: logger)
151+
let packageBuilder = PackageBuilder(logger: logger)
153152
let outputPath = determineOutputPath(
154153
output: output,
155154
name: metadata.name,

Sources/quickpkg/QuickPkgError.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ enum QuickPkgError: LocalizedError {
1313
case pkgbuildFailed(String)
1414
case scriptNotFound(String)
1515
case scriptConflict(String)
16-
case commandFailed(command: String, exitCode: Int32, stderr: String)
1716
case plistParsingFailed(String)
1817

1918
var errorDescription: String? {
@@ -42,8 +41,6 @@ enum QuickPkgError: LocalizedError {
4241
return "Script not found: \(path)"
4342
case .scriptConflict(let reason):
4443
return "Script conflict: \(reason)"
45-
case .commandFailed(let command, let exitCode, let stderr):
46-
return "Command failed (\(exitCode)): \(command)\n\(stderr)"
4744
case .plistParsingFailed(let reason):
4845
return "Failed to parse plist: \(reason)"
4946
}

0 commit comments

Comments
 (0)