Skip to content

Commit 3567261

Browse files
committed
Model gpg
1 parent f0d7533 commit 3567261

File tree

4 files changed

+127
-23
lines changed

4 files changed

+127
-23
lines changed

Sources/LinuxPlatform/Linux.swift

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,11 @@ public struct Linux: Platform {
275275
try await fs.withTemporary(files: tmpFile) {
276276
try await ctx.httpClient.getGpgKeys().download(to: tmpFile)
277277
if let mockedHomeDir = ctx.mockedHomeDir {
278-
try self.runProgram(
279-
"gpg", "--import", "\(tmpFile)", quiet: true,
280-
env: ["GNUPGHOME": (mockedHomeDir / ".gnupg").string]
281-
)
278+
var env = ProcessInfo.processInfo.environment
279+
env["GNUPGHOME"] = (mockedHomeDir / ".gnupg").string
280+
try await sys.gpg()._import(keys: tmpFile).run(self, env: env, quiet: true)
282281
} else {
283-
try self.runProgram("gpg", "--import", "\(tmpFile)", quiet: true)
282+
try await sys.gpg()._import(keys: tmpFile).run(self, quiet: true)
284283
}
285284
}
286285
}
@@ -417,12 +416,11 @@ public struct Linux: Platform {
417416
await ctx.print("Verifying toolchain signature...")
418417
do {
419418
if let mockedHomeDir = ctx.mockedHomeDir {
420-
try self.runProgram(
421-
"gpg", "--verify", "\(sigFile)", "\(archive)", quiet: false,
422-
env: ["GNUPGHOME": (mockedHomeDir / ".gnupg").string]
423-
)
419+
var env = ProcessInfo.processInfo.environment
420+
env["GNUPGHOME"] = (mockedHomeDir / ".gnupg").string
421+
try await sys.gpg().verify(detachedSignature: sigFile, signedData: archive).run(self, env: env, quiet: false)
424422
} else {
425-
try self.runProgram("gpg", "--verify", "\(sigFile)", "\(archive)", quiet: !verbose)
423+
try await sys.gpg().verify(detachedSignature: sigFile, signedData: archive).run(self, quiet: !verbose)
426424
}
427425
} catch {
428426
throw SwiftlyError(message: "Signature verification failed: \(error).")
@@ -447,12 +445,11 @@ public struct Linux: Platform {
447445
await ctx.print("Verifying swiftly signature...")
448446
do {
449447
if let mockedHomeDir = ctx.mockedHomeDir {
450-
try self.runProgram(
451-
"gpg", "--verify", "\(sigFile)", "\(archive)", quiet: false,
452-
env: ["GNUPGHOME": (mockedHomeDir / ".gnupg").string]
453-
)
448+
var env = ProcessInfo.processInfo.environment
449+
env["GNUPGHOME"] = (mockedHomeDir / ".gnupg").string
450+
try await sys.gpg().verify(detachedSignature: sigFile, signedData: archive).run(self, env: env, quiet: false)
454451
} else {
455-
try self.runProgram("gpg", "--verify", "\(sigFile)", "\(archive)", quiet: !verbose)
452+
try await sys.gpg().verify(detachedSignature: sigFile, signedData: archive).run(self, quiet: !verbose)
456453
}
457454
} catch {
458455
throw SwiftlyError(message: "Signature verification failed: \(error).")

Sources/SwiftlyCore/Commands.swift

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,3 +1216,100 @@ extension SystemCommand {
12161216

12171217
extension SystemCommand.ProductBuildCommand.SynthesizeCommand: Runnable {}
12181218
extension SystemCommand.ProductBuildCommand.DistributionCommand: Runnable {}
1219+
1220+
extension SystemCommand {
1221+
public static func gpg(executable: Executable = GpgCommand.defaultExecutable) -> GpgCommand {
1222+
GpgCommand(executable: executable)
1223+
}
1224+
1225+
public struct GpgCommand {
1226+
public static var defaultExecutable: Executable { .name("gpg") }
1227+
1228+
public var executable: Executable
1229+
1230+
public init(executable: Executable) {
1231+
self.executable = executable
1232+
}
1233+
1234+
public func config() -> Configuration {
1235+
var args: [String] = []
1236+
1237+
return Configuration(
1238+
executable: self.executable,
1239+
arguments: Arguments(args),
1240+
environment: .inherit
1241+
)
1242+
}
1243+
1244+
public func _import(keys: FilePath...) -> ImportCommand {
1245+
self._import(keys: keys)
1246+
}
1247+
1248+
public func _import(keys: [FilePath]) -> ImportCommand {
1249+
ImportCommand(self, keys: keys)
1250+
}
1251+
1252+
public struct ImportCommand {
1253+
public var gpg: GpgCommand
1254+
1255+
public var keys: [FilePath]
1256+
1257+
public init(_ gpg: GpgCommand, keys: [FilePath]) {
1258+
self.gpg = gpg
1259+
self.keys = keys
1260+
}
1261+
1262+
public func config() -> Configuration {
1263+
var c: Configuration = self.gpg.config()
1264+
1265+
var args = c.arguments.storage.map(\.description)
1266+
1267+
args.append("--import")
1268+
1269+
for key in self.keys {
1270+
args.append("\(key)")
1271+
}
1272+
1273+
c.arguments = .init(args)
1274+
1275+
return c
1276+
}
1277+
}
1278+
1279+
public func verify(detachedSignature: FilePath, signedData: FilePath) -> VerifyCommand {
1280+
VerifyCommand(self, detachedSignature: detachedSignature, signedData: signedData)
1281+
}
1282+
1283+
public struct VerifyCommand {
1284+
public var gpg: GpgCommand
1285+
1286+
public var detachedSignature: FilePath
1287+
1288+
public var signedData: FilePath
1289+
1290+
public init(_ gpg: GpgCommand, detachedSignature: FilePath, signedData: FilePath) {
1291+
self.gpg = gpg
1292+
self.detachedSignature = detachedSignature
1293+
self.signedData = signedData
1294+
}
1295+
1296+
public func config() -> Configuration {
1297+
var c: Configuration = self.gpg.config()
1298+
1299+
var args = c.arguments.storage.map(\.description)
1300+
1301+
args.append("--verify")
1302+
1303+
args.append("\(self.detachedSignature)")
1304+
args.append("\(self.signedData)")
1305+
1306+
c.arguments = .init(args)
1307+
1308+
return c
1309+
}
1310+
}
1311+
}
1312+
}
1313+
1314+
extension SystemCommand.GpgCommand.ImportCommand: Runnable {}
1315+
extension SystemCommand.GpgCommand.VerifyCommand: Runnable {}

Tests/SwiftlyTests/CommandLineTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,12 @@ public struct CommandLineTests {
220220
config = sys.productbuild().distribution(.packagePath(FilePath("pkgpath")), .sign("mycert"), distPath: FilePath("mydist"), productOutputPath: FilePath("myproduct")).config()
221221
#expect(String(describing: config) == "productbuild --distribution mydist --package-path pkgpath --sign mycert myproduct")
222222
}
223+
224+
@Test func testGpg() async throws {
225+
var config = sys.gpg()._import(keys: FilePath("somekeys.asc")).config()
226+
#expect(String(describing: config) == "gpg --import somekeys.asc")
227+
228+
config = sys.gpg().verify(detachedSignature: FilePath("file.sig"), signedData: FilePath("file")).config()
229+
#expect(String(describing: config) == "gpg --verify file.sig file")
230+
}
223231
}

Tests/SwiftlyTests/HTTPClientTests.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Testing
1919
}
2020

2121
try await withGpg { runGpg in
22-
try runGpg(["--import", "\(tmpFile)"])
22+
try await runGpg(sys.gpg()._import(keys: tmpFile))
2323
}
2424
}
2525
}
@@ -47,8 +47,8 @@ import Testing
4747

4848
try await withGpg { runGpg in
4949
try await httpClient.getGpgKeys().download(to: keysFile)
50-
try runGpg(["--import", "\(keysFile)"])
51-
try runGpg(["--verify", "\(tmpFileSignature)", "\(tmpFile)"])
50+
try await runGpg(sys.gpg()._import(keys: keysFile))
51+
try await runGpg(sys.gpg().verify(detachedSignature: tmpFileSignature, signedData: tmpFile))
5252
}
5353
}
5454
}
@@ -76,8 +76,8 @@ import Testing
7676

