forked from swiftlang/swiftly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMacOS.swift
182 lines (145 loc) · 8.16 KB
/
MacOS.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import Foundation
import SwiftlyCore
public struct SwiftPkgInfo: Codable {
public var CFBundleIdentifier: String
public init(CFBundleIdentifier: String) {
self.CFBundleIdentifier = CFBundleIdentifier
}
}
/// `Platform` implementation for macOS systems.
public struct MacOS: Platform {
public init() {}
public var appDataDirectory: URL {
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Application Support", isDirectory: true)
}
public var swiftlyBinDir: URL {
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
?? ProcessInfo.processInfo.environment["SWIFTLY_BIN_DIR"].map { URL(fileURLWithPath: $0) }
?? FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Application Support/swiftly/bin", isDirectory: true)
}
public var swiftlyToolchainsDir: URL {
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("Toolchains", isDirectory: true) }
// The toolchains are always installed here by the installer. We bypass the installer in the case of test mocks
?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Developer/Toolchains", isDirectory: true)
}
public var toolchainFileExtension: String {
"pkg"
}
public func isSystemDependencyPresent(_: SystemDependency) -> Bool {
// All system dependencies on macOS should be present
true
}
public func verifySwiftlySystemPrerequisites() throws {
// All system prerequisites are there for swiftly on macOS
}
public func verifySystemPrerequisitesForInstall(httpClient _: SwiftlyHTTPClient, platformName _: String, version _: ToolchainVersion, requireSignatureValidation _: Bool) async throws -> String? {
// All system prerequisites should be there for macOS
nil
}
public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
guard tmpFile.fileExists() else {
throw Error(message: "\(tmpFile) doesn't exist")
}
if !self.swiftlyToolchainsDir.fileExists() {
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir, withIntermediateDirectories: false)
}
if SwiftlyCore.mockedHomeDir == nil {
SwiftlyCore.print("Installing package in user home directory...")
try runProgram("installer", "-verbose", "-pkg", tmpFile.path, "-target", "CurrentUserHomeDirectory", quiet: !verbose)
} else {
// In the case of a mock for testing purposes we won't use the installer, perferring a manual process because
// the installer will not install to an arbitrary path, only a volume or user home directory.
SwiftlyCore.print("Expanding pkg...")
let tmpDir = self.getTempFilePath()
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent("\(version.identifier).xctoolchain", isDirectory: true)
if !toolchainDir.fileExists() {
try FileManager.default.createDirectory(at: toolchainDir, withIntermediateDirectories: false)
}
try runProgram("pkgutil", "--verbose", "--expand", tmpFile.path, tmpDir.path, quiet: !verbose)
// There's a slight difference in the location of the special Payload file between official swift packages
// and the ones that are mocked here in the test framework.
var payload = tmpDir.appendingPathComponent("Payload")
if !payload.fileExists() {
payload = tmpDir.appendingPathComponent("\(version.identifier)-osx-package.pkg/Payload")
}
SwiftlyCore.print("Untarring pkg Payload...")
try runProgram("tar", "-C", toolchainDir.path, "-xvf", payload.path, quiet: !verbose)
}
}
public func extractSwiftlyAndInstall(from archive: URL) throws {
guard archive.fileExists() else {
throw Error(message: "\(archive) doesn't exist")
}
let homeDir: URL
if SwiftlyCore.mockedHomeDir == nil {
homeDir = FileManager.default.homeDirectoryForCurrentUser
SwiftlyCore.print("Extracting the swiftly package...")
try runProgram("installer", "-pkg", archive.path, "-target", "CurrentUserHomeDirectory")
try? runProgram("pkgutil", "--volume", homeDir.path, "--forget", "org.swift.swiftly")
} else {
homeDir = SwiftlyCore.mockedHomeDir ?? FileManager.default.homeDirectoryForCurrentUser
let installDir = homeDir.appendingPathComponent("usr/local")
try FileManager.default.createDirectory(atPath: installDir.path, withIntermediateDirectories: true)
// In the case of a mock for testing purposes we won't use the installer, perferring a manual process because
// the installer will not install to an arbitrary path, only a volume or user home directory.
let tmpDir = self.getTempFilePath()
try runProgram("pkgutil", "--expand", archive.path, tmpDir.path)
// There's a slight difference in the location of the special Payload file between official swift packages
// and the ones that are mocked here in the test framework.
let payload = tmpDir.appendingPathComponent("Payload")
guard payload.fileExists() else {
throw Error(message: "Payload file could not be found at \(tmpDir).")
}
try runProgram("tar", "-C", installDir.path, "-xf", payload.path)
}
try self.runProgram(homeDir.appendingPathComponent("usr/local/bin/swiftly").path, "init")
}
public func uninstall(_ toolchain: ToolchainVersion) throws {
SwiftlyCore.print("Uninstalling package in user home directory...")
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent("\(toolchain.identifier).xctoolchain", isDirectory: true)
let decoder = PropertyListDecoder()
let infoPlist = toolchainDir.appendingPathComponent("Info.plist")
guard let data = try? Data(contentsOf: infoPlist) else {
throw Error(message: "could not open \(infoPlist)")
}
guard let pkgInfo = try? decoder.decode(SwiftPkgInfo.self, from: data) else {
throw Error(message: "could not decode plist at \(infoPlist)")
}
try FileManager.default.removeItem(at: toolchainDir)
let homedir = ProcessInfo.processInfo.environment["HOME"]!
try? runProgram("pkgutil", "--volume", homedir, "--forget", pkgInfo.CFBundleIdentifier)
}
public func getExecutableName() -> String {
"swiftly-macos-osx"
}
public func getTempFilePath() -> URL {
FileManager.default.temporaryDirectory.appendingPathComponent("swiftly-\(UUID()).pkg")
}
public func verifySignature(httpClient _: SwiftlyHTTPClient, archiveDownloadURL _: URL, archive _: URL, verbose _: Bool) async throws {
// No signature verification is required on macOS since the pkg files have their own signing
// mechanism and the swift.org downloadables are trusted by stock macOS installations.
}
public func detectPlatform(disableConfirmation _: Bool, platform _: String?) async -> PlatformDefinition {
// No special detection required on macOS platform
PlatformDefinition.macOS
}
public func getShell() async throws -> String {
if let directoryInfo = try await runProgramOutput("dscl", ".", "-read", FileManager.default.homeDirectoryForCurrentUser.path) {
for line in directoryInfo.components(separatedBy: "\n") {
if line.hasPrefix("UserShell: ") {
if case let comps = line.components(separatedBy: ": "), comps.count == 2 {
return comps[1]
}
}
}
}
// Fall back to zsh on macOS
return "/bin/zsh"
}
public func findToolchainLocation(_ toolchain: ToolchainVersion) -> URL {
self.swiftlyToolchainsDir.appendingPathComponent("\(toolchain.identifier).xctoolchain")
}
public static let currentPlatform: any Platform = MacOS()
}