144 lines
4.4 KiB
Swift
144 lines
4.4 KiB
Swift
// Copyright 2020 Carton contributors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import CartonCore
|
|
import CartonHelpers
|
|
import Foundation
|
|
|
|
private let expectedArchiveSize = 891_856_371
|
|
|
|
extension AsyncFileDownload.Progress {
|
|
fileprivate var totalOrEstimatedBytes: Int {
|
|
totalBytes ?? expectedArchiveSize
|
|
}
|
|
}
|
|
|
|
extension ToolchainSystem {
|
|
func installSDK(
|
|
version: String,
|
|
from url: Foundation.URL,
|
|
to sdkPath: AbsolutePath,
|
|
_ terminal: InteractiveWriter
|
|
) async throws -> AbsolutePath {
|
|
if !fileSystem.exists(sdkPath, followSymlink: true) {
|
|
try fileSystem.createDirectory(sdkPath, recursive: true)
|
|
}
|
|
|
|
guard fileSystem.isDirectory(sdkPath) else {
|
|
throw ToolchainError.directoryDoesNotExist(sdkPath)
|
|
}
|
|
|
|
let ext = url.pathExtension
|
|
|
|
let archivePath = sdkPath.appending(component: "\(version).\(ext)")
|
|
|
|
// Clean up the downloaded file (especially important for failed downloads, otherwise running
|
|
// `carton` again will fail trying to pick up the broken download).
|
|
defer {
|
|
do {
|
|
try fileSystem.removeFileTree(archivePath)
|
|
} catch {
|
|
terminal.write("Failed to remove downloaded file with error \(error)\n", inColor: .red)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let fileDownload = AsyncFileDownload(
|
|
path: archivePath.pathString,
|
|
url,
|
|
onTotalBytes: {
|
|
terminal.write("Archive size is \($0 / 1_000_000) MB\n", inColor: .yellow)
|
|
}
|
|
)
|
|
|
|
let animation = PercentProgressAnimation(
|
|
stream: stdoutStream,
|
|
header: "Downloading the archive"
|
|
)
|
|
defer { terminal.write("\n") }
|
|
|
|
var previouslyReceived = 0
|
|
for try await progress in fileDownload.progressStream {
|
|
guard progress.receivedBytes - previouslyReceived >= (progress.totalOrEstimatedBytes / 100)
|
|
else {
|
|
continue
|
|
}
|
|
defer { previouslyReceived = progress.receivedBytes }
|
|
|
|
animation.update(
|
|
step: progress.receivedBytes,
|
|
total: progress.totalOrEstimatedBytes,
|
|
text: "saving to \(archivePath.pathString)"
|
|
)
|
|
}
|
|
} catch {
|
|
terminal.write("Download failed with error \(error)\n", inColor: .red)
|
|
throw error
|
|
}
|
|
|
|
terminal.write("Download completed successfully\n", inColor: .green)
|
|
|
|
let installationPath: AbsolutePath
|
|
|
|
let arguments: [String]
|
|
if ext == "pkg" {
|
|
guard let resolver = userXCToolchainResolver else {
|
|
throw ToolchainError.noInstallationDirectory(path: "~/Library")
|
|
}
|
|
installationPath = resolver.toolchain(for: version)
|
|
arguments = [
|
|
"installer", "-target", "CurrentUserHomeDirectory", "-pkg", archivePath.pathString,
|
|
]
|
|
} else {
|
|
installationPath = sdkPath.appending(component: version)
|
|
try fileSystem.createDirectory(installationPath, recursive: true)
|
|
|
|
arguments = [
|
|
"tar", "xzf", archivePath.pathString, "--strip-components=1",
|
|
"--directory", installationPath.pathString,
|
|
]
|
|
}
|
|
terminal.logLookup("Unpacking the archive: ", arguments.joined(separator: " "))
|
|
try await Process.run(arguments, terminal)
|
|
|
|
if ext == "pkg", Self.isSnapshotVersion(version) {
|
|
try await patchSnapshotForMac(path: installationPath, terminal: terminal)
|
|
}
|
|
|
|
return installationPath
|
|
}
|
|
|
|
func patchSnapshotForMac(path: AbsolutePath, terminal: InteractiveWriter) async throws {
|
|
let binDir = path.appending(components: ["usr", "bin"])
|
|
|
|
terminal.write(
|
|
"To avoid issues with the snapshot, the toolchain will be re-signed.\n",
|
|
inColor: .yellow
|
|
)
|
|
|
|
for file in try fileSystem.traverseRecursively(binDir) {
|
|
guard fileSystem.isFile(file) else { continue }
|
|
|
|
try Foundation.Process.checkRun(
|
|
URL(fileURLWithPath: "/usr/bin/codesign"),
|
|
arguments: [
|
|
"--force",
|
|
"--preserve-metadata=identifier,entitlements",
|
|
"--sign", "-", file.pathString
|
|
]
|
|
)
|
|
}
|
|
}
|
|
}
|