Skip to content

Commit f0d7533

Browse files
committed
Model productbuild
1 parent 58ab36b commit f0d7533

File tree

3 files changed

+167
-13
lines changed

3 files changed

+167
-13
lines changed

Sources/SwiftlyCore/Commands.swift

+153-10
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ public enum SystemCommand {}
55

66
// This file contains a set of system commands that's used by Swiftly and its related tests and tooling
77

8-
// Directory Service command line utility for macOS
9-
// See dscl(1) for details
108
extension SystemCommand {
9+
// Directory Service command line utility for macOS
10+
// See dscl(1) for details
1111
public static func dscl(executable: Executable = DsclCommand.defaultExecutable, datasource: String? = nil) -> DsclCommand {
1212
DsclCommand(executable: executable, datasource: datasource)
1313
}
@@ -93,13 +93,15 @@ extension SystemCommand.DsclCommand.ReadCommand: Output {
9393
}
9494
}
9595

96-
// Create or operate on universal files
97-
// See lipo(1) for details
9896
extension SystemCommand {
97+
// Create or operate on universal files
98+
// See lipo(1) for details
9999
public static func lipo(executable: Executable = LipoCommand.defaultExecutable, inputFiles: FilePath...) -> LipoCommand {
100100
Self.lipo(executable: executable, inputFiles: inputFiles)
101101
}
102102

103+
// Create or operate on universal files
104+
// See lipo(1) for details
103105
public static func lipo(executable: Executable = LipoCommand.defaultExecutable, inputFiles: [FilePath]) -> LipoCommand {
104106
LipoCommand(executable: executable, inputFiles: inputFiles)
105107
}
@@ -155,13 +157,15 @@ extension SystemCommand {
155157

156158
extension SystemCommand.LipoCommand.CreateCommand: Runnable {}
157159

158-
// Build a macOS Installer component package from on-disk files
159-
// See pkgbuild(1) for more details
160160
extension SystemCommand {
161+
// Build a macOS Installer component package from on-disk files
162+
// See pkgbuild(1) for more details
161163
public static func pkgbuild(executable: Executable = PkgbuildCommand.defaultExecutable, _ options: PkgbuildCommand.Option..., root: FilePath, packageOutputPath: FilePath) -> PkgbuildCommand {
162164
Self.pkgbuild(executable: executable, options: options, root: root, packageOutputPath: packageOutputPath)
163165
}
164166

167+
// Build a macOS Installer component package from on-disk files
168+
// See pkgbuild(1) for more details
165169
public static func pkgbuild(executable: Executable = PkgbuildCommand.defaultExecutable, options: [PkgbuildCommand.Option], root: FilePath, packageOutputPath: FilePath) -> PkgbuildCommand {
166170
PkgbuildCommand(executable: executable, options, root: root, packageOutputPath: packageOutputPath)
167171
}
@@ -224,13 +228,15 @@ extension SystemCommand {
224228

225229
extension SystemCommand.PkgbuildCommand: Runnable {}
226230

227-
// get entries from Name Service Switch libraries
228-
// See getent(1) for more details
229231
extension SystemCommand {
232+
// get entries from Name Service Switch libraries
233+
// See getent(1) for more details
230234
public static func getent(executable: Executable = GetentCommand.defaultExecutable, database: String, keys: String...) -> GetentCommand {
231235
Self.getent(executable: executable, database: database, keys: keys)
232236
}
233237

238+
// get entries from Name Service Switch libraries
239+
// See getent(1) for more details
234240
public static func getent(executable: Executable = GetentCommand.defaultExecutable, database: String, keys: [String]) -> GetentCommand {
235241
GetentCommand(executable: executable, database: database, keys: keys)
236242
}
@@ -283,6 +289,8 @@ extension SystemCommand.GetentCommand: Output {
283289
}
284290

285291
extension SystemCommand {
292+
// the stupid content tracker
293+
// See git(1) for more information.
286294
public static func git(executable: Executable = GitCommand.defaultExecutable, workingDir: FilePath? = nil) -> GitCommand {
287295
GitCommand(executable: executable, workingDir: workingDir)
288296
}
@@ -486,13 +494,15 @@ extension SystemCommand.GitCommand.DiffIndexCommand: Runnable {}
486494
extension SystemCommand.GitCommand.InitCommand: Runnable {}
487495
extension SystemCommand.GitCommand.CommitCommand: Runnable {}
488496

489-
// manipulate tape archives
490-
// See tar(1) for more details
491497
extension SystemCommand {
498+
// manipulate tape archives
499+
// See tar(1) for more details
492500
public static func tar(executable: Executable = TarCommand.defaultExecutable, _ options: TarCommand.Option...) -> TarCommand {
493501
Self.tar(executable: executable, options)
494502
}
495503

504+
// manipulate tape archives
505+
// See tar(1) for more details
496506
public static func tar(executable: Executable = TarCommand.defaultExecutable, _ options: [TarCommand.Option]) -> TarCommand {
497507
TarCommand(executable: executable, options)
498508
}
@@ -948,6 +958,8 @@ extension SystemCommand.SwiftCommand.SdkCommand.RemoveCommand: Runnable {}
948958
extension SystemCommand.SwiftCommand.BuildCommand: Runnable {}
949959

950960
extension SystemCommand {
961+
// make utility to maintain groups of programs
962+
// See make(1) for more information.
951963
public static func make(executable: Executable = MakeCommand.defaultExecutable) -> MakeCommand {
952964
MakeCommand(executable: executable)
953965
}
@@ -1001,10 +1013,14 @@ extension SystemCommand.MakeCommand: Runnable {}
10011013
extension SystemCommand.MakeCommand.InstallCommand: Runnable {}
10021014

10031015
extension SystemCommand {
1016+
// remove symbols
1017+
// See strip(1) for more information.
10041018
public static func strip(executable: Executable = StripCommand.defaultExecutable, names: FilePath...) -> StripCommand {
10051019
self.strip(executable: executable, names: names)
10061020
}
10071021

1022+
// remove symbols
1023+
// See strip(1) for more information.
10081024
public static func strip(executable: Executable = StripCommand.defaultExecutable, names: [FilePath]) -> StripCommand {
10091025
StripCommand(executable: executable, names: names)
10101026
}
@@ -1038,10 +1054,14 @@ extension SystemCommand {
10381054
extension SystemCommand.StripCommand: Runnable {}
10391055

10401056
extension SystemCommand {
1057+
// calculate a message-digest fingerprint (checksum) for a file
1058+
// See sha256sum(1) for more information.
10411059
public static func sha256sum(executable: Executable = Sha256SumCommand.defaultExecutable, files: FilePath...) -> Sha256SumCommand {
10421060
self.sha256sum(executable: executable, files: files)
10431061
}
10441062

1063+
// calculate a message-digest fingerprint (checksum) for a file
1064+
// See sha256sum(1) for more information.
10451065
public static func sha256sum(executable: Executable, files: [FilePath]) -> Sha256SumCommand {
10461066
Sha256SumCommand(executable: executable, files: files)
10471067
}
@@ -1073,3 +1093,126 @@ extension SystemCommand {
10731093
}
10741094

10751095
extension SystemCommand.Sha256SumCommand: Output {}
1096+
1097+
extension SystemCommand {
1098+
// Build a product archive for the macOS Installer or the Mac App Store.
1099+
// See productbuild(1) for more information.
1100+
public static func productbuild(executable: Executable = ProductBuildCommand.defaultExecutable) -> ProductBuildCommand {
1101+
ProductBuildCommand(executable: executable)
1102+
}
1103+
1104+
public struct ProductBuildCommand {
1105+
public static var defaultExecutable: Executable { .name("productbuild") }
1106+
1107+
public var executable: Executable
1108+
1109+
public init(executable: Executable) {
1110+
self.executable = executable
1111+
}
1112+
1113+
public func config() -> Configuration {
1114+
var args: [String] = []
1115+
1116+
return Configuration(
1117+
executable: self.executable,
1118+
arguments: Arguments(args),
1119+
environment: .inherit
1120+
)
1121+
}
1122+
1123+
public func synthesize(package: FilePath, distributionOutputPath: FilePath) -> SynthesizeCommand {
1124+
SynthesizeCommand(self, package: package, distributionOutputPath: distributionOutputPath)
1125+
}
1126+
1127+
public struct SynthesizeCommand {
1128+
public var productBuildCommand: ProductBuildCommand
1129+
1130+
public var package: FilePath
1131+
1132+
public var distributionOutputPath: FilePath
1133+
1134+
public init(_ productBuildCommand: ProductBuildCommand, package: FilePath, distributionOutputPath: FilePath) {
1135+
self.productBuildCommand = productBuildCommand
1136+
self.package = package
1137+
self.distributionOutputPath = distributionOutputPath
1138+
}
1139+
1140+
public func config() -> Configuration {
1141+
var c = self.productBuildCommand.config()
1142+
1143+
var args = c.arguments.storage.map(\.description)
1144+
1145+
args.append("--synthesize")
1146+
1147+
args.append(contentsOf: ["--package", "\(self.package)"])
1148+
args.append("\(self.distributionOutputPath)")
1149+
1150+
c.arguments = .init(args)
1151+
1152+
return c
1153+
}
1154+
}
1155+
1156+
public func distribution(_ options: DistributionCommand.Option..., distPath: FilePath, productOutputPath: FilePath) -> DistributionCommand {
1157+
self.distribution(options, distPath: distPath, productOutputPath: productOutputPath)
1158+
}
1159+
1160+
public func distribution(_ options: [DistributionCommand.Option], distPath: FilePath, productOutputPath: FilePath) -> DistributionCommand {
1161+
DistributionCommand(self, options, distPath: distPath, productOutputPath: productOutputPath)
1162+
}
1163+
1164+
public struct DistributionCommand {
1165+
public var productBuildCommand: ProductBuildCommand
1166+
1167+
public var options: [Option]
1168+
1169+
public var distPath: FilePath
1170+
1171+
public var productOutputPath: FilePath
1172+
1173+
public enum Option {
1174+
case packagePath(FilePath)
1175+
case sign(String)
1176+
1177+
public func args() -> [String] {
1178+
switch self {
1179+
case let .packagePath(packagePath):
1180+
["--package-path", "\(packagePath)"]
1181+
case let .sign(sign):
1182+
["--sign", "\(sign)"]
1183+
}
1184+
}
1185+
}
1186+
1187+
public init(_ productBuildCommand: ProductBuildCommand, _ options: [Option], distPath: FilePath, productOutputPath: FilePath) {
1188+
self.productBuildCommand = productBuildCommand
1189+
self.options = options
1190+
self.distPath = distPath
1191+
self.productOutputPath = productOutputPath
1192+
}
1193+
1194+
public func config() -> Configuration {
1195+
var c = self.productBuildCommand.config()
1196+
1197+
var args = c.arguments.storage.map(\.description)
1198+
1199+
args.append("--distribution")
1200+
1201+
args.append("\(self.distPath)")
1202+
1203+
for opt in self.options {
1204+
args.append(contentsOf: opt.args())
1205+
}
1206+
1207+
args.append("\(self.productOutputPath)")
1208+
1209+
c.arguments = .init(args)
1210+
1211+
return c
1212+
}
1213+
}
1214+
}
1215+
}
1216+
1217+
extension SystemCommand.ProductBuildCommand.SynthesizeCommand: Runnable {}
1218+
extension SystemCommand.ProductBuildCommand.DistributionCommand: Runnable {}

Tests/SwiftlyTests/CommandLineTests.swift

+11
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,15 @@ public struct CommandLineTests {
209209
var config = sys.sha256sum(files: FilePath("abcde")).config()
210210
#expect(String(describing: config) == "sha256sum abcde")
211211
}
212+
213+
@Test func testProductBuild() async throws {
214+
var config = sys.productbuild().synthesize(package: FilePath("mypkg"), distributionOutputPath: FilePath("distribution")).config()
215+
#expect(String(describing: config) == "productbuild --synthesize --package mypkg distribution")
216+
217+
config = sys.productbuild().distribution(distPath: FilePath("mydist"), productOutputPath: FilePath("product")).config()
218+
#expect(String(describing: config) == "productbuild --distribution mydist product")
219+
220+
config = sys.productbuild().distribution(.packagePath(FilePath("pkgpath")), .sign("mycert"), distPath: FilePath("mydist"), productOutputPath: FilePath("myproduct")).config()
221+
#expect(String(describing: config) == "productbuild --distribution mydist --package-path pkgpath --sign mycert myproduct")
222+
}
212223
}

Tools/build-swiftly-release/BuildSwiftlyRelease.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -422,16 +422,16 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
422422
let pkgFileReconfigured = releaseDir.appendingPathComponent("swiftly-\(self.version)-reconfigured.pkg")
423423
let distFile = releaseDir.appendingPathComponent("distribution.plist")
424424

425-
try runProgram("productbuild", "--synthesize", "--package", pkgFile.path, distFile.path)
425+
try await sys.productbuild().synthesize(package: FilePath(pkgFile.path), distributionOutputPath: FilePath(distFile.path))
426426

427427
var distFileContents = try String(contentsOf: distFile, encoding: .utf8)
428428
distFileContents = distFileContents.replacingOccurrences(of: "<choices-outline>", with: "<title>swiftly</title><domains enable_anywhere=\"false\" enable_currentUserHome=\"true\" enable_localSystem=\"false\"/><choices-outline>")
429429
try distFileContents.write(to: distFile, atomically: true, encoding: .utf8)
430430

431431
if let cert = cert {
432-
try runProgram("productbuild", "--distribution", distFile.path, "--package-path", pkgFile.deletingLastPathComponent().path, "--sign", cert, pkgFileReconfigured.path)
432+
try await sys.productbuild().distribution(.packagePath(FilePath(pkgFile.deletingLastPathComponent().path)), .sign(cert), distPath: FilePath(distFile.path), productOutputPath: FilePath(pkgFileReconfigured.path))
433433
} else {
434-
try runProgram("productbuild", "--distribution", distFile.path, "--package-path", pkgFile.deletingLastPathComponent().path, pkgFileReconfigured.path)
434+
try await sys.productbuild().distribution(.packagePath(FilePath(pkgFile.deletingLastPathComponent().path)), distPath: FilePath(distFile.path), productOutputPath: FilePath(pkgFileReconfigured.path))
435435
}
436436
try FileManager.default.removeItem(at: pkgFile)
437437
try FileManager.default.copyItem(atPath: pkgFileReconfigured.path, toPath: pkgFile.path)

0 commit comments

Comments
 (0)