Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 49 additions & 49 deletions Sources/LinuxPlatform/Linux.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Foundation
import SwiftlyCore

var swiftGPGKeysRefreshed = false

/// `Platform` implementation for Linux systems.
/// This implementation can be reused for any supported Linux platform.
/// TODO: replace dummy implementations
Expand All @@ -29,25 +27,21 @@ public struct Linux: Platform {
}
}

public var swiftlyBinDir: URL {
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
public func swiftlyBinDir(_ ctx: SwiftlyCoreContext) -> URL {
ctx.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
?? ProcessInfo.processInfo.environment["SWIFTLY_BIN_DIR"].map { URL(fileURLWithPath: $0) }
?? FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".local/share/swiftly/bin", isDirectory: true)
}

public var swiftlyToolchainsDir: URL {
self.swiftlyHomeDir.appendingPathComponent("toolchains", isDirectory: true)
public func swiftlyToolchainsDir(_ ctx: SwiftlyCoreContext) -> URL {
self.swiftlyHomeDir(ctx).appendingPathComponent("toolchains", isDirectory: true)
}

public var toolchainFileExtension: String {
"tar.gz"
}

public func isSystemDependencyPresent(_: SystemDependency) -> Bool {
true
}

private static let skipVerificationMessage: String = "To skip signature verification, specify the --no-verify flag."

public func verifySwiftlySystemPrerequisites() throws {
Expand All @@ -73,7 +67,7 @@ public struct Linux: Platform {
}
}

public func verifySystemPrerequisitesForInstall(httpClient: SwiftlyHTTPClient, platformName: String, version _: ToolchainVersion, requireSignatureValidation: Bool) async throws -> String? {
public func verifySystemPrerequisitesForInstall(_ ctx: SwiftlyCoreContext, platformName: String, version _: ToolchainVersion, requireSignatureValidation: Bool) async throws -> String? {
// TODO: these are hard-coded until we have a place to query for these based on the toolchain version
// These lists were copied from the dockerfile sources here: https://github.com/apple/swift-docker/tree/ea035798755cce4ec41e0c6dbdd320904cef0421/5.10
let packages: [String] = switch platformName {
Expand Down Expand Up @@ -264,22 +258,21 @@ public struct Linux: Platform {
throw SwiftlyError(message: msg)
}

// Import the latest swift keys, but only once per session, which will help with the performance in tests
if !swiftGPGKeysRefreshed {
let tmpFile = self.getTempFilePath()
let _ = FileManager.default.createFile(atPath: tmpFile.path, contents: nil, attributes: [.posixPermissions: 0o600])
defer {
try? FileManager.default.removeItem(at: tmpFile)
}
let tmpFile = self.getTempFilePath()
let _ = FileManager.default.createFile(atPath: tmpFile.path, contents: nil, attributes: [.posixPermissions: 0o600])
defer {
try? FileManager.default.removeItem(at: tmpFile)
}

guard let url = URL(string: "https://www.swift.org/keys/all-keys.asc") else {
throw SwiftlyError(message: "malformed URL to the swift gpg keys")
}
guard let url = URL(string: "https://www.swift.org/keys/all-keys.asc") else {
throw SwiftlyError(message: "malformed URL to the swift gpg keys")
}

try await httpClient.downloadFile(url: url, to: tmpFile)
try await ctx.httpClient.downloadFile(url: url, to: tmpFile)
if let mockedHomeDir = ctx.mockedHomeDir {
try self.runProgram("gpg", "--import", tmpFile.path, quiet: true, env: ["GNUPGHOME": mockedHomeDir.appendingPathComponent(".gnupg").path])
} else {
try self.runProgram("gpg", "--import", tmpFile.path, quiet: true)

swiftGPGKeysRefreshed = true
}
}

Expand Down Expand Up @@ -330,17 +323,17 @@ public struct Linux: Platform {
}
}

public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
public func install(_ ctx: SwiftlyCoreContext, from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
guard tmpFile.fileExists() else {
throw SwiftlyError(message: "\(tmpFile) doesn't exist")
}

if !self.swiftlyToolchainsDir.fileExists() {
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir, withIntermediateDirectories: false)
if !self.swiftlyToolchainsDir(ctx).fileExists() {
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir(ctx), withIntermediateDirectories: false)
}

SwiftlyCore.print("Extracting toolchain...")
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent(version.name)
ctx.print("Extracting toolchain...")
let toolchainDir = self.swiftlyToolchainsDir(ctx).appendingPathComponent(version.name)

if toolchainDir.fileExists() {
try FileManager.default.removeItem(at: toolchainDir)
Expand All @@ -354,23 +347,26 @@ public struct Linux: Platform {
let destination = toolchainDir.appendingPathComponent(String(relativePath))

if verbose {
SwiftlyCore.print("\(destination.path)")
ctx.print("\(destination.path)")
}

// prepend /path/to/swiftlyHomeDir/toolchains/<toolchain> to each file name
return destination
}
}

public func extractSwiftlyAndInstall(from archive: URL) throws {
public func extractSwiftlyAndInstall(_ ctx: SwiftlyCoreContext, from archive: URL) throws {
guard archive.fileExists() else {
throw SwiftlyError(message: "\(archive) doesn't exist")
}

let tmpDir = self.getTempFilePath()
defer {
try? FileManager.default.removeItem(at: tmpDir)
}
try FileManager.default.createDirectory(atPath: tmpDir.path, withIntermediateDirectories: true)

SwiftlyCore.print("Extracting new swiftly...")
ctx.print("Extracting new swiftly...")
try extractArchive(atPath: archive) { name in
// Extract to the temporary directory
tmpDir.appendingPathComponent(String(name))
Expand All @@ -379,8 +375,8 @@ public struct Linux: Platform {
try self.runProgram(tmpDir.appendingPathComponent("swiftly").path, "init")
}

public func uninstall(_ toolchain: ToolchainVersion, verbose _: Bool) throws {
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent(toolchain.name)
public func uninstall(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, verbose _: Bool) throws {
let toolchainDir = self.swiftlyToolchainsDir(ctx).appendingPathComponent(toolchain.name)
try FileManager.default.removeItem(at: toolchainDir)
}

Expand All @@ -394,9 +390,9 @@ public struct Linux: Platform {
FileManager.default.temporaryDirectory.appendingPathComponent("swiftly-\(UUID())")
}

public func verifySignature(httpClient: SwiftlyHTTPClient, archiveDownloadURL: URL, archive: URL, verbose: Bool) async throws {
public func verifySignature(_ ctx: SwiftlyCoreContext, archiveDownloadURL: URL, archive: URL, verbose: Bool) async throws {
if verbose {
SwiftlyCore.print("Downloading toolchain signature...")
ctx.print("Downloading toolchain signature...")
}

let sigFile = self.getTempFilePath()
Expand All @@ -405,20 +401,24 @@ public struct Linux: Platform {
try? FileManager.default.removeItem(at: sigFile)
}

try await httpClient.downloadFile(
try await ctx.httpClient.downloadFile(
url: archiveDownloadURL.appendingPathExtension("sig"),
to: sigFile
)

SwiftlyCore.print("Verifying toolchain signature...")
ctx.print("Verifying toolchain signature...")
do {
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose)
if let mockedHomeDir = ctx.mockedHomeDir {
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: false, env: ["GNUPGHOME": mockedHomeDir.appendingPathComponent(".gnupg").path])
} else {
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose)
}
} catch {
throw SwiftlyError(message: "Signature verification failed: \(error).")
}
}

private func manualSelectPlatform(_ platformPretty: String?) async -> PlatformDefinition {
private func manualSelectPlatform(_ ctx: SwiftlyCoreContext, _ platformPretty: String?) async -> PlatformDefinition {
if let platformPretty {
print("\(platformPretty) is not an officially supported platform, but the toolchains for another platform may still work on it.")
} else {
Expand All @@ -434,7 +434,7 @@ public struct Linux: Platform {
\(selections)
""")

let choice = SwiftlyCore.readLine(prompt: "Pick one of the available selections [0-\(self.linuxPlatforms.count)] ") ?? "0"
let choice = ctx.readLine(prompt: "Pick one of the available selections [0-\(self.linuxPlatforms.count)] ") ?? "0"

guard let choiceNum = Int(choice) else {
fatalError("Installation canceled")
Expand All @@ -447,7 +447,7 @@ public struct Linux: Platform {
return self.linuxPlatforms[choiceNum - 1]
}

public func detectPlatform(disableConfirmation: Bool, platform: String?) async throws -> PlatformDefinition {
public func detectPlatform(_ ctx: SwiftlyCoreContext, disableConfirmation: Bool, platform: String?) async throws -> PlatformDefinition {
// We've been given a hint to use
if let platform {
guard let pd = linuxPlatforms.first(where: { $0.nameFull == platform }) else {
Expand Down Expand Up @@ -475,7 +475,7 @@ public struct Linux: Platform {
} else {
print(message)
}
return await self.manualSelectPlatform(platformPretty)
return await self.manualSelectPlatform(ctx, platformPretty)
}

let releaseInfo = try String(contentsOfFile: releaseFile, encoding: .utf8)
Expand All @@ -502,7 +502,7 @@ public struct Linux: Platform {
} else {
print(message)
}
return await self.manualSelectPlatform(platformPretty)
return await self.manualSelectPlatform(ctx, platformPretty)
}

if (id + (idlike ?? "")).contains("amzn") {
Expand All @@ -513,7 +513,7 @@ public struct Linux: Platform {
} else {
print(message)
}
return await self.manualSelectPlatform(platformPretty)
return await self.manualSelectPlatform(ctx, platformPretty)
}

return .amazonlinux2
Expand All @@ -525,7 +525,7 @@ public struct Linux: Platform {
} else {
print(message)
}
return await self.manualSelectPlatform(platformPretty)
return await self.manualSelectPlatform(ctx, platformPretty)
}

return .rhel9
Expand All @@ -539,7 +539,7 @@ public struct Linux: Platform {
} else {
print(message)
}
return await self.manualSelectPlatform(platformPretty)
return await self.manualSelectPlatform(ctx, platformPretty)
}

public func getShell() async throws -> String {
Expand All @@ -559,8 +559,8 @@ public struct Linux: Platform {
return "/bin/bash"
}

public func findToolchainLocation(_ toolchain: ToolchainVersion) -> URL {
self.swiftlyToolchainsDir.appendingPathComponent("\(toolchain.name)")
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> URL {
self.swiftlyToolchainsDir(ctx).appendingPathComponent("\(toolchain.name)")
}

public static let currentPlatform: any Platform = Linux()
Expand Down
Loading