1
1
import Foundation
2
2
import SystemPackage
3
+ import Subprocess
4
+ #if os(macOS)
5
+ import System
6
+ #else
7
+ import SystemPackage
8
+ #endif
3
9
4
10
public struct PlatformDefinition : Codable , Equatable , Sendable {
5
11
/// The name of the platform as it is used in the Swift download URLs.
@@ -57,31 +63,31 @@ public struct RunProgramError: Swift.Error {
57
63
public protocol Platform : Sendable {
58
64
/// The platform-specific default location on disk for swiftly's home
59
65
/// directory.
60
- var defaultSwiftlyHomeDir : FilePath { get }
66
+ var defaultSwiftlyHomeDir : SystemPackage . FilePath { get }
61
67
62
68
/// The directory which stores the swiftly executable itself as well as symlinks
63
69
/// to executables in the "bin" directory of the active toolchain.
64
70
///
65
71
/// If a mocked home directory is set, this will be the "bin" subdirectory of the home directory.
66
72
/// If not, this will be the SWIFTLY_BIN_DIR environment variable if set. If that's also unset,
67
73
/// this will default to the platform's default location.
68
- func swiftlyBinDir( _ ctx: SwiftlyCoreContext ) -> FilePath
74
+ func swiftlyBinDir( _ ctx: SwiftlyCoreContext ) -> SystemPackage . FilePath
69
75
70
76
/// The "toolchains" subdirectory that contains the Swift toolchains managed by swiftly.
71
- func swiftlyToolchainsDir( _ ctx: SwiftlyCoreContext ) -> FilePath
77
+ func swiftlyToolchainsDir( _ ctx: SwiftlyCoreContext ) -> SystemPackage . FilePath
72
78
73
79
/// The file extension of the downloaded toolchain for this platform.
74
80
/// e.g. for Linux systems this is "tar.gz" and on macOS it's "pkg".
75
81
var toolchainFileExtension : String { get }
76
82
77
83
/// Installs a toolchain from a file on disk pointed to by the given path.
78
84
/// After this completes, a user can “use” the toolchain.
79
- func install( _ ctx: SwiftlyCoreContext , from: FilePath , version: ToolchainVersion , verbose: Bool )
85
+ func install( _ ctx: SwiftlyCoreContext , from: SystemPackage . FilePath , version: ToolchainVersion , verbose: Bool )
80
86
async throws
81
87
82
88
/// Extract swiftly from the provided downloaded archive and install
83
89
/// ourselves from that.
84
- func extractSwiftlyAndInstall( _ ctx: SwiftlyCoreContext , from archive: FilePath ) async throws
90
+ func extractSwiftlyAndInstall( _ ctx: SwiftlyCoreContext , from archive: SystemPackage . FilePath ) async throws
85
91
86
92
/// Uninstalls a toolchain associated with the given version.
87
93
/// If this version is in use, the next latest version will be used afterwards.
@@ -111,14 +117,14 @@ public protocol Platform: Sendable {
111
117
/// Downloads the signature file associated with the archive and verifies it matches the downloaded archive.
112
118
/// Throws an error if the signature does not match.
113
119
func verifyToolchainSignature(
114
- _ ctx: SwiftlyCoreContext , toolchainFile: ToolchainFile , archive: FilePath , verbose: Bool
120
+ _ ctx: SwiftlyCoreContext , toolchainFile: ToolchainFile , archive: SystemPackage . FilePath , verbose: Bool
115
121
)
116
122
async throws
117
123
118
124
/// Downloads the signature file associated with the archive and verifies it matches the downloaded archive.
119
125
/// Throws an error if the signature does not match.
120
126
func verifySwiftlySignature(
121
- _ ctx: SwiftlyCoreContext , archiveDownloadURL: URL , archive: FilePath , verbose: Bool
127
+ _ ctx: SwiftlyCoreContext , archiveDownloadURL: URL , archive: SystemPackage . FilePath , verbose: Bool
122
128
) async throws
123
129
124
130
/// Detect the platform definition for this platform.
@@ -129,10 +135,10 @@ public protocol Platform: Sendable {
129
135
func getShell( ) async throws -> String
130
136
131
137
/// Find the location where the toolchain should be installed.
132
- func findToolchainLocation( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> FilePath
138
+ func findToolchainLocation( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> SystemPackage . FilePath
133
139
134
140
/// Find the location of the toolchain binaries.
135
- func findToolchainBinDir( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> FilePath
141
+ func findToolchainBinDir( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> SystemPackage . FilePath
136
142
}
137
143
138
144
extension Platform {
@@ -149,14 +155,14 @@ extension Platform {
149
155
/// -- config.json
150
156
/// ```
151
157
///
152
- public func swiftlyHomeDir( _ ctx: SwiftlyCoreContext ) -> FilePath {
158
+ public func swiftlyHomeDir( _ ctx: SwiftlyCoreContext ) -> SystemPackage . FilePath {
153
159
ctx. mockedHomeDir
154
160
?? ProcessInfo . processInfo. environment [ " SWIFTLY_HOME_DIR " ] . map { FilePath ( $0) }
155
161
?? self . defaultSwiftlyHomeDir
156
162
}
157
163
158
164
/// The path of the configuration file in swiftly's home directory.
159
- public func swiftlyConfigFile( _ ctx: SwiftlyCoreContext ) -> FilePath {
165
+ public func swiftlyConfigFile( _ ctx: SwiftlyCoreContext ) -> SystemPackage . FilePath {
160
166
self . swiftlyHomeDir ( ctx) / " config.json "
161
167
}
162
168
@@ -216,7 +222,7 @@ extension Platform {
216
222
}
217
223
#endif
218
224
219
- try self . runProgram ( [ commandToRun] + arguments, env: newEnv)
225
+ try await self . runProgram ( [ commandToRun] + arguments, env: newEnv)
220
226
}
221
227
222
228
/// Proxy the invocation of the provided command to the chosen toolchain and capture the output.
@@ -243,9 +249,9 @@ extension Platform {
243
249
/// the exit code and program information.
244
250
///
245
251
public func runProgram( _ args: String ... , quiet: Bool = false , env: [ String : String ] ? = nil )
246
- throws
252
+ async throws
247
253
{
248
- try self . runProgram ( [ String] ( args) , quiet: quiet, env: env)
254
+ try await self . runProgram ( [ String] ( args) , quiet: quiet, env: env)
249
255
}
250
256
251
257
/// Run a program.
@@ -254,39 +260,65 @@ extension Platform {
254
260
/// the exit code and program information.
255
261
///
256
262
public func runProgram( _ args: [ String ] , quiet: Bool = false , env: [ String : String ] ? = nil )
257
- throws
263
+ async throws
258
264
{
259
- let process = Process ( )
260
- process. executableURL = URL ( fileURLWithPath: " /usr/bin/env " )
261
- process. arguments = args
265
+ if !quiet {
266
+ let result = try await run (
267
+ . path( " /usr/bin/env " ) ,
268
+ arguments: . init( args) ,
269
+ environment: env != nil ? . inherit. updating ( env ?? [ : ] ) : . inherit,
270
+ output: . fileDescriptor( . standardError, closeAfterSpawningProcess: false ) ,
271
+ error: . fileDescriptor( . standardError, closeAfterSpawningProcess: false ) ,
272
+ )
262
273
263
- if let env {
264
- process. environment = env
265
- }
274
+ // TODO figure out how to set the process group
275
+ // Attach this process to our process group so that Ctrl-C and other signals work
276
+ /*let pgid = tcgetpgrp(STDOUT_FILENO)
277
+ if pgid != -1 {
278
+ tcsetpgrp(STDOUT_FILENO, process.processIdentifier)
279
+ }
266
280
267
- if quiet {
268
- process. standardOutput = nil
269
- process. standardError = nil
270
- }
281
+ defer {
282
+ if pgid != -1 {
283
+ tcsetpgrp(STDOUT_FILENO, pgid)
284
+ }
285
+ }
271
286
272
- try process. run ( )
273
- // Attach this process to our process group so that Ctrl-C and other signals work
274
- let pgid = tcgetpgrp ( STDOUT_FILENO)
275
- if pgid != - 1 {
276
- tcsetpgrp ( STDOUT_FILENO, process. processIdentifier)
277
- }
287
+ process.waitUntilExit()*/
278
288
279
- defer {
289
+ if case . exited( let code) = result. terminationStatus, code != 0 {
290
+ throw RunProgramError ( exitCode: code, program: args. first!, arguments: Array ( args. dropFirst ( ) ) )
291
+ }
292
+ } else {
293
+ let result = try await run (
294
+ . path( " /usr/bin/env " ) ,
295
+ arguments: . init( args) ,
296
+ environment: env != nil ? . inherit. updating ( env ?? [ : ] ) : . inherit,
297
+ output: . discarded,
298
+ error: . discarded,
299
+ )
300
+
301
+ // TODO figure out how to set the process group
302
+ // Attach this process to our process group so that Ctrl-C and other signals work
303
+ /*let pgid = tcgetpgrp(STDOUT_FILENO)
280
304
if pgid != -1 {
281
- tcsetpgrp ( STDOUT_FILENO, pgid )
305
+ tcsetpgrp(STDOUT_FILENO, process.processIdentifier )
282
306
}
283
- }
284
307
285
- process. waitUntilExit ( )
308
+ defer {
309
+ if pgid != -1 {
310
+ tcsetpgrp(STDOUT_FILENO, pgid)
311
+ }
312
+ }
313
+
314
+ process.waitUntilExit()*/
286
315
287
- guard process. terminationStatus == 0 else {
288
- throw RunProgramError ( exitCode: process. terminationStatus, program: args. first!, arguments: Array ( args. dropFirst ( ) ) )
316
+ if case . exited( let code) = result. terminationStatus, code != 0 {
317
+ throw RunProgramError ( exitCode: code, program: args. first!, arguments: Array ( args. dropFirst ( ) ) )
318
+ }
289
319
}
320
+
321
+ // TODO handle exits with a signal
290
322
}
291
323
292
324
/// Run a program and capture its output.
@@ -308,22 +340,17 @@ extension Platform {
308
340
public func runProgramOutput( _ program: String , _ args: [ String ] , env: [ String : String ] ? = nil )
309
341
async throws -> String ?
310
342
{
311
- let process = Process ( )
312
- process. executableURL = URL ( fileURLWithPath: " /usr/bin/env " )
313
- process. arguments = [ program] + args
314
-
315
- if let env {
316
- process. environment = env
317
- }
318
-
319
- let outPipe = Pipe ( )
320
- process. standardInput = FileHandle . nullDevice
321
- process. standardError = FileHandle . nullDevice
322
- process. standardOutput = outPipe
323
-
324
- try process. run ( )
325
- // Attach this process to our process group so that Ctrl-C and other signals work
326
- let pgid = tcgetpgrp ( STDOUT_FILENO)
343
+ let result = try await run (
344
+ . path( " /usr/bin/env " ) ,
345
+ arguments: . init( [ program] + args) ,
346
+ environment: env != nil ? . inherit. updating ( env ?? [ : ] ) : . inherit,
347
+ input: . none,
348
+ output: . string( limit: 10 * 1024 * 1024 , encoding: UTF8 . self) ,
349
+ error: . discarded,
350
+ )
351
+
352
+ // TODO Attach this process to our process group so that Ctrl-C and other signals work
353
+ /*let pgid = tcgetpgrp(STDOUT_FILENO)
327
354
if pgid != -1 {
328
355
tcsetpgrp(STDOUT_FILENO, process.processIdentifier)
329
356
}
@@ -332,28 +359,21 @@ extension Platform {
332
359
tcsetpgrp(STDOUT_FILENO, pgid)
333
360
}
334
361
}
362
+ */
335
363
336
- let outData = try outPipe. fileHandleForReading. readToEnd ( )
337
-
338
- process. waitUntilExit ( )
339
-
340
- guard process. terminationStatus == 0 else {
341
- throw RunProgramError ( exitCode: process. terminationStatus, program: program, arguments: args)
364
+ if case . exited( let code) = result. terminationStatus, code != 0 {
365
+ throw RunProgramError ( exitCode: code, program: args. first!, arguments: Array ( args. dropFirst ( ) ) )
342
366
}
343
367
344
- if let outData {
345
- return String ( data: outData, encoding: . utf8)
346
- } else {
347
- return nil
348
- }
368
+ return result. standardOutput
349
369
}
350
370
351
371
// Install ourselves in the final location
352
372
public func installSwiftlyBin( _ ctx: SwiftlyCoreContext ) async throws {
353
373
// First, let's find out where we are.
354
374
let cmd = CommandLine . arguments [ 0 ]
355
375
356
- var cmdAbsolute : FilePath ?
376
+ var cmdAbsolute : SystemPackage . FilePath ?
357
377
358
378
if cmd. hasPrefix ( " / " ) {
359
379
cmdAbsolute = FilePath ( cmd)
@@ -385,7 +405,7 @@ extension Platform {
385
405
// Proceed to installation only if we're in the user home directory, or a non-system location.
386
406
let userHome = fs. home
387
407
388
- let systemRoots : [ FilePath ] = [ " /usr " , " /opt " , " /bin " ]
408
+ let systemRoots : [ SystemPackage . FilePath ] = [ " /usr " , " /opt " , " /bin " ]
389
409
390
410
guard cmdAbsolute. starts ( with: userHome) || systemRoots. filter ( { cmdAbsolute. starts ( with: $0) } ) . first == nil else {
391
411
return
@@ -421,12 +441,12 @@ extension Platform {
421
441
}
422
442
423
443
// Find the location where swiftly should be executed.
424
- public func findSwiftlyBin( _ ctx: SwiftlyCoreContext ) async throws -> FilePath ? {
444
+ public func findSwiftlyBin( _ ctx: SwiftlyCoreContext ) async throws -> SystemPackage . FilePath ? {
425
445
let swiftlyHomeBin = self . swiftlyBinDir ( ctx) / " swiftly "
426
446
427
447
// First, let's find out where we are.
428
448
let cmd = CommandLine . arguments [ 0 ]
429
- var cmdAbsolute : FilePath ?
449
+ var cmdAbsolute : SystemPackage . FilePath ?
430
450
if cmd. hasPrefix ( " / " ) {
431
451
cmdAbsolute = FilePath ( cmd)
432
452
} else {
@@ -457,7 +477,7 @@ extension Platform {
457
477
}
458
478
}
459
479
460
- let systemRoots : [ FilePath ] = [ " /usr " , " /opt " , " /bin " ]
480
+ let systemRoots : [ SystemPackage . FilePath ] = [ " /usr " , " /opt " , " /bin " ]
461
481
462
482
// If we are system managed then we know where swiftly should be.
463
483
let userHome = fs. home
@@ -479,7 +499,7 @@ extension Platform {
479
499
return try await fs. exists ( atPath: swiftlyHomeBin) ? swiftlyHomeBin : nil
480
500
}
481
501
482
- public func findToolchainBinDir( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> FilePath
502
+ public func findToolchainBinDir( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> SystemPackage . FilePath
483
503
{
484
504
( try await self . findToolchainLocation ( ctx, toolchain) ) / " usr/bin "
485
505
}
0 commit comments