7777
try await withGpg { runGpg in
7878
try await httpClient.getGpgKeys().download(to: keysFile)
79-
try runGpg(["--import", "\(keysFile)"])
80-
try runGpg(["--verify", "\(tmpFileSignature)", "\(tmpFile)"])
79+
try await runGpg(sys.gpg()._import(keys: keysFile))
80+
try await runGpg(sys.gpg().verify(detachedSignature: tmpFileSignature, signedData: tmpFile))
8181
}
8282
}
8383
}
@@ -126,15 +126,17 @@ import Testing
126126
}
127127
}
128128

129-
private func withGpg(_ body: (([String]) throws -> Void) async throws -> Void) async throws {
129+
private func withGpg(_ body: ((Runnable) async throws -> Void) async throws -> Void) async throws {
130130
#if os(Linux)
131131
// With linux, we can ask gpg to try an import to see if the file is valid
132132
// in a sandbox home directory to avoid contaminating the system
133133
let gpgHome = fs.mktemp()
134134
try await fs.mkdir(.parents, atPath: gpgHome)
135135
try await fs.withTemporary(files: gpgHome) {
136-
func runGpg(arguments: [String]) throws {
137-
try Swiftly.currentPlatform.runProgram(["gpg"] + arguments, quiet: false, env: ["GNUPGHOME": gpgHome.string])
136+
func runGpg(_ runnable: Runnable) async throws {
137+
var env = ProcessInfo.processInfo.environment
138+
env["GNUPGHOME"] = gpgHome.string
139+
try await runnable.run(Swiftly.currentPlatform, env: env, quiet: false)
138140
}
139141

140142
try await body(runGpg)

0 commit comments

Comments
 (0)