Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow cross-bundling #60

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,6 @@ enum Platform: String, CaseIterable {
case .tvOSSimulator: self = .tvOSSimulator
}
}

/// The platform that Swift Bundler is currently being run on.
static var host: Platform {
#if os(macOS)
return .macOS
#elseif os(Linux)
return .linux
#endif
}
}

extension Platform: Equatable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,46 +236,53 @@ enum SwiftPackageManager {
let platformArguments: [String]
switch buildContext.platform {
case .iOS, .visionOS, .tvOS, .iOSSimulator, .visionOSSimulator, .tvOSSimulator:
let sdkPath: String
switch getLatestSDKPath(for: buildContext.platform) {
case .success(let path):
sdkPath = path
case .failure(let error):
return .failure(error)
}
#if os(macOS)
let sdkPath: String
switch getLatestSDKPath(for: buildContext.platform) {
case .success(let path):
sdkPath = path
case .failure(let error):
return .failure(error)
}

guard let platformVersion = buildContext.platformVersion else {
return .failure(.missingDarwinPlatformVersion(buildContext.platform))
}
let hostArchitecture = BuildArchitecture.current

let targetTriple: LLVMTargetTriple
switch buildContext.platform {
case .iOS:
targetTriple = .apple(.arm64, .iOS(platformVersion))
case .visionOS:
targetTriple = .apple(.arm64, .visionOS(platformVersion))
case .tvOS:
targetTriple = .apple(.arm64, .tvOS(platformVersion))
case .iOSSimulator:
targetTriple = .apple(hostArchitecture, .iOS(platformVersion), .simulator)
case .visionOSSimulator:
targetTriple = .apple(hostArchitecture, .visionOS(platformVersion), .simulator)
case .tvOSSimulator:
targetTriple = .apple(hostArchitecture, .tvOS(platformVersion), .simulator)
default:
fatalError("Unreachable (supposedly)")
}
guard let platformVersion = buildContext.platformVersion else {
return .failure(.missingDarwinPlatformVersion(buildContext.platform))
}
let hostArchitecture = BuildArchitecture.current

let targetTriple: LLVMTargetTriple
switch buildContext.platform {
case .iOS:
targetTriple = .apple(.arm64, .iOS(platformVersion))
case .visionOS:
targetTriple = .apple(.arm64, .visionOS(platformVersion))
case .tvOS:
targetTriple = .apple(.arm64, .tvOS(platformVersion))
case .iOSSimulator:
targetTriple = .apple(hostArchitecture, .iOS(platformVersion), .simulator)
case .visionOSSimulator:
targetTriple = .apple(hostArchitecture, .visionOS(platformVersion), .simulator)
case .tvOSSimulator:
targetTriple = .apple(hostArchitecture, .tvOS(platformVersion), .simulator)
default:
fatalError("Unreachable (supposedly)")
}

platformArguments =
[
"-sdk", sdkPath,
"-target", targetTriple.description,
].flatMap { ["-Xswiftc", $0] }
+ [
"--target=\(targetTriple)",
"-isysroot", sdkPath,
].flatMap { ["-Xcc", $0] }
// TODO: Should this be omitted on non-mac hosts?
// adding '-target' requires setting '-sdk', which can be retrieved on mac platforms via `xcrun`, but if using
// cross-compilation how do we get the SDK path?
platformArguments =
[
"-sdk", sdkPath,
"-target", targetTriple.description,
].flatMap { ["-Xswiftc", $0] }
+ [
"--target=\(targetTriple)",
"-isysroot", sdkPath,
].flatMap { ["-Xcc", $0] }
#else
platformArguments = []
#endif
case .macOS, .linux:
platformArguments = []
}
Expand All @@ -300,22 +307,26 @@ enum SwiftPackageManager {
return .success(arguments)
}

/// Gets the path to the latest SDK for a given platform.
/// - Parameter platform: The platform to get the SDK path for.
/// - Returns: The SDK's path, or a failure if an error occurs.
static func getLatestSDKPath(for platform: Platform) -> Result<String, SwiftPackageManagerError> {
return Process.create(
"/usr/bin/xcrun",
arguments: [
"--sdk", platform.sdkName,
"--show-sdk-path",
]
).getOutput().map { output in
return output.trimmingCharacters(in: .whitespacesAndNewlines)
}.mapError { error in
return .failedToGetLatestSDKPath(platform, error)
/// xcrun is only available on macOS.
#if os(macOS)
/// Gets the path to the latest SDK for a given platform.
/// - Parameter platform: The platform to get the SDK path for.
/// - Returns: The SDK's path, or a failure if an error occurs.
static func getLatestSDKPath(for platform: Platform) -> Result<String, SwiftPackageManagerError>
{
return Process.create(
"/usr/bin/xcrun",
arguments: [
"--sdk", platform.sdkName,
"--show-sdk-path",
]
).getOutput().map { output in
return output.trimmingCharacters(in: .whitespacesAndNewlines)
}.mapError { error in
return .failedToGetLatestSDKPath(platform, error)
}
}
}
#endif

/// Gets the version of the current Swift installation.
/// - Returns: The swift version, or a failure if an error occurs.
Expand Down
32 changes: 18 additions & 14 deletions Sources/swift-bundler/Commands/BundleArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ struct BundleArguments: ParsableArguments {
var appName: String?

@Option(
help: "The bundler to use \(BundlerChoice.possibleValuesDescription).",
help: """
The bundler to use \(BundlerChoice.possibleValuesDescription). \
(default: \(BundlerChoice.defaultForHostPlatform))
""",
transform: {
guard let choice = BundlerChoice(rawValue: $0) else {
throw CLIError.invalidBundlerChoice($0)
Expand Down Expand Up @@ -70,11 +73,10 @@ struct BundleArguments: ParsableArguments {
@Option(
name: [.customShort("a"), .customLong("arch")],
parsing: .singleValue,
help: {
let possibleValues = BuildArchitecture.possibleValuesDescription
let defaultValue = BuildArchitecture.current.rawValue
return "The architectures to build for \(possibleValues). (default: [\(defaultValue)])"
}(),
help: """
The architectures to build for \(BuildArchitecture.possibleValuesDescription). \
(default: [\(BuildArchitecture.current.rawValue)])
""",
transform: { string in
guard let arch = BuildArchitecture.init(rawValue: string) else {
throw CLIError.invalidArchitecture(string)
Expand All @@ -100,10 +102,10 @@ struct BundleArguments: ParsableArguments {
/// The platform to build for.
@Option(
name: .shortAndLong,
help: {
let possibleValues = Platform.possibleValuesDescription
return "The platform to build for \(possibleValues). (default: macOS)"
}(),
help: """
The platform to build for \(Platform.possibleValuesDescription). \
(default: \(SwiftBundler.host.defaultForHostPlatform))
""",
transform: { string in
// also support getting a platform by its apple sdk equivalent.
if let appleSDK = AppleSDKPlatform(rawValue: string) {
Expand All @@ -115,12 +117,14 @@ struct BundleArguments: ParsableArguments {
}
return platform
})
var platform = Platform.host
var platform = SwiftBundler.host.defaultForHostPlatform

/// A codesigning identity to use.
@Option(
name: .customLong("identity"),
help: "The identity to use for codesigning")
#if os(macOS)
@Option(
name: .customLong("identity"),
help: "The identity to use for codesigning")
#endif
var identity: String?

/// A provisioning profile to use.
Expand Down
41 changes: 18 additions & 23 deletions Sources/swift-bundler/Commands/BundleCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,28 @@ struct BundleCommand: AsyncCommand {
}
#endif

if Platform.host == .linux && platform != .linux {
log.error("'--platform \(platform)' is not supported on Linux")
return false
}

guard arguments.bundler.isSupportedOnHostPlatform else {
log.error(
"""
The '\(arguments.bundler.rawValue)' bundler is not supported on the \
current host platform. Supported values: \
\(BundlerChoice.supportedHostValuesDescription)
"""
)
return false
}
// TODO: Should this be removed now that cross-compilation works?
// guard arguments.bundler.isSupportedOnHostPlatform else {
// log.error(
// """
// The '\(arguments.bundler.rawValue)' bundler is not supported on the \
// current host platform. Supported values: \
// \(BundlerChoice.supportedHostValuesDescription)
// """
// )
// return false
// }

guard arguments.bundler.supportedTargetPlatforms.contains(platform) else {
let alternatives = BundlerChoice.allCases.filter { choice in
choice.supportedTargetPlatforms.contains(platform)
}
let alternativesDescription = "(\(alternatives.map(\.rawValue).joined(separator: "|")))"
let alternativesDescription =
"(\(BundlerChoice.allCases.map(\.rawValue).joined(separator: "|")))"
let bundleTargetsDescription =
"(\(arguments.bundler.supportedTargetPlatforms.map(\.rawValue).joined(separator: "|")))"
log.error(
"""
The '\(arguments.bundler.rawValue)' bundler doesn't support bundling \
for '\(platform)'. Supported target platforms: \
\(BundlerChoice.supportedHostValuesDescription). Valid alternative \
bundlers: \(alternativesDescription)
The '\(arguments.bundler.rawValue)' bundler doesn't support bundling for '\(platform)'.
* Supported platforms for bundler '\(arguments.bundler.rawValue)': \(bundleTargetsDescription).
* Alternative bundlers: \(alternativesDescription)
"""
)
return false
Expand Down
34 changes: 16 additions & 18 deletions Sources/swift-bundler/Commands/BundlerChoice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,27 @@ enum BundlerChoice: String, CaseIterable {
}

/// Whether the choice is supported on the host platform.
var isSupportedOnHostPlatform: Bool {
supportedHostPlatforms.contains(Platform.host)
}
// var isSupportedOnHostPlatform: Bool {
// supportedHostPlatforms.contains(SwiftBundler.host.defaultForHostPlatform)
// }

/// The default choice for the host platform.
static var defaultForHostPlatform: Self {
// TODO: Give Platform.host its own type so that we don't have to pretend
// that iOS is a valid host platform (for now...)
switch Platform.host {
case .macOS, .iOS, .iOSSimulator, .tvOS, .tvOSSimulator, .visionOS, .visionOSSimulator:
switch SwiftBundler.host {
case .macOS:
return .darwinApp
case .linux:
return .linuxGeneric
}
}

/// A list of supported values for human consumption.
static var supportedHostValuesDescription: String {
let supportedChoices = allCases.filter { choice in
choice.isSupportedOnHostPlatform
}
return "(\(supportedChoices.map(\.rawValue).joined(separator: "|")))"
}
// static var supportedHostValuesDescription: String {
// let supportedChoices = allCases.filter { choice in
// choice.isSupportedOnHostPlatform
// }
// return "(\(supportedChoices.map(\.rawValue).joined(separator: "|")))"
// }

/// Target platforms that the choice is valid for.
var supportedTargetPlatforms: [Platform] {
Expand All @@ -55,9 +53,9 @@ enum BundlerChoice: String, CaseIterable {
}

/// Host platforms that the choice is valid for.
var supportedHostPlatforms: [Platform] {
// Nice and simple one-to-one for now. With SwiftPM cross-compilation advancing
// I'm sure I'll eventually get cross-bundling working.
supportedTargetPlatforms
}
// var supportedHostPlatforms: [Platform] {
// // Nice and simple one-to-one for now. With SwiftPM cross-compilation advancing
// // I'm sure I'll eventually get cross-bundling working.
// supportedTargetPlatforms
// }
}
28 changes: 27 additions & 1 deletion Sources/swift-bundler/SwiftBundler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

static let configuration = CommandConfiguration(
commandName: "swift-bundler",
abstract: "A tool for creating macOS apps from Swift packages.",
abstract: "A tool for creating apps from Swift packages.",
version: "v" + version.description,
shouldDisplay: true,
subcommands: [
Expand All @@ -20,8 +20,16 @@
SimulatorsCommand.self,
TemplatesCommand.self,
GenerateXcodeSupportCommand.self,
ListIdentitiesCommand.self,

Check warning on line 23 in Sources/swift-bundler/SwiftBundler.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
]
.filter {
if Self.host != .macOS {
// Other non-macOS systems do not support running simulators and identities
$0 != SimulatorsCommand.self && $0 != ListIdentitiesCommand.self
} else {
true
}
}
)

@Flag(
Expand All @@ -35,4 +43,22 @@
log.logLevel = .debug
}
}

#if os(Linux)
static let host = Host.linux
#elseif os(macOS)
static let host = Host.macOS
#endif

enum Host: Hashable, Codable {
case macOS
case linux

var defaultForHostPlatform: Platform {
switch self {
case .macOS: .macOS
case .linux: .linux
}
}
}
}
Loading