Skip to content

Commit 4480f3c

Browse files
committed
added building distribution style packages as an option
1 parent fa3d6f0 commit 4480f3c

3 files changed

Lines changed: 105 additions & 15 deletions

File tree

Sources/quickpkg/InputType.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,21 @@ enum Compression: String, ExpressibleByArgument, CaseIterable {
2323
case latest
2424
case legacy
2525
}
26+
27+
enum PackageType: String, EnumerableFlag {
28+
case component
29+
case distribution
30+
31+
static func name(for value: PackageType) -> NameSpecification {
32+
.long
33+
}
34+
35+
static func help(for value: PackageType) -> ArgumentHelp? {
36+
switch value {
37+
case .component:
38+
return "Build a component package"
39+
case .distribution:
40+
return "Build a distribution package using productbuild"
41+
}
42+
}
43+
}

Sources/quickpkg/PackageBuilder.swift

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct PackageBuilder: Sendable {
3030
func build(
3131
payloadDir: URL,
3232
outputPath: String,
33+
name: String,
3334
identifier: String,
3435
version: String,
3536
installLocation: String,
@@ -38,6 +39,7 @@ struct PackageBuilder: Sendable {
3839
compression: Compression,
3940
relocatable: Bool,
4041
minOSVersion: String?,
42+
packageType: PackageType,
4143
sign: String?,
4244
keychain: String?,
4345
cert: String?,
@@ -52,7 +54,7 @@ struct PackageBuilder: Sendable {
5254
installLocation: installLocation,
5355
outputPlist: componentPlist
5456
)
55-
57+
5658
// Modify relocatable setting if needed
5759
if !relocatable {
5860
try PlistHandler.setRelocatable(false, in: componentPlist)
@@ -64,6 +66,14 @@ struct PackageBuilder: Sendable {
6466
"/usr/bin/xattr", "-dr", "com.apple.quarantine", payloadDir.path
6567
])
6668

69+
// Determine output path for pkgbuild (temp location for distribution, final for component)
70+
let pkgbuildOutput: String
71+
if packageType == .distribution {
72+
pkgbuildOutput = tempDir.appendingPathComponent("\(name).pkg").path
73+
} else {
74+
pkgbuildOutput = outputPath
75+
}
76+
6777
// Build the pkgbuild command
6878
var arguments = [
6979
"/usr/bin/pkgbuild",
@@ -73,12 +83,12 @@ struct PackageBuilder: Sendable {
7383
"--version", version,
7484
"--install-location", installLocation
7585
]
76-
86+
7787
if let scriptsDir = scripts {
7888
arguments += ["--scripts", scriptsDir.path]
7989
logger.log("Scripts path: \(scriptsDir.path)", level: 1)
8090
}
81-
91+
8292
if let ownership = ownership {
8393
arguments += ["--ownership", ownership.rawValue]
8494
}
@@ -90,27 +100,84 @@ struct PackageBuilder: Sendable {
90100
logger.log("Minimum OS version: \(minOSVersion)", level: 1)
91101
}
92102

103+
// Only sign with pkgbuild for component packages
104+
if packageType == .component {
105+
if let sign = sign {
106+
arguments += ["--sign", sign]
107+
}
108+
109+
if let keychain = keychain {
110+
arguments += ["--keychain", keychain]
111+
}
112+
113+
if let cert = cert {
114+
arguments += ["--cert", cert]
115+
}
116+
}
117+
118+
arguments.append(pkgbuildOutput)
119+
120+
logger.log("Building component package: \(pkgbuildOutput)", level: 1)
121+
let result = try await executor.run(arguments)
122+
123+
logger.log(result.stdout, level: 1)
124+
125+
if result.exitCode != 0 {
126+
throw QuickPkgError.pkgbuildFailed("(\(result.exitCode)) \(result.stderr)")
127+
}
128+
129+
// For distribution packages, run productbuild
130+
if packageType == .distribution {
131+
try await buildDistribution(
132+
componentPackage: pkgbuildOutput,
133+
outputPath: outputPath,
134+
identifier: identifier,
135+
version: version,
136+
sign: sign,
137+
keychain: keychain,
138+
cert: cert
139+
)
140+
}
141+
}
142+
143+
/// Build a distribution package from a component package
144+
private func buildDistribution(
145+
componentPackage: String,
146+
outputPath: String,
147+
identifier: String,
148+
version: String,
149+
sign: String?,
150+
keychain: String?,
151+
cert: String?
152+
) async throws {
153+
var arguments = [
154+
"/usr/bin/productbuild",
155+
"--package", componentPackage,
156+
"--identifier", identifier,
157+
"--version", version
158+
]
159+
93160
if let sign = sign {
94161
arguments += ["--sign", sign]
95162
}
96-
163+
97164
if let keychain = keychain {
98165
arguments += ["--keychain", keychain]
99166
}
100-
167+
101168
if let cert = cert {
102169
arguments += ["--cert", cert]
103170
}
104-
171+
105172
arguments.append(outputPath)
106-
107-
logger.log("Building package: \(outputPath)", level: 1)
173+
174+
logger.log("Building distribution package: \(outputPath)", level: 1)
108175
let result = try await executor.run(arguments)
109-
176+
110177
logger.log(result.stdout, level: 1)
111-
178+
112179
if result.exitCode != 0 {
113-
throw QuickPkgError.pkgbuildFailed("(\(result.exitCode)) \(result.stderr)")
180+
throw QuickPkgError.pkgbuildFailed("productbuild failed (\(result.exitCode)) \(result.stderr)")
114181
}
115182
}
116183
}

Sources/quickpkg/QuickPkg.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ struct QuickPkg: AsyncParsableCommand {
3535

3636
// MARK: - Package Options
3737

38-
@Option(name: .customLong("install-location"), help: "Install location (default: /Applications)")
38+
@Option(name: .customLong("install-location"), help: "Install location")
3939
var installLocation: String = "/Applications"
4040

4141
@Option(help: "Ownership setting: recommended, preserve, or preserve-other")
4242
var ownership: Ownership?
4343

44-
@Option(help: "Compression type: latest or legacy (default: latest)")
44+
@Option(help: "Compression type: latest or legacy")
4545
var compression: Compression = .latest
4646

4747
@Option(name: [.customLong("output"), .customLong("out"), .short],
@@ -50,12 +50,15 @@ struct QuickPkg: AsyncParsableCommand {
5050

5151
// MARK: - Flags
5252

53-
@Flag(inversion: .prefixedNo, help: "Clean up temp files (default: true)")
53+
@Flag(inversion: .prefixedNo, help: "Clean up temp files")
5454
var clean: Bool = true
5555

56-
@Flag(inversion: .prefixedNo, help: "Make package relocatable (default: false)")
56+
@Flag(inversion: .prefixedNo, help: "Make package relocatable")
5757
var relocatable: Bool = false
5858

59+
@Flag(exclusivity: .exclusive)
60+
var packageType: PackageType = .distribution
61+
5962
// MARK: - Signing Options
6063

6164
@Option(help: "Signing identity for the package")
@@ -260,6 +263,7 @@ struct QuickPkg: AsyncParsableCommand {
260263
try await packageBuilder.build(
261264
payloadDir: payloadDir,
262265
outputPath: outputPath,
266+
name: metadata.name,
263267
identifier: metadata.identifier,
264268
version: metadata.version,
265269
installLocation: installLocation,
@@ -268,6 +272,7 @@ struct QuickPkg: AsyncParsableCommand {
268272
compression: compression,
269273
relocatable: relocatable,
270274
minOSVersion: metadata.minimumSystemVersion,
275+
packageType: packageType,
271276
sign: sign,
272277
keychain: keychain,
273278
cert: cert,

0 commit comments

Comments
 (0)