-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathInit.swift
266 lines (216 loc) · 11.5 KB
/
Init.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
import ArgumentParser
import Foundation
import SwiftlyCore
internal struct Init: SwiftlyCommand {
public static var configuration = CommandConfiguration(
abstract: "Perform swiftly initialization into your user account."
)
@Flag(name: [.customShort("n"), .long], help: "Do not attempt to modify the profile file to set environment variables (e.g. PATH) on login.")
var noModifyProfile: Bool = false
@Flag(name: .shortAndLong, help: """
Overwrite the existing swiftly installation found at the configured SWIFTLY_HOME, if any. If this option is unspecified and an existing \
installation is found, the swiftly executable will be updated, but the rest of the installation will not be modified.
""")
var overwrite: Bool = false
@Option(name: .long, help: "Specify the current Linux platform for swiftly")
var platform: String?
@Flag(help: "Skip installing the latest toolchain")
var skipInstall: Bool = false
@OptionGroup var root: GlobalOptions
private enum CodingKeys: String, CodingKey {
case noModifyProfile, overwrite, platform, skipInstall, root
}
public mutating func validate() throws {}
internal mutating func run() async throws {
try await Self.execute(assumeYes: self.root.assumeYes, noModifyProfile: self.noModifyProfile, overwrite: self.overwrite, platform: self.platform, verbose: self.root.verbose, skipInstall: self.skipInstall)
}
/// Initialize the installation of swiftly.
internal static func execute(assumeYes: Bool, noModifyProfile: Bool, overwrite: Bool, platform: String?, verbose: Bool, skipInstall: Bool) async throws {
try Swiftly.currentPlatform.verifySwiftlySystemPrerequisites()
var config = try? Config.load()
if var config, !overwrite && config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0, suffix: "dev") {
// This is a simple upgrade from the 0.4.0-dev pre-release
// Move our executable over to the correct place
try Swiftly.currentPlatform.installSwiftlyBin()
// Update and save the version
config.version = SwiftlyCore.version
try config.save()
return
}
if let config, !overwrite && config.version != SwiftlyCore.version {
// We don't support downgrades, and versions prior to 0.4.0-dev
throw SwiftlyError(message: "An existing swiftly installation was detected. You can try again with '--overwrite' to overwrite it.")
}
// Give the user the prompt and the choice to abort at this point.
if !assumeYes {
#if os(Linux)
let sigMsg = " In the process of installing the new toolchain swiftly will add swift.org GnuPG keys into your keychain to verify the integrity of the downloads."
#else
let sigMsg = ""
#endif
let installMsg = if !skipInstall {
"\nOnce swiftly is installed it will install the latest available swift toolchain.\(sigMsg)\n"
} else { "" }
SwiftlyCore.print("""
Swiftly will be installed into the following locations:
\(Swiftly.currentPlatform.swiftlyHomeDir.path) - Data and configuration files directory including toolchains
\(Swiftly.currentPlatform.swiftlyBinDir.path) - Executables installation directory
These locations can be changed with SWIFTLY_HOME and SWIFTLY_BIN environment variables and run this again.
\(installMsg)
""")
guard SwiftlyCore.promptForConfirmation(defaultBehavior: true) else {
throw SwiftlyError(message: "Swiftly installation has been cancelled")
}
}
// Ensure swiftly doesn't overwrite any existing executables without getting confirmation first.
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir
let swiftlyBinDirContents = (try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.path)) ?? [String]()
let willBeOverwritten = Set(["swiftly"]).intersection(swiftlyBinDirContents)
if !willBeOverwritten.isEmpty && !overwrite {
SwiftlyCore.print("The following existing executables will be overwritten:")
for executable in willBeOverwritten {
SwiftlyCore.print(" \(swiftlyBinDir.appendingPathComponent(executable).path)")
}
guard SwiftlyCore.promptForConfirmation(defaultBehavior: false) else {
throw SwiftlyError(message: "Swiftly installation has been cancelled")
}
}
let shell = if let s = ProcessInfo.processInfo.environment["SHELL"] {
s
} else {
try await Swiftly.currentPlatform.getShell()
}
let envFile: URL
let sourceLine: String
if shell.hasSuffix("fish") {
envFile = Swiftly.currentPlatform.swiftlyHomeDir.appendingPathComponent("env.fish", isDirectory: false)
sourceLine = """
# Added by swiftly
source "\(envFile.path)"
"""
} else {
envFile = Swiftly.currentPlatform.swiftlyHomeDir.appendingPathComponent("env.sh", isDirectory: false)
sourceLine = """
# Added by swiftly
. "\(envFile.path)"
"""
}
if overwrite {
try? FileManager.default.removeItem(at: Swiftly.currentPlatform.swiftlyToolchainsDir)
try? FileManager.default.removeItem(at: Swiftly.currentPlatform.swiftlyHomeDir)
}
// Go ahead and create the directories as needed
for requiredDir in Swiftly.requiredDirectories {
if !requiredDir.fileExists() {
do {
try FileManager.default.createDirectory(at: requiredDir, withIntermediateDirectories: true)
} catch {
throw SwiftlyError(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)")
}
}
}
// Force the configuration to be present. Generate it if it doesn't already exist or overwrite is set
if overwrite || config == nil {
let pd = try await Swiftly.currentPlatform.detectPlatform(disableConfirmation: assumeYes, platform: platform)
var c = Config(inUse: nil, installedToolchains: [], platform: pd)
// Stamp the current version of swiftly on this config
c.version = SwiftlyCore.version
try c.save()
config = c
}
guard var config else { throw SwiftlyError(message: "Configuration could not be set") }
// Move our executable over to the correct place
try Swiftly.currentPlatform.installSwiftlyBin()
if overwrite || !FileManager.default.fileExists(atPath: envFile.path) {
SwiftlyCore.print("Creating shell environment file for the user...")
var env = ""
if shell.hasSuffix("fish") {
env = """
set -x SWIFTLY_HOME_DIR "\(Swiftly.currentPlatform.swiftlyHomeDir.path)"
set -x SWIFTLY_BIN_DIR "\(Swiftly.currentPlatform.swiftlyBinDir.path)"
if not contains "$SWIFTLY_BIN_DIR" $PATH
set -x PATH "$SWIFTLY_BIN_DIR" $PATH
end
"""
} else {
env = """
export SWIFTLY_HOME_DIR="\(Swiftly.currentPlatform.swiftlyHomeDir.path)"
export SWIFTLY_BIN_DIR="\(Swiftly.currentPlatform.swiftlyBinDir.path)"
if [[ ":$PATH:" != *":$SWIFTLY_BIN_DIR:"* ]]; then
export PATH="$SWIFTLY_BIN_DIR:$PATH"
fi
"""
}
try Data(env.utf8).write(to: envFile, options: .atomic)
}
if !noModifyProfile {
SwiftlyCore.print("Updating profile...")
let userHome = FileManager.default.homeDirectoryForCurrentUser
let profileHome: URL
if shell.hasSuffix("zsh") {
profileHome = userHome.appendingPathComponent(".zprofile", isDirectory: false)
} else if shell.hasSuffix("bash") {
if case let p = userHome.appendingPathComponent(".bash_profile", isDirectory: false), FileManager.default.fileExists(atPath: p.path) {
profileHome = p
} else if case let p = userHome.appendingPathComponent(".bash_login", isDirectory: false), FileManager.default.fileExists(atPath: p.path) {
profileHome = p
} else {
profileHome = userHome.appendingPathComponent(".profile", isDirectory: false)
}
} else if shell.hasSuffix("fish") {
if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"], case let xdgConfigURL = URL(fileURLWithPath: xdgConfigHome) {
let confDir = xdgConfigURL.appendingPathComponent("fish/conf.d", isDirectory: true)
try FileManager.default.createDirectory(at: confDir, withIntermediateDirectories: true)
profileHome = confDir.appendingPathComponent("swiftly.fish", isDirectory: false)
} else {
let confDir = userHome.appendingPathComponent(
".config/fish/conf.d", isDirectory: true
)
try FileManager.default.createDirectory(
at: confDir, withIntermediateDirectories: true
)
profileHome = confDir.appendingPathComponent("swiftly.fish", isDirectory: false)
}
} else {
profileHome = userHome.appendingPathComponent(".profile", isDirectory: false)
}
var addEnvToProfile = false
do {
if !FileManager.default.fileExists(atPath: profileHome.path) {
addEnvToProfile = true
} else if case let profileContents = try String(contentsOf: profileHome), !profileContents.contains(sourceLine) {
addEnvToProfile = true
}
} catch {
addEnvToProfile = true
}
var postInstall: String?
var pathChanged = false
if !skipInstall {
let latestVersion = try await Install.resolve(config: config, selector: ToolchainSelector.latest)
(postInstall, pathChanged) = try await Install.execute(version: latestVersion, &config, useInstalledToolchain: true, verifySignature: true, verbose: verbose, assumeYes: assumeYes)
}
if addEnvToProfile {
try Data(sourceLine.utf8).append(to: profileHome)
SwiftlyCore.print("""
To begin using installed swiftly from your current shell, first run the following command:
\(sourceLine)
""")
}
if pathChanged {
SwiftlyCore.print("""
Your shell caches items on your path for better performance. Swiftly has added items to your path that may not get picked up right away. You can run this command to update your shell to get these items.
hash -r
""")
}
if let postInstall {
SwiftlyCore.print("""
There are some dependencies that should be installed before using this toolchain.
You can run the following script as the system administrator (e.g. root) to prepare
your system:
\(postInstall)
""")
}
}
}
}