Skip to content

Commit 9e3b3f4

Browse files
committed
Model pkgutil
1 parent 3567261 commit 9e3b3f4

File tree

3 files changed

+169
-7
lines changed

3 files changed

+169
-7
lines changed

Sources/MacOSPlatform/MacOS.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public struct MacOS: Platform {
8686

8787
await ctx.print("Checking package signature...")
8888
do {
89-
try runProgram("pkgutil", "--check-signature", "\(tmpFile)", quiet: !verbose)
89+
try await sys.pkgutil().checkSignature(pkgPath: tmpFile).run(self, quiet: !verbose)
9090
} catch {
9191
// If this is not a test that uses mocked toolchains then we must throw this error and abort installation
9292
guard ctx.mockedHomeDir != nil else {
@@ -96,7 +96,7 @@ public struct MacOS: Platform {
9696
// We permit the signature verification to fail during testing
9797
await ctx.print("Signature verification failed, which is allowable during testing with mocked toolchains")
9898
}
99-
try runProgram("pkgutil", "--verbose", "--expand", "\(tmpFile)", "\(tmpDir)", quiet: !verbose)
99+
try await sys.pkgutil(.verbose).expand(pkgPath: tmpFile, dirPath: tmpDir).run(self, quiet: !verbose)
100100

101101
// There's a slight difference in the location of the special Payload file between official swift packages
102102
// and the ones that are mocked here in the test framework.
@@ -120,15 +120,15 @@ public struct MacOS: Platform {
120120
if ctx.mockedHomeDir == nil {
121121
await ctx.print("Extracting the swiftly package...")
122122
try runProgram("installer", "-pkg", "\(archive)", "-target", "CurrentUserHomeDirectory")
123-
try? runProgram("pkgutil", "--volume", "\(userHomeDir)", "--forget", "org.swift.swiftly")
123+
try? await sys.pkgutil(.volume(userHomeDir)).forget(packageId: "org.swift.swiftly").run(self)
124124
} else {
125125
let installDir = userHomeDir / ".swiftly"
126126
try await fs.mkdir(.parents, atPath: installDir)
127127

128128
// In the case of a mock for testing purposes we won't use the installer, perferring a manual process because
129129
// the installer will not install to an arbitrary path, only a volume or user home directory.
130130
let tmpDir = fs.mktemp()
131-
try runProgram("pkgutil", "--expand", "\(archive)", "\(tmpDir)")
131+
try await sys.pkgutil().expand(pkgPath: archive, dirPath: tmpDir).run(self)
132132

133133
// There's a slight difference in the location of the special Payload file between official swift packages
134134
// and the ones that are mocked here in the test framework.
@@ -161,9 +161,7 @@ public struct MacOS: Platform {
161161

162162
try await fs.remove(atPath: toolchainDir)
163163

164-
try? runProgram(
165-
"pkgutil", "--volume", "\(fs.home)", "--forget", pkgInfo.CFBundleIdentifier, quiet: !verbose
166-
)
164+
try? await sys.pkgutil(.volume(fs.home)).forget(packageId: pkgInfo.CFBundleIdentifier).run(self, quiet: !verbose)
167165
}
168166

169167
public func getExecutableName() -> String {

Sources/SwiftlyCore/Commands.swift

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,8 @@ extension SystemCommand.ProductBuildCommand.SynthesizeCommand: Runnable {}
12181218
extension SystemCommand.ProductBuildCommand.DistributionCommand: Runnable {}
12191219

12201220
extension SystemCommand {
1221+
// OpenPGP encryption and signing tool
1222+
// See gpg(1) for more information.
12211223
public static func gpg(executable: Executable = GpgCommand.defaultExecutable) -> GpgCommand {
12221224
GpgCommand(executable: executable)
12231225
}
@@ -1313,3 +1315,154 @@ extension SystemCommand {
13131315

13141316
extension SystemCommand.GpgCommand.ImportCommand: Runnable {}
13151317
extension SystemCommand.GpgCommand.VerifyCommand: Runnable {}
1318+
1319+
extension SystemCommand {
1320+
// Query and manipulate macOS Installer packages and receipts.
1321+
// See pkgutil(1) for more information.
1322+
public static func pkgutil(executable: Executable = PkgutilCommand.defaultExecutable, _ options: PkgutilCommand.Option...) -> PkgutilCommand {
1323+
Self.pkgutil(executable: executable, options)
1324+
}
1325+
1326+
// Query and manipulate macOS Installer packages and receipts.
1327+
// See pkgutil(1) for more information.
1328+
public static func pkgutil(executable: Executable = PkgutilCommand.defaultExecutable, _ options: [PkgutilCommand.Option]) -> PkgutilCommand {
1329+
PkgutilCommand(executable: executable, options)
1330+
}
1331+
1332+
public struct PkgutilCommand {
1333+
public static var defaultExecutable: Executable { .name("pkgutil") }
1334+
1335+
public var executable: Executable
1336+
1337+
public var options: [Option]
1338+
1339+
public enum Option {
1340+
case verbose
1341+
case volume(FilePath)
1342+
1343+
public func args() -> [String] {
1344+
switch self {
1345+
case .verbose:
1346+
["--verbose"]
1347+
case let .volume(volume):
1348+
["--volume", "\(volume)"]
1349+
}
1350+
}
1351+
}
1352+
1353+
public init(executable: Executable, _ options: [Option]) {
1354+
self.executable = executable
1355+
self.options = options
1356+
}
1357+
1358+
public func config() -> Configuration {
1359+
var args: [String] = []
1360+
1361+
for opt in self.options {
1362+
args.append(contentsOf: opt.args())
1363+
}
1364+
1365+
return Configuration(
1366+
executable: self.executable,
1367+
arguments: Arguments(args),
1368+
environment: .inherit
1369+
)
1370+
}
1371+
1372+
public func checkSignature(pkgPath: FilePath) -> CheckSignatureCommand {
1373+
CheckSignatureCommand(self, pkgPath: pkgPath)
1374+
}
1375+
1376+
public struct CheckSignatureCommand {
1377+
public var pkgutil: PkgutilCommand
1378+
1379+
public var pkgPath: FilePath
1380+
1381+
public init(_ pkgutil: PkgutilCommand, pkgPath: FilePath) {
1382+
self.pkgutil = pkgutil
1383+
self.pkgPath = pkgPath
1384+
}
1385+
1386+
public func config() -> Configuration {
1387+
var c: Configuration = self.pkgutil.config()
1388+
1389+
var args = c.arguments.storage.map(\.description)
1390+
1391+
args.append("--check-signature")
1392+
1393+
args.append("\(self.pkgPath)")
1394+
1395+
c.arguments = .init(args)
1396+
1397+
return c
1398+
}
1399+
}
1400+
1401+
public func expand(pkgPath: FilePath, dirPath: FilePath) -> ExpandCommand {
1402+
ExpandCommand(self, pkgPath: pkgPath, dirPath: dirPath)
1403+
}
1404+
1405+
public struct ExpandCommand {
1406+
public var pkgutil: PkgutilCommand
1407+
1408+
public var pkgPath: FilePath
1409+
1410+
public var dirPath: FilePath
1411+
1412+
public init(_ pkgutil: PkgutilCommand, pkgPath: FilePath, dirPath: FilePath) {
1413+
self.pkgutil = pkgutil
1414+
self.pkgPath = pkgPath
1415+
self.dirPath = dirPath
1416+
}
1417+
1418+
public func config() -> Configuration {
1419+
var c: Configuration = self.pkgutil.config()
1420+
1421+
var args = c.arguments.storage.map(\.description)
1422+
1423+
args.append("--expand")
1424+
1425+
args.append("\(self.pkgPath)")
1426+
1427+
args.append("\(self.dirPath)")
1428+
1429+
c.arguments = .init(args)
1430+
1431+
return c
1432+
}
1433+
}
1434+
1435+
public func forget(packageId: String) -> ForgetCommand {
1436+
ForgetCommand(self, packageId: packageId)
1437+
}
1438+
1439+
public struct ForgetCommand {
1440+
public var pkgutil: PkgutilCommand
1441+
1442+
public var packageId: String
1443+
1444+
public init(_ pkgutil: PkgutilCommand, packageId: String) {
1445+
self.pkgutil = pkgutil
1446+
self.packageId = packageId
1447+
}
1448+
1449+
public func config() -> Configuration {
1450+
var c: Configuration = self.pkgutil.config()
1451+
1452+
var args = c.arguments.storage.map(\.description)
1453+
1454+
args.append("--forget")
1455+
1456+
args.append("\(self.packageId)")
1457+
1458+
c.arguments = .init(args)
1459+
1460+
return c
1461+
}
1462+
}
1463+
}
1464+
}
1465+
1466+
extension SystemCommand.PkgutilCommand.CheckSignatureCommand: Runnable {}
1467+
extension SystemCommand.PkgutilCommand.ExpandCommand: Runnable {}
1468+
extension SystemCommand.PkgutilCommand.ForgetCommand: Runnable {}

Tests/SwiftlyTests/CommandLineTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,15 @@ public struct CommandLineTests {
228228
config = sys.gpg().verify(detachedSignature: FilePath("file.sig"), signedData: FilePath("file")).config()
229229
#expect(String(describing: config) == "gpg --verify file.sig file")
230230
}
231+
232+
@Test func testPkgutil() async throws {
233+
var config = sys.pkgutil(.verbose).checkSignature(pkgPath: FilePath("path/to/my.pkg")).config()
234+
#expect(String(describing: config) == "pkgutil --verbose --check-signature path/to/my.pkg")
235+
236+
config = sys.pkgutil(.verbose).expand(pkgPath: FilePath("path/to/my.pkg"), dirPath: FilePath("expand/to/here")).config()
237+
#expect(String(describing: config) == "pkgutil --verbose --expand path/to/my.pkg expand/to/here")
238+
239+
config = sys.pkgutil(.volume("CurrentUserHomeDirectory")).forget(packageId: "com.example.pkg").config()
240+
#expect(String(describing: config) == "pkgutil --volume CurrentUserHomeDirectory --forget com.example.pkg")
241+
}
231242
}

0 commit comments

Comments
 (0)