diff --git a/Sources/SwiftlyCore/Commands.swift b/Sources/SwiftlyCore/Commands.swift index bb4a81b5..b3cb0d98 100644 --- a/Sources/SwiftlyCore/Commands.swift +++ b/Sources/SwiftlyCore/Commands.swift @@ -922,7 +922,7 @@ extension SystemCommand { case arch(String) case configuration(String) case packagePath(FilePath) - case pkgConfigPath(String) + case pkgConfigPath(FilePath) case product(String) case swiftSdk(String) case staticSwiftStdlib diff --git a/Sources/SwiftlyCore/ModeledCommandLine.swift b/Sources/SwiftlyCore/ModeledCommandLine.swift index 42bc4c5b..94c07622 100644 --- a/Sources/SwiftlyCore/ModeledCommandLine.swift +++ b/Sources/SwiftlyCore/ModeledCommandLine.swift @@ -15,6 +15,12 @@ public struct Configuration: Sendable { public var arguments: Arguments /// The environment to use when running the executable. public var environment: Environment + + public init(executable: Executable, arguments: Arguments, environment: Environment) { + self.executable = executable + self.arguments = arguments + self.environment = environment + } } public struct Executable: Sendable, Hashable { diff --git a/Sources/SwiftlyWebsiteAPI/openapi.yaml b/Sources/SwiftlyWebsiteAPI/openapi.yaml index 61d3efea..03ba23c3 100644 --- a/Sources/SwiftlyWebsiteAPI/openapi.yaml +++ b/Sources/SwiftlyWebsiteAPI/openapi.yaml @@ -284,6 +284,9 @@ components: items: $ref: '#/components/schemas/Architecture' description: List of supported architectures. + checksum: + type: string + description: SHA-256 Checksum of the static SDK, if this platform is the static SDK. required: - name - platform diff --git a/Sources/TestSwiftly/TestSwiftly.swift b/Sources/TestSwiftly/TestSwiftly.swift index 49fda0ec..f2c5f3a8 100644 --- a/Sources/TestSwiftly/TestSwiftly.swift +++ b/Sources/TestSwiftly/TestSwiftly.swift @@ -17,8 +17,60 @@ let currentPlatform: Platform = MacOS.currentPlatform #error("Unsupported platform") #endif +typealias sys = SwiftlyCore.SystemCommand typealias fs = SwiftlyCore.FileSystem +func sh(executable: Executable = ShCommand.defaultExecutable, _ options: ShCommand.Option...) -> ShCommand { + sh(executable: executable, options) +} + +func sh(executable: Executable = ShCommand.defaultExecutable, _ options: [ShCommand.Option]) -> ShCommand { + ShCommand(executable: executable, options) +} + +struct ShCommand { + static var defaultExecutable: Executable { .name("sh") } + + var executable: Executable + + var options: [Option] + + enum Option { + case login + case command(String) + + func args() -> [String] { + switch self { + case .login: + ["-l"] + case let .command(command): + ["-c", command] + } + } + } + + init(executable: Executable, _ options: [Option]) { + self.executable = executable + self.options = options + } + + func config() -> Configuration { + var args: [String] = [] + + for opt in self.options { + args.append(contentsOf: opt.args()) + } + + return Configuration( + executable: self.executable, + arguments: Arguments(args), + environment: .inherit + ) + } +} + +extension ShCommand: Runnable {} + @main struct TestSwiftly: AsyncParsableCommand { @Flag(name: [.customShort("y"), .long], help: "Disable confirmation prompts by assuming 'yes'") @@ -40,11 +92,13 @@ struct TestSwiftly: AsyncParsableCommand { Foundation.exit(2) } + guard case let swiftlyArchive = FilePath(swiftlyArchive) else { fatalError("") } + print("Extracting swiftly release") #if os(Linux) - try currentPlatform.runProgram("tar", "-zxvf", swiftlyArchive, quiet: false) + try await sys.tar().extract(.verbose, .compressed, .archive(swiftlyArchive)).run(currentPlatform, quiet: false) #elseif os(macOS) - try currentPlatform.runProgram("installer", "-pkg", swiftlyArchive, "-target", "CurrentUserHomeDirectory", quiet: false) + try await sys.installer(.verbose, pkg: swiftlyArchive, target: "CurrentUserHomeDirectory").run(currentPlatform, quiet: false) #endif #if os(Linux) @@ -54,7 +108,7 @@ struct TestSwiftly: AsyncParsableCommand { #endif var env = ProcessInfo.processInfo.environment - let shell = try await currentPlatform.getShell() + let shell = FilePath(try await currentPlatform.getShell()) var customLoc: FilePath? if self.customLocation { @@ -66,37 +120,39 @@ struct TestSwiftly: AsyncParsableCommand { env["SWIFTLY_TOOLCHAINS_DIR"] = (customLoc! / "toolchains").string try currentPlatform.runProgram(extractedSwiftly.string, "init", "--assume-yes", "--no-modify-profile", "--skip-install", quiet: false, env: env) - try currentPlatform.runProgram(shell, "-l", "-c", ". \"\(customLoc! / "env.sh")\" && swiftly install --assume-yes latest --post-install-file=./post-install.sh", quiet: false, env: env) + try await sh(executable: .path(shell), .login, .command(". \"\(customLoc! / "env.sh")\" && swiftly install --assume-yes latest --post-install-file=./post-install.sh")).run(currentPlatform, env: env, quiet: false) } else { print("Installing swiftly to the default location.") // Setting this environment helps to ensure that the profile gets sourced with bash, even if it is not in an interactive shell - if shell.hasSuffix("bash") { + if shell.ends(with: "bash") { env["BASH_ENV"] = (fs.home / ".profile").string - } else if shell.hasSuffix("zsh") { + } else if shell.ends(with: "zsh") { env["ZDOTDIR"] = fs.home.string - } else if shell.hasSuffix("fish") { + } else if shell.ends(with: "fish") { env["XDG_CONFIG_HOME"] = (fs.home / ".config").string } try currentPlatform.runProgram(extractedSwiftly.string, "init", "--assume-yes", "--skip-install", quiet: false, env: env) - try currentPlatform.runProgram(shell, "-l", "-c", "swiftly install --assume-yes latest --post-install-file=./post-install.sh", quiet: false, env: env) + try await sh(executable: .path(shell), .login, .command("swiftly install --assume-yes latest --post-install-file=./post-install.sh")).run(currentPlatform, env: env, quiet: false) } var swiftReady = false - if NSUserName() == "root" && FileManager.default.fileExists(atPath: "./post-install.sh") { - try currentPlatform.runProgram(shell, "./post-install.sh", quiet: false) + if NSUserName() == "root" { + if try await fs.exists(atPath: "./post-install.sh") { + try currentPlatform.runProgram(shell.string, "./post-install.sh", quiet: false) + } swiftReady = true - } else if FileManager.default.fileExists(atPath: "./post-install.sh") { + } else if try await fs.exists(atPath: "./post-install.sh") { print("WARNING: not running as root, so skipping the post installation steps and final swift verification.") } else { swiftReady = true } if let customLoc = customLoc, swiftReady { - try currentPlatform.runProgram(shell, "-l", "-c", ". \"\(customLoc / "env.sh")\" && swift --version", quiet: false, env: env) + try await sh(executable: .path(shell), .login, .command(". \"\(customLoc / "env.sh")\" && swift --version")).run(currentPlatform, env: env, quiet: false) } else if swiftReady { - try currentPlatform.runProgram(shell, "-l", "-c", "swift --version", quiet: false, env: env) + try await sh(executable: .path(shell), .login, .command("swift --version")).run(currentPlatform, env: env, quiet: false) } } } diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index cb8cbcdb..6483444c 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -1,4 +1,5 @@ import ArgumentParser +import AsyncHTTPClient import Foundation import SwiftlyCore import SystemPackage @@ -29,16 +30,6 @@ extension Runnable { } } -public struct SwiftPlatform: Codable { - public var name: String? - public var checksum: String? -} - -public struct SwiftRelease: Codable { - public var name: String? - public var platforms: [SwiftPlatform]? -} - // These functions are cloned and adapted from SwiftlyCore until we can do better bootstrapping public struct Error: LocalizedError, CustomStringConvertible { public let message: String @@ -51,93 +42,6 @@ public struct Error: LocalizedError, CustomStringConvertible { public var description: String { self.message } } -public func runProgramEnv(_ args: String..., quiet: Bool = false, env: [String: String]?) throws { - if !quiet { print("\(args.joined(separator: " "))") } - - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - process.arguments = args - - if let env = env { - process.environment = env - } - - if quiet { - process.standardOutput = nil - process.standardError = nil - } - - try process.run() - // Attach this process to our process group so that Ctrl-C and other signals work - let pgid = tcgetpgrp(STDOUT_FILENO) - if pgid != -1 { - tcsetpgrp(STDOUT_FILENO, process.processIdentifier) - } - process.waitUntilExit() - - guard process.terminationStatus == 0 else { - throw Error(message: "\(args.first!) exited with non-zero status: \(process.terminationStatus)") - } -} - -public func runProgram(_ args: String..., quiet: Bool = false) throws { - if !quiet { print("\(args.joined(separator: " "))") } - - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - process.arguments = args - - if quiet { - process.standardOutput = nil - process.standardError = nil - } - - try process.run() - // Attach this process to our process group so that Ctrl-C and other signals work - let pgid = tcgetpgrp(STDOUT_FILENO) - if pgid != -1 { - tcsetpgrp(STDOUT_FILENO, process.processIdentifier) - } - process.waitUntilExit() - - guard process.terminationStatus == 0 else { - throw Error(message: "\(args.first!) exited with non-zero status: \(process.terminationStatus)") - } -} - -public func runProgramOutput(_ program: String, _ args: String...) async throws -> String? { - print("\(program) \(args.joined(separator: " "))") - - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - process.arguments = [program] + args - - let outPipe = Pipe() - process.standardInput = FileHandle.nullDevice - process.standardOutput = outPipe - - try process.run() - // Attach this process to our process group so that Ctrl-C and other signals work - let pgid = tcgetpgrp(STDOUT_FILENO) - if pgid != -1 { - tcsetpgrp(STDOUT_FILENO, process.processIdentifier) - } - let outData = try outPipe.fileHandleForReading.readToEnd() - - process.waitUntilExit() - - guard process.terminationStatus == 0 else { - print("\(args.first!) exited with non-zero status: \(process.terminationStatus)") - throw Error(message: "\(args.first!) exited with non-zero status: \(process.terminationStatus)") - } - - if let outData { - return String(data: outData, encoding: .utf8) - } else { - return nil - } -} - @main struct BuildSwiftlyRelease: AsyncParsableCommand { static let configuration = CommandConfiguration( @@ -177,29 +81,17 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { #endif } - func assertTool(_ name: String, message: String) async throws -> String { - guard let _ = try? await runProgramOutput(currentPlatform.getShell(), "-c", "which which") else { - throw Error(message: "The which command could not be found. Please install it with your package manager.") - } - - guard let location = try? await runProgramOutput(currentPlatform.getShell(), "-c", "which \(name)") else { - throw Error(message: message) - } - - return location.replacingOccurrences(of: "\n", with: "") - } - - func findSwiftVersion() throws -> String? { - var cwd = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + func findSwiftVersion() async throws -> String? { + var cwd = fs.cwd - while cwd.path != "" && cwd.path != "/" { - guard FileManager.default.fileExists(atPath: cwd.path) else { + while !cwd.isEmpty && !cwd.removingRoot().isEmpty { + guard try await fs.exists(atPath: cwd) else { break } - let svFile = cwd.appendingPathComponent(".swift-version") + let svFile = cwd / ".swift-version" - if FileManager.default.fileExists(atPath: svFile.path) { + if try await fs.exists(atPath: svFile) { let selector = try? String(contentsOf: svFile, encoding: .utf8) if let selector { return selector.replacingOccurrences(of: "\n", with: "") @@ -207,7 +99,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { return selector } - cwd = cwd.deletingLastPathComponent() + cwd = cwd.removingLastComponent() } return nil @@ -229,19 +121,16 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { } } - func collectLicenses(_ licenseDir: String) async throws { - try FileManager.default.createDirectory(atPath: licenseDir, withIntermediateDirectories: true) + func collectLicenses(_ licenseDir: FilePath) async throws { + try await fs.mkdir(.parents, atPath: licenseDir) - let cwd = FileManager.default.currentDirectoryPath + let cwd = fs.cwd // Copy the swiftly license to the bundle - try FileManager.default.copyItem(atPath: cwd + "/LICENSE.txt", toPath: licenseDir + "/LICENSE.txt") + try await fs.copy(atPath: cwd / "LICENSE.txt", toPath: licenseDir / "LICENSE.txt") } func buildLinuxRelease() async throws { - // TODO: turn these into checks that the system meets the criteria for being capable of using the toolchain + checking for packages, not tools - let curl = try await self.assertTool("curl", message: "Please install curl with `yum install curl`") - try await self.checkGitRepoStatus() // Start with a fresh SwiftPM package @@ -251,34 +140,52 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { let libArchiveVersion = "3.7.9" let libArchiveTarSha = "aa90732c5a6bdda52fda2ad468ac98d75be981c15dde263d7b5cf6af66fd009f" - let buildCheckoutsDir = FileManager.default.currentDirectoryPath + "/.build/checkouts" - let libArchivePath = buildCheckoutsDir + "/libarchive-\(libArchiveVersion)" - let pkgConfigPath = libArchivePath + "/pkgconfig" + let buildCheckoutsDir = fs.cwd / ".build/checkouts" + let libArchivePath = buildCheckoutsDir / "libarchive-\(libArchiveVersion)" + let pkgConfigPath = libArchivePath / "pkgconfig" + + try? await fs.mkdir(.parents, atPath: buildCheckoutsDir) + try? await fs.mkdir(.parents, atPath: pkgConfigPath) - try? FileManager.default.createDirectory(atPath: buildCheckoutsDir, withIntermediateDirectories: true) - try? FileManager.default.createDirectory(atPath: pkgConfigPath, withIntermediateDirectories: true) + try? await fs.remove(atPath: libArchivePath) - try? FileManager.default.removeItem(atPath: libArchivePath) - try runProgram(curl, "-L", "-o", "\(buildCheckoutsDir + "/libarchive-\(libArchiveVersion).tar.gz")", "--remote-name", "--location", "https://github.com/libarchive/libarchive/releases/download/v\(libArchiveVersion)/libarchive-\(libArchiveVersion).tar.gz") - let libArchiveTarShaActual = try await sys.sha256sum(files: FilePath("\(buildCheckoutsDir)/libarchive-\(libArchiveVersion).tar.gz")).output(currentPlatform) + // Download libarchive + let libarchiveRequest = HTTPClientRequest(url: "https://github.com/libarchive/libarchive/releases/download/v\(libArchiveVersion)/libarchive-\(libArchiveVersion).tar.gz") + let libarchiveResponse = try await HTTPClient.shared.execute(libarchiveRequest, timeout: .seconds(60)) + guard libarchiveResponse.status == .ok else { + throw Error(message: "Download failed with status: \(libarchiveResponse.status)") + } + let buf = try await libarchiveResponse.body.collect(upTo: 20 * 1024 * 1024) + guard let contents = buf.getBytes(at: 0, length: buf.readableBytes) else { + throw Error(message: "Unable to read all of the bytes") + } + let data = Data(contents) + try data.write(to: buildCheckoutsDir / "libarchive-\(libArchiveVersion).tar.gz") + + let libArchiveTarShaActual = try await sys.sha256sum(files: buildCheckoutsDir / "libarchive-\(libArchiveVersion).tar.gz").output(currentPlatform) guard let libArchiveTarShaActual, libArchiveTarShaActual.starts(with: libArchiveTarSha) else { let shaActual = libArchiveTarShaActual ?? "none" throw Error(message: "The libarchive tar.gz file sha256sum is \(shaActual), but expected \(libArchiveTarSha)") } - try await sys.tar(.directory(FilePath(buildCheckoutsDir))).extract(.compressed, .archive(FilePath("\(buildCheckoutsDir)/libarchive-\(libArchiveVersion).tar.gz"))).run(currentPlatform) + try await sys.tar(.directory(buildCheckoutsDir)).extract(.compressed, .archive(buildCheckoutsDir / "libarchive-\(libArchiveVersion).tar.gz")).run(currentPlatform) - let cwd = FileManager.default.currentDirectoryPath - FileManager.default.changeCurrentDirectoryPath(libArchivePath) + let cwd = fs.cwd + FileManager.default.changeCurrentDirectoryPath(libArchivePath.string) let swiftVerRegex: Regex<(Substring, Substring)> = try! Regex("Swift version (\\d+\\.\\d+\\.?\\d*) ") - let swiftVerOutput = (try await runProgramOutput("swift", "--version")) ?? "" + let swiftVerOutput = (try await currentPlatform.runProgramOutput("swift", "--version")) ?? "" guard let swiftVerMatch = try swiftVerRegex.firstMatch(in: swiftVerOutput) else { throw Error(message: "Unable to detect swift version") } let swiftVersion = swiftVerMatch.output.1 + let httpExecutor = HTTPRequestExecutorImpl() + guard let swiftRelease = (try await httpExecutor.getReleaseToolchains()).first(where: { $0.name == swiftVersion }) else { + throw Error(message: "Unable to find swift release using swift.org API: \(swiftVersion)") + } + let sdkName = "swift-\(swiftVersion)-RELEASE_static-linux-0.0.1" #if arch(arm64) @@ -287,14 +194,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { let arch = "x86_64" #endif - let swiftReleasesJson = (try await runProgramOutput(curl, "https://www.swift.org/api/v1/install/releases.json")) ?? "[]" - let swiftReleases = try JSONDecoder().decode([SwiftRelease].self, from: swiftReleasesJson.data(using: .utf8)!) - - guard let swiftRelease = swiftReleases.first(where: { ($0.name ?? "") == swiftVersion }) else { - throw Error(message: "Unable to find swift release using swift.org API: \(swiftVersion)") - } - - guard let sdkPlatform = (swiftRelease.platforms ?? [SwiftPlatform]()).first(where: { ($0.name ?? "") == "Static SDK" }) else { + guard let sdkPlatform = swiftRelease.platforms.first(where: { $0.name == "Static SDK" }) else { throw Error(message: "Swift release \(swiftVersion) has no Static SDK offering") } @@ -302,9 +202,9 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { var customEnv = ProcessInfo.processInfo.environment customEnv["CC"] = "\(cwd)/Tools/build-swiftly-release/musl-clang" - customEnv["MUSL_PREFIX"] = "\(FileManager.default.homeDirectoryForCurrentUser.path)/.swiftpm/swift-sdks/\(sdkName).artifactbundle/\(sdkName)/swift-linux-musl/musl-1.2.5.sdk/\(arch)/usr" + customEnv["MUSL_PREFIX"] = "\(fs.home / ".swiftpm/swift-sdks/\(sdkName).artifactbundle/\(sdkName)/swift-linux-musl/musl-1.2.5.sdk/\(arch)/usr")" - try runProgramEnv( + try currentPlatform.runProgram( "./configure", "--prefix=\(pkgConfigPath)", "--enable-shared=no", @@ -330,38 +230,38 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { try await sys.make().install().run(currentPlatform) - FileManager.default.changeCurrentDirectoryPath(cwd) + FileManager.default.changeCurrentDirectoryPath(cwd.string) - try await sys.swift().build(.swiftSdk("\(arch)-swift-linux-musl"), .product("swiftly"), .pkgConfigPath("\(pkgConfigPath)/lib/pkgconfig"), .staticSwiftStdlib, .configuration("release")).run(currentPlatform) + try await sys.swift().build(.swiftSdk("\(arch)-swift-linux-musl"), .product("swiftly"), .pkgConfigPath(pkgConfigPath / "lib/pkgconfig"), .staticSwiftStdlib, .configuration("release")).run(currentPlatform) - let releaseDir = cwd + "/.build/release" + let releaseDir = cwd / ".build/release" // Strip the symbols from the binary to decrease its size - try await sys.strip(names: FilePath(releaseDir) / "swiftly").run(currentPlatform) + try await sys.strip(names: releaseDir / "swiftly").run(currentPlatform) try await self.collectLicenses(releaseDir) #if arch(arm64) - let releaseArchive = "\(releaseDir)/swiftly-\(version)-aarch64.tar.gz" + let releaseArchive = releaseDir / "swiftly-\(version)-aarch64.tar.gz" #else - let releaseArchive = "\(releaseDir)/swiftly-\(version)-x86_64.tar.gz" + let releaseArchive = releaseDir / "swiftly-\(version)-x86_64.tar.gz" #endif - try await sys.tar(.directory(FilePath(releaseDir))).create(.compressed, .archive(FilePath(releaseArchive)), files: "swiftly", "LICENSE.txt").run(currentPlatform) + try await sys.tar(.directory(releaseDir)).create(.compressed, .archive(releaseArchive), files: "swiftly", "LICENSE.txt").run(currentPlatform) print(releaseArchive) if self.test { - let debugDir = cwd + "/.build/debug" + let debugDir = cwd / ".build/debug" #if arch(arm64) - let testArchive = "\(debugDir)/test-swiftly-linux-aarch64.tar.gz" + let testArchive = debugDir / "test-swiftly-linux-aarch64.tar.gz" #else - let testArchive = "\(debugDir)/test-swiftly-linux-x86_64.tar.gz" + let testArchive = debugDir / "test-swiftly-linux-x86_64.tar.gz" #endif - try await sys.swift().build(.swiftSdk("\(arch)-swift-linux-musl"), .product("test-swiftly"), .pkgConfigPath("\(pkgConfigPath)/lib/pkgconfig"), .staticSwiftStdlib, .configuration("debug")).run(currentPlatform) - try await sys.tar(.directory(FilePath(debugDir))).create(.compressed, .archive(FilePath(testArchive)), files: "test-swiftly").run(currentPlatform) + try await sys.swift().build(.swiftSdk("\(arch)-swift-linux-musl"), .product("test-swiftly"), .pkgConfigPath(pkgConfigPath / "lib/pkgconfig"), .staticSwiftStdlib, .configuration("debug")).run(currentPlatform) + try await sys.tar(.directory(debugDir)).create(.compressed, .archive(testArchive), files: "test-swiftly").run(currentPlatform) print(testArchive) } @@ -388,14 +288,14 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { .create(output: swiftlyBinDir / "swiftly") .runEcho(currentPlatform) - let swiftlyLicenseDir = FileManager.default.currentDirectoryPath + "/.build/release/.swiftly/license" - try? FileManager.default.createDirectory(atPath: swiftlyLicenseDir, withIntermediateDirectories: true) + let swiftlyLicenseDir = fs.cwd / ".build/release/.swiftly/license" + try? await fs.mkdir(.parents, atPath: swiftlyLicenseDir) try await self.collectLicenses(swiftlyLicenseDir) - let cwd = FileManager.default.currentDirectoryPath + let cwd = fs.cwd - let releaseDir = URL(fileURLWithPath: cwd + "/.build/release") - let pkgFile = releaseDir.appendingPathComponent("/swiftly-\(self.version).pkg") + let releaseDir = cwd / ".build/release" + let pkgFile = releaseDir / "swiftly-\(self.version).pkg" if let cert { try await sys.pkgbuild( @@ -404,7 +304,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { .identifier(identifier), .sign(cert), root: swiftlyBinDir.parent, - packageOutputPath: FilePath(".build/release/swiftly-\(self.version).pkg") + packageOutputPath: releaseDir / "swiftly-\(self.version).pkg" ).runEcho(currentPlatform) } else { try await sys.pkgbuild( @@ -412,39 +312,39 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { .version(self.version), .identifier(identifier), root: swiftlyBinDir.parent, - packageOutputPath: FilePath(".build/release/swiftly-\(self.version).pkg") + packageOutputPath: releaseDir / "swiftly-\(self.version).pkg" ).runEcho(currentPlatform) } // Re-configure the pkg to prefer installs into the current user's home directory with the help of productbuild. // Note that command-line installs can override this preference, but the GUI install will limit the choices. - let pkgFileReconfigured = releaseDir.appendingPathComponent("swiftly-\(self.version)-reconfigured.pkg") - let distFile = releaseDir.appendingPathComponent("distribution.plist") + let pkgFileReconfigured = releaseDir / "swiftly-\(self.version)-reconfigured.pkg" + let distFile = releaseDir / "distribution.plist" - try await sys.productbuild().synthesize(package: FilePath(pkgFile.path), distributionOutputPath: FilePath(distFile.path)).runEcho(currentPlatform) + try await sys.productbuild().synthesize(package: pkgFile, distributionOutputPath: distFile).runEcho(currentPlatform) var distFileContents = try String(contentsOf: distFile, encoding: .utf8) distFileContents = distFileContents.replacingOccurrences(of: "", with: "swiftly") try distFileContents.write(to: distFile, atomically: true, encoding: .utf8) if let cert = cert { - try await sys.productbuild().distribution(.packagePath(FilePath(pkgFile.deletingLastPathComponent().path)), .sign(cert), distPath: FilePath(distFile.path), productOutputPath: FilePath(pkgFileReconfigured.path)).runEcho(currentPlatform) + try await sys.productbuild().distribution(.packagePath(pkgFile.parent), .sign(cert), distPath: distFile, productOutputPath: pkgFileReconfigured).runEcho(currentPlatform) } else { - try await sys.productbuild().distribution(.packagePath(FilePath(pkgFile.deletingLastPathComponent().path)), distPath: FilePath(distFile.path), productOutputPath: FilePath(pkgFileReconfigured.path)).runEcho(currentPlatform) + try await sys.productbuild().distribution(.packagePath(pkgFile.parent), distPath: distFile, productOutputPath: pkgFileReconfigured).runEcho(currentPlatform) } - try FileManager.default.removeItem(at: pkgFile) - try FileManager.default.copyItem(atPath: pkgFileReconfigured.path, toPath: pkgFile.path) + try await fs.remove(atPath: pkgFile) + try await fs.copy(atPath: pkgFileReconfigured, toPath: pkgFile) - print(pkgFile.path) + print(pkgFile) if self.test { for arch in ["x86_64", "arm64"] { try await sys.swift().build(.product("test-swiftly"), .configuration("debug"), .arch("\(arch)")).runEcho(currentPlatform) - try await sys.strip(names: FilePath(".build") / "\(arch)-apple-macosx/release/swiftly").runEcho(currentPlatform) + try await sys.strip(names: ".build" / "\(arch)-apple-macosx/release/swiftly").runEcho(currentPlatform) } - let testArchive = releaseDir.appendingPathComponent("test-swiftly-macos.tar.gz") + let testArchive = releaseDir / "test-swiftly-macos.tar.gz" try await sys.lipo( inputFiles: ".build/x86_64-apple-macosx/debug/test-swiftly", ".build/arm64-apple-macosx/debug/test-swiftly" @@ -452,9 +352,9 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { .create(output: swiftlyBinDir / "swiftly") .runEcho(currentPlatform) - try await sys.tar(.directory(FilePath(".build/x86_64-apple-macosx/debug"))).create(.compressed, .archive(FilePath(testArchive.path)), files: "test-swiftly").run(currentPlatform) + try await sys.tar(.directory(".build/x86_64-apple-macosx/debug")).create(.compressed, .archive(testArchive), files: "test-swiftly").run(currentPlatform) - print(testArchive.path) + print(testArchive) } } }