diff --git a/Sources/SwiftlyCore/Commands.swift b/Sources/SwiftlyCore/Commands.swift index 641fcfbd..ad837104 100644 --- a/Sources/SwiftlyCore/Commands.swift +++ b/Sources/SwiftlyCore/Commands.swift @@ -48,7 +48,7 @@ extension SystemCommand { self.read(path: path, keys: keys) } - public struct ReadCommand: Output { + public struct ReadCommand { var dscl: DsclCommand var path: FilePath? var keys: [String] @@ -76,7 +76,7 @@ extension SystemCommand { } } -extension SystemCommand.DsclCommand.ReadCommand { +extension SystemCommand.DsclCommand.ReadCommand: Output { public func properties(_ p: Platform) async throws -> [(key: String, value: String)] { let output = try await self.output(p) guard let output else { return [] } @@ -90,3 +90,63 @@ extension SystemCommand.DsclCommand.ReadCommand { return props } } + +extension SystemCommand { + public static func lipo(executable: Executable = LipoCommand.defaultExecutable, inputFiles: FilePath...) -> LipoCommand { + Self.lipo(executable: executable, inputFiles: inputFiles) + } + + public static func lipo(executable: Executable = LipoCommand.defaultExecutable, inputFiles: [FilePath]) -> LipoCommand { + LipoCommand(executable: executable, inputFiles: inputFiles) + } + + public struct LipoCommand { + public static var defaultExecutable: Executable { .name("lipo") } + + var executable: Executable + var inputFiles: [FilePath] + + public init(executable: Executable, inputFiles: [FilePath]) { + self.executable = executable + self.inputFiles = inputFiles + } + + func config() -> Configuration { + var args: [String] = [] + + args += self.inputFiles.map(\.string) + + return Configuration( + executable: self.executable, + arguments: Arguments(args), + environment: .inherit + ) + } + + public func create(output: FilePath) -> CreateCommand { + CreateCommand(self, output: output) + } + + public struct CreateCommand { + var lipo: LipoCommand + var output: FilePath + + init(_ lipo: LipoCommand, output: FilePath) { + self.lipo = lipo + self.output = output + } + + public func config() -> Configuration { + var c = self.lipo.config() + + var args = c.arguments.storage.map(\.description) + ["-create", "-output", "\(self.output)"] + + c.arguments = .init(args) + + return c + } + } + } +} + +extension SystemCommand.LipoCommand.CreateCommand: Runnable {} diff --git a/Sources/SwiftlyCore/FileManager+FilePath.swift b/Sources/SwiftlyCore/FileManager+FilePath.swift index 7c6779bc..5d1453b4 100644 --- a/Sources/SwiftlyCore/FileManager+FilePath.swift +++ b/Sources/SwiftlyCore/FileManager+FilePath.swift @@ -199,4 +199,6 @@ extension FilePath { public static func / (left: FilePath, right: String) -> FilePath { left.appending(right) } + + public var parent: FilePath { self.removingLastComponent() } } diff --git a/Sources/SwiftlyCore/ModeledCommandLine.swift b/Sources/SwiftlyCore/ModeledCommandLine.swift index a7a21a9b..cb07f372 100644 --- a/Sources/SwiftlyCore/ModeledCommandLine.swift +++ b/Sources/SwiftlyCore/ModeledCommandLine.swift @@ -126,7 +126,7 @@ public protocol Runnable { } extension Runnable { - public func run(_ p: Platform, quiet: Bool) async throws { + public func run(_ p: Platform, quiet: Bool = false) async throws { let c = self.config() let executable = switch c.executable.storage { case let .executable(name): diff --git a/Tests/SwiftlyTests/CommandLineTests.swift b/Tests/SwiftlyTests/CommandLineTests.swift index 62e2aecf..fb478fad 100644 --- a/Tests/SwiftlyTests/CommandLineTests.swift +++ b/Tests/SwiftlyTests/CommandLineTests.swift @@ -28,4 +28,15 @@ public struct CommandLineTests { #expect(properties.count == 1) // Only one shell for the current user #expect(properties[0].key == "UserShell") // The one property key should be the one that is requested } + + @Test func testLipo() { + var config = sys.lipo(inputFiles: "swiftly1", "swiftly2").create(output: "swiftly-universal").config() + + #expect(config.executable == .name("lipo")) + #expect(config.arguments.storage.map(\.description) == ["swiftly1", "swiftly2", "-create", "-output", "swiftly-universal"]) + + config = sys.lipo(inputFiles: "swiftly").create(output: "swiftly-universal-with-one-arch").config() + #expect(config.executable == .name("lipo")) + #expect(config.arguments.storage.map(\.description) == ["swiftly", "-create", "-output", "swiftly-universal-with-one-arch"]) + } } diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index cf6219b0..b5ead917 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -1,6 +1,7 @@ import ArgumentParser import Foundation import SwiftlyCore +import SystemPackage #if os(macOS) import MacOSPlatform @@ -14,6 +15,9 @@ let currentPlatform = MacOS() let currentPlatform = Linux() #endif +typealias fs = FileSystem +typealias sys = SystemCommand + public struct SwiftPlatform: Codable { public var name: String? public var checksum: String? @@ -392,7 +396,6 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { try await self.checkGitRepoStatus(git) - let lipo = try await self.assertTool("lipo", message: "In order to make a universal binary there needs to be the `lipo` tool that is installed on macOS.") let pkgbuild = try await self.assertTool("pkgbuild", message: "In order to make pkg installers there needs to be the `pkgbuild` tool that is installed on macOS.") let strip = try await self.assertTool("strip", message: "In order to strip binaries there needs to be the `strip` tool that is installed on macOS.") @@ -405,10 +408,14 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { try runProgram(strip, ".build/\(arch)-apple-macosx/release/swiftly") } - let swiftlyBinDir = FileManager.default.currentDirectoryPath + "/.build/release/.swiftly/bin" - try? FileManager.default.createDirectory(atPath: swiftlyBinDir, withIntermediateDirectories: true) + let swiftlyBinDir = fs.cwd / ".build/release/.swiftly/bin" + try? await fs.mkdir(.parents, atPath: swiftlyBinDir) - try runProgram(lipo, ".build/x86_64-apple-macosx/release/swiftly", ".build/arm64-apple-macosx/release/swiftly", "-create", "-o", "\(swiftlyBinDir)/swiftly") + try await sys.lipo( + inputFiles: ".build/x86_64-apple-macosx/release/swiftly", ".build/arm64-apple-macosx/release/swiftly" + ) + .create(output: swiftlyBinDir / "swiftly") + .run(currentPlatform) let swiftlyLicenseDir = FileManager.default.currentDirectoryPath + "/.build/release/.swiftly/license" try? FileManager.default.createDirectory(atPath: swiftlyLicenseDir, withIntermediateDirectories: true) @@ -423,7 +430,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { try runProgram( pkgbuild, "--root", - swiftlyBinDir + "/..", + "\(swiftlyBinDir.parent)", "--install-location", ".swiftly", "--version", @@ -438,7 +445,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { try runProgram( pkgbuild, "--root", - swiftlyBinDir + "/..", + "\(swiftlyBinDir.parent)", "--install-location", ".swiftly", "--version", @@ -479,7 +486,12 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { let testArchive = releaseDir.appendingPathComponent("test-swiftly-macos.tar.gz") - try runProgram(lipo, ".build/x86_64-apple-macosx/debug/test-swiftly", ".build/arm64-apple-macosx/debug/test-swiftly", "-create", "-o", "\(swiftlyBinDir)/swiftly") + try await sys.lipo( + inputFiles: ".build/x86_64-apple-macosx/debug/test-swiftly", ".build/arm64-apple-macosx/debug/test-swiftly" + ) + .create(output: swiftlyBinDir / "swiftly") + .run(currentPlatform) + try runProgram(tar, "--directory=.build/x86_64-apple-macosx/debug", "-czf", testArchive.path, "test-swiftly") print(testArchive.path)