11import Foundation
22import SwiftlyCore
3+ import SystemPackage
34
45/// `Platform` implementation for Linux systems.
56/// This implementation can be reused for any supported Linux platform.
@@ -18,24 +19,22 @@ public struct Linux: Platform {
1819
1920 public init ( ) { }
2021
21- public var defaultSwiftlyHomeDirectory : URL {
22+ public var defaultSwiftlyHomeDir : FilePath {
2223 if let dir = ProcessInfo . processInfo. environment [ " XDG_DATA_HOME " ] {
23- return URL ( fileURLWithPath : dir) . appendingPathComponent ( " swiftly " , isDirectory : true )
24+ return FilePath ( dir) / " swiftly "
2425 } else {
25- return FileManager . default. homeDirectoryForCurrentUser
26- . appendingPathComponent ( " .local/share/swiftly " , isDirectory: true )
26+ return homeDir / " .local/share/swiftly "
2727 }
2828 }
2929
30- public func swiftlyBinDir( _ ctx: SwiftlyCoreContext ) -> URL {
31- ctx. mockedHomeDir. map { $0. appendingPathComponent ( " bin " , isDirectory: true ) }
32- ?? ProcessInfo . processInfo. environment [ " SWIFTLY_BIN_DIR " ] . map { URL ( fileURLWithPath: $0) }
33- ?? FileManager . default. homeDirectoryForCurrentUser
34- . appendingPathComponent ( " .local/share/swiftly/bin " , isDirectory: true )
30+ public func swiftlyBinDir( _ ctx: SwiftlyCoreContext ) -> FilePath {
31+ ctx. mockedHomeDir. map { $0 / " bin " }
32+ ?? ProcessInfo . processInfo. environment [ " SWIFTLY_BIN_DIR " ] . map { FilePath ( $0) }
33+ ?? homeDir / " .local/share/swiftly/bin "
3534 }
3635
37- public func swiftlyToolchainsDir( _ ctx: SwiftlyCoreContext ) -> URL {
38- self . swiftlyHomeDir ( ctx) . appendingPathComponent ( " toolchains " , isDirectory : true )
36+ public func swiftlyToolchainsDir( _ ctx: SwiftlyCoreContext ) -> FilePath {
37+ self . swiftlyHomeDir ( ctx) / " toolchains "
3938 }
4039
4140 public var toolchainFileExtension : String {
@@ -45,12 +44,12 @@ public struct Linux: Platform {
4544 private static let skipVerificationMessage : String =
4645 " To skip signature verification, specify the --no-verify flag. "
4746
48- public func verifySwiftlySystemPrerequisites( ) throws {
47+ public func verifySwiftlySystemPrerequisites( ) async throws {
4948 // Check if the root CA certificates are installed on this system for NIOSSL to use.
5049 // This list comes from LinuxCABundle.swift in NIOSSL.
5150 var foundTrustedCAs = false
5251 for crtFile in [ " /etc/ssl/certs/ca-certificates.crt " , " /etc/pki/tls/certs/ca-bundle.crt " ] {
53- if URL ( fileURLWithPath : crtFile) . fileExists ( ) {
52+ if try await fileExists ( atPath : FilePath ( crtFile) ) {
5453 foundTrustedCAs = true
5554 break
5655 }
@@ -267,21 +266,17 @@ public struct Linux: Platform {
267266 }
268267
269268 let tmpFile = self . getTempFilePath ( )
270- let _ = FileManager . default. createFile (
271- atPath: tmpFile. path, contents: nil , attributes: [ . posixPermissions: 0o600 ]
272- )
273- defer {
274- try ? FileManager . default. removeItem ( at: tmpFile)
275- }
276-
277- try await ctx. httpClient. getGpgKeys ( ) . download ( to: tmpFile)
278- if let mockedHomeDir = ctx. mockedHomeDir {
279- try self . runProgram (
280- " gpg " , " --import " , tmpFile. path, quiet: true ,
281- env: [ " GNUPGHOME " : mockedHomeDir. appendingPathComponent ( " .gnupg " ) . path]
282- )
283- } else {
284- try self . runProgram ( " gpg " , " --import " , tmpFile. path, quiet: true )
269+ try await create ( file: tmpFile, contents: nil , mode: 0o600 )
270+ try await withTemporary ( files: tmpFile) {
271+ try await ctx. httpClient. getGpgKeys ( ) . download ( to: tmpFile)
272+ if let mockedHomeDir = ctx. mockedHomeDir {
273+ try self . runProgram (
274+ " gpg " , " --import " , " \( tmpFile) " , quiet: true ,
275+ env: [ " GNUPGHOME " : ( mockedHomeDir / " .gnupg " ) . string]
276+ )
277+ } else {
278+ try self . runProgram ( " gpg " , " --import " , " \( tmpFile) " , quiet: true )
279+ }
285280 }
286281 }
287282
@@ -333,69 +328,63 @@ public struct Linux: Platform {
333328 }
334329
335330 public func install(
336- _ ctx: SwiftlyCoreContext , from tmpFile: URL , version: ToolchainVersion , verbose: Bool
331+ _ ctx: SwiftlyCoreContext , from tmpFile: FilePath , version: ToolchainVersion , verbose: Bool
337332 ) async throws {
338- guard tmpFile . fileExists ( ) else {
333+ guard try await fileExists ( atPath : tmpFile ) else {
339334 throw SwiftlyError ( message: " \( tmpFile) doesn't exist " )
340335 }
341336
342- if !self . swiftlyToolchainsDir ( ctx) . fileExists ( ) {
343- try FileManager . default. createDirectory (
344- at: self . swiftlyToolchainsDir ( ctx) , withIntermediateDirectories: false
345- )
337+ if !( try await fileExists ( atPath: self . swiftlyToolchainsDir ( ctx) ) ) {
338+ try await mkdir ( atPath: self . swiftlyToolchainsDir ( ctx) )
346339 }
347340
348341 await ctx. print ( " Extracting toolchain... " )
349- let toolchainDir = self . swiftlyToolchainsDir ( ctx) . appendingPathComponent ( version. name)
342+ let toolchainDir = self . swiftlyToolchainsDir ( ctx) / version. name
350343
351- if toolchainDir . fileExists ( ) {
352- try FileManager . default . removeItem ( at : toolchainDir)
344+ if try await fileExists ( atPath : toolchainDir ) {
345+ try await remove ( atPath : toolchainDir)
353346 }
354347
355348 try extractArchive( atPath: tmpFile) { name in
356349 // drop swift-a.b.c-RELEASE etc name from the extracted files.
357350 let relativePath = name. drop { c in c != " / " } . dropFirst ( )
358351
359352 // prepend /path/to/swiftlyHomeDir/toolchains/<toolchain> to each file name
360- let destination = toolchainDir. appendingPathComponent ( String ( relativePath) )
353+ let destination = toolchainDir / String( relativePath)
361354
362355 if verbose {
363356 // To avoid having to make extractArchive async this is a regular print
364357 // to stdout. Note that it is unlikely that the test mocking will require
365358 // capturing this output.
366- print ( " \( destination. path ) " )
359+ print ( " \( destination) " )
367360 }
368361
369362 // prepend /path/to/swiftlyHomeDir/toolchains/<toolchain> to each file name
370363 return destination
371364 }
372365 }
373366
374- public func extractSwiftlyAndInstall( _ ctx: SwiftlyCoreContext , from archive: URL ) async throws {
375- guard archive . fileExists ( ) else {
367+ public func extractSwiftlyAndInstall( _ ctx: SwiftlyCoreContext, from archive: FilePath ) async throws {
368+ guard try await fileExists ( atPath : archive ) else {
376369 throw SwiftlyError ( message: " \( archive) doesn't exist " )
377370 }
378371
379372 let tmpDir = self . getTempFilePath ( )
380- defer {
381- try ? FileManager . default. removeItem ( at: tmpDir)
382- }
383- try FileManager . default. createDirectory ( atPath: tmpDir. path, withIntermediateDirectories: true )
373+ try await mkdir ( atPath: tmpDir, parents: true )
374+ try await withTemporary ( files: tmpDir) {
375+ await ctx. print ( " Extracting new swiftly... " )
376+ try extractArchive ( atPath: archive) { name in
377+ // Extract to the temporary directory
378+ tmpDir / String( name)
379+ }
384380
385- await ctx. print ( " Extracting new swiftly... " )
386- try extractArchive ( atPath: archive) { name in
387- // Extract to the temporary directory
388- tmpDir. appendingPathComponent ( String ( name) )
381+ try self . runProgram ( ( tmpDir / " swiftly " ) . string, " init " )
389382 }
390-
391- try self . runProgram ( tmpDir. appendingPathComponent ( " swiftly " ) . path, " init " )
392383 }
393384
394- public func uninstall( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion , verbose _: Bool )
395- throws
396- {
397- let toolchainDir = self . swiftlyToolchainsDir ( ctx) . appendingPathComponent ( toolchain. name)
398- try FileManager . default. removeItem ( at: toolchainDir)
385+ public func uninstall( _ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, verbose _: Bool) async throws {
386+ let toolchainDir = self . swiftlyToolchainsDir ( ctx) / toolchain. name
387+ try await remove ( atPath: toolchainDir)
399388 }
400389
401390 public func getExecutableName( ) - > String {
@@ -404,69 +393,65 @@ public struct Linux: Platform {
404393 return " swiftly- \( arch) -unknown-linux-gnu "
405394 }
406395
407- public func getTempFilePath( ) -> URL {
408- FileManager . default . temporaryDirectory . appendingPathComponent ( " swiftly- \( UUID ( ) ) " )
396+ public func getTempFilePath( ) -> FilePath {
397+ tmpDir / " swiftly- \( UUID ( ) ) "
409398 }
410399
411400 public func verifyToolchainSignature(
412- _ ctx: SwiftlyCoreContext , toolchainFile: ToolchainFile , archive: URL , verbose: Bool
401+ _ ctx: SwiftlyCoreContext , toolchainFile: ToolchainFile , archive: FilePath , verbose: Bool
413402 ) async throws {
414403 if verbose {
415404 await ctx. print ( " Downloading toolchain signature... " )
416405 }
417406
418407 let sigFile = self . getTempFilePath ( )
419- let _ = FileManager . default . createFile ( atPath : sigFile. path , contents: nil )
420- defer {
421- try ? FileManager . default . removeItem ( at : sigFile)
422- }
423-
424- try await ctx . httpClient . getSwiftToolchainFileSignature ( toolchainFile ) . download ( to : sigFile )
425-
426- await ctx . print ( " Verifying toolchain signature... " )
427- do {
428- if let mockedHomeDir = ctx . mockedHomeDir {
429- try self . runProgram (
430- " gpg " , " --verify " , sigFile . path , archive . path , quiet : false ,
431- env : [ " GNUPGHOME " : mockedHomeDir . appendingPathComponent ( " .gnupg " ) . path ]
432- )
433- } else {
434- try self . runProgram ( " gpg " , " --verify " , sigFile . path , archive . path , quiet : !verbose )
408+ try await create ( file : sigFile, contents: nil )
409+ try await withTemporary ( files : sigFile ) {
410+ try await ctx . httpClient . getSwiftToolchainFileSignature ( toolchainFile ) . download ( to : sigFile)
411+
412+ await ctx . print ( " Verifying toolchain signature... " )
413+ do {
414+ if let mockedHomeDir = ctx . mockedHomeDir {
415+ try self . runProgram (
416+ " gpg " , " --verify " , " \( sigFile ) " , " \( archive ) " , quiet : false ,
417+ env : [ " GNUPGHOME " : ( mockedHomeDir / " .gnupg " ) . string ]
418+ )
419+ } else {
420+ try self . runProgram ( " gpg " , " --verify " , " \( sigFile ) " , " \( archive ) " , quiet : !verbose )
421+ }
422+ } catch {
423+ throw SwiftlyError ( message : " Signature verification failed: \( error ) . " )
435424 }
436- } catch {
437- throw SwiftlyError ( message: " Signature verification failed: \( error) . " )
438425 }
439426 }
440427
441428 public func verifySwiftlySignature(
442- _ ctx: SwiftlyCoreContext , archiveDownloadURL: URL , archive: URL , verbose: Bool
429+ _ ctx: SwiftlyCoreContext , archiveDownloadURL: URL , archive: FilePath , verbose: Bool
443430 ) async throws {
444431 if verbose {
445432 await ctx. print ( " Downloading swiftly signature... " )
446433 }
447434
448435 let sigFile = self . getTempFilePath ( )
449- let _ = FileManager . default . createFile ( atPath : sigFile. path , contents: nil )
450- defer {
451- try ? FileManager . default . removeItem ( at : sigFile )
452- }
453-
454- try await ctx . httpClient . getSwiftlyReleaseSignature (
455- url : archiveDownloadURL . appendingPathExtension ( " sig " )
456- ) . download ( to : sigFile )
457-
458- await ctx . print ( " Verifying swiftly signature... " )
459- do {
460- if let mockedHomeDir = ctx . mockedHomeDir {
461- try self . runProgram (
462- " gpg " , " --verify " , sigFile . path , archive . path , quiet : false ,
463- env : [ " GNUPGHOME " : mockedHomeDir . appendingPathComponent ( " .gnupg " ) . path ]
464- )
465- } else {
466- try self . runProgram ( " gpg " , " --verify " , sigFile . path , archive . path , quiet : !verbose )
436+ try await create ( file : sigFile, contents: nil )
437+ try await withTemporary ( files : sigFile ) {
438+ try await ctx . httpClient . getSwiftlyReleaseSignature (
439+ url : archiveDownloadURL . appendingPathExtension ( " sig " )
440+ ) . download ( to : sigFile )
441+
442+ await ctx . print ( " Verifying swiftly signature... " )
443+ do {
444+ if let mockedHomeDir = ctx . mockedHomeDir {
445+ try self . runProgram (
446+ " gpg " , " --verify " , " \( sigFile ) " , " \( archive ) " , quiet : false ,
447+ env : [ " GNUPGHOME " : ( mockedHomeDir / " .gnupg " ) . string ]
448+ )
449+ } else {
450+ try self . runProgram ( " gpg " , " --verify " , " \( sigFile ) " , " \( archive ) " , quiet : !verbose )
451+ }
452+ } catch {
453+ throw SwiftlyError ( message : " Signature verification failed: \( error ) . " )
467454 }
468- } catch {
469- throw SwiftlyError ( message: " Signature verification failed: \( error) . " )
470455 }
471456 }
472457
@@ -631,9 +616,9 @@ public struct Linux: Platform {
631616 return " /bin/bash"
632617 }
633618
634- public func findToolchainLocation( _ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) - > URL
619+ public func findToolchainLocation( _ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) - > FilePath
635620 {
636- self . swiftlyToolchainsDir ( ctx) . appendingPathComponent ( " \( toolchain. name) " )
621+ self . swiftlyToolchainsDir ( ctx) / " \( toolchain. name) "
637622 }
638623
639624 public static let currentPlatform : any Platform = Linux ( )
0 commit comments