Skip to content
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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ let package = Package(
name: "PackLib",
dependencies: [
"XUtils",
.product(name: "Superutils", package: "xtool-core"),
.product(name: "Yams", package: "Yams"),
.product(name: "XcodeGenKit", package: "XcodeGen", condition: .when(platforms: [.macOS])),
],
Expand Down
152 changes: 123 additions & 29 deletions Sources/PackLib/BuildSettings.swift
Original file line number Diff line number Diff line change
@@ -1,37 +1,73 @@
import Foundation
import Subprocess
import XUtils
import Superutils

public struct BuildSettings: Sendable {
private static let customBinDir =
// this is the same option used by SwiftPM itself for dev builds
ProcessInfo.processInfo.environment["SWIFTPM_CUSTOM_BIN_DIR"].map { FilePath($0) }

private static let envURL = URL(fileURLWithPath: "/usr/bin/env")

public let packagePath: String
public var packagePath: String
public let configuration: BuildConfiguration
public let triple: String
public let sdkOptions: [String]
public let options: [String]
public let buildSystem: BuildSystem
public let customOptions: [String]

public var sdkOptions: [String]
public var sdkEnvironment: [Environment.Key: String?]

private var configOptions: [String] {
return [
"--configuration", configuration.rawValue,
"--build-system", buildSystem.pmName,
"--package-path", packagePath,
]
}

private var resolvedBaseOptions: [String] {
configOptions + sdkOptions + customOptions
}

public init(
configuration: BuildConfiguration,
triple: String,
buildSystem: BuildSystem = .default,
packagePath: String = ".",
options: [String] = []
) async throws {
self.packagePath = packagePath
self.configuration = configuration
self.options = options
self.customOptions = options
self.triple = triple
self.buildSystem = buildSystem

// on macOS we don't explicitly install a Swift SDK but
// SwiftPM vends "implicit" Darwin SDKs as of Swift 6.1,
// i.e. we can pass `--swift-sdk arm64-apple-ios` and it
// just works. See:
// https://github.com/swiftlang/swift-package-manager/pull/6828
self.sdkOptions = ["--swift-sdk", triple]
self.sdkEnvironment = [
// xcrun passes an SDKROOT that messes with our sdk configuration
"SDKROOT": nil,
]

switch buildSystem {
case .swiftPM:
// on macOS we don't explicitly install a Swift SDK but
// SwiftPM vends "implicit" Darwin SDKs as of Swift 6.1,
// i.e. we can pass `--swift-sdk arm64-apple-ios` and it
// just works. See:
// https://github.com/swiftlang/swift-package-manager/pull/6828
self.sdkOptions = ["--swift-sdk", triple]
case .swiftBuild:
self.sdkOptions = ["--triple", triple]
#if !os(macOS)
let darwinSDK = try await DarwinSDK.current()
.orThrow(StringError("No Darwin SDK configured. Please run `xtool setup`."))
self.sdkOptions += [
"--toolset", "\(darwinSDK.bundle.path)/toolset-swb.json",
]
self.sdkEnvironment.merge([
"XCODE_EXTRA_PLATFORM_FOLDERS": "\(darwinSDK.bundle.path)/Developer/Platforms",
]) { $1 }
#endif
}
}

#if os(macOS)
Expand All @@ -44,15 +80,40 @@ public struct BuildSettings: Sendable {
return result.standardOutput?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
}

private static let swiftURL = Task {
private static let _swiftURL = Task {
try await FilePath(xcrun(["-f", "swift"]))
}

public static func swiftURL() async throws -> FilePath {
try await _swiftURL.value
}

private static let _swiftcURL = Task {
try await FilePath(xcrun(["-f", "swiftc"]))
}

public static func swiftcURL() async throws -> FilePath {
try await _swiftcURL.value
}
#else
public static func swiftURL() async throws -> FilePath {
try await FilePath(ToolRegistry.locate("swift")).orThrow(StringError("Got bad path for swift executable"))
}

public static func swiftcURL() async throws -> FilePath {
try await FilePath(ToolRegistry.locate("swiftc")).orThrow(StringError("Got bad path for swiftc executable"))
}
#endif

public func withPackagePath(_ path: String) -> Self {
var copy = self
copy.packagePath = path
return copy
}

public func swiftPMInvocation(
forTool tool: String,
arguments: [String],
packagePathOverride: String? = nil
) async throws -> Subprocess.Configuration {
let executable: Executable
let baseArguments: [String]
Expand All @@ -65,35 +126,68 @@ public struct BuildSettings: Sendable {
// to add SDKROOT=.../MacOSX.sdk to our invocations. We avoid this by
// 1) invoking the real swift executable (located with `xcrun -f`) and
// 2) explicitly removing SDKROOT from the env, as it may be inherited
// through the `swift run pack` invocation.
executable = .path(try await Self.swiftURL.value)
// through a parent process (e.g. `swift run xtool`).
executable = .path(try await Self.swiftURL())
#else
executable = .name("swift")
#endif
baseArguments = [tool]
}

let extraArguments: [String] = [
"--package-path", packagePathOverride ?? packagePath,
"--configuration", configuration.rawValue,
]

var rawEnv = ProcessInfo.processInfo.environment
rawEnv.removeValue(forKey: "SDKROOT")
let env = Dictionary(uniqueKeysWithValues: rawEnv.map {
(Environment.Key(rawValue: $0)!, $1)
})

return Configuration(
executable,
arguments: .init(baseArguments + extraArguments + sdkOptions + options + arguments),
environment: .custom(env),
arguments: .init(baseArguments + resolvedBaseOptions + arguments),
environment: .inherit.updating(sdkEnvironment),
platformOptions: .withGracefulShutDown,
)
}

private var baseBuildServerArguments: [String] {
return [
"experimental-build-server",
"--disable-automatic-resolution",
// this requires Swift 6.4 but we need 6.4 on Linux anyway, for the platform toolset fixes
"--experimental-skip-acquiring-lock",
]
}

public var buildServerArguments: [String] {
["package"] + baseBuildServerArguments + resolvedBaseOptions
}

public func buildServerInvocation() async throws -> Subprocess.Configuration {
try await swiftPMInvocation(forTool: "package", arguments: baseBuildServerArguments)
}
}

public enum BuildConfiguration: String, CaseIterable, Sendable {
case debug
case release

var swiftBuildValue: String {
switch self {
case .debug: "Debug"
case .release: "Release"
}
}
}

public enum BuildSystem: Sendable {
case swiftPM
case swiftBuild

public static var `default`: Self {
#if os(macOS)
return .swiftBuild
#else
return .swiftBuild
#endif
}

var pmName: String {
switch self {
case .swiftPM: "native"
case .swiftBuild: "swiftbuild"
}
}
}
39 changes: 24 additions & 15 deletions Sources/PackLib/Packer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,21 @@ public struct Packer: Sendable {
try Data().write(to: sources.appendingPathComponent("stub.c", isDirectory: false))
}

let buildConfig = try await buildSettings.swiftPMInvocation(
forTool: "build",
arguments: [
"--package-path", packageDir.path,
"--scratch-path", ".build",
// resolving can cause SwiftPM to overwrite the root package deps
// with just the deps needed for the builder package (which is to
// say, any "dev dependencies" of the root package may be removed.)
// fortunately we've already resolved the root package by this point
// in order to dump the plan, so we can skip resolution here to skirt
// the issue.
"--disable-automatic-resolution",
]
)
let buildConfig = try await buildSettings
.withPackagePath(packageDir.path)
.swiftPMInvocation(
forTool: "build",
arguments: [
"--scratch-path", ".build",
// resolving can cause SwiftPM to overwrite the root package deps
// with just the deps needed for the builder package (which is to
// say, any "dev dependencies" of the root package may be removed.)
// fortunately we've already resolved the root package by this point
// in order to dump the plan, so we can skip resolution here to skirt
// the issue.
"--disable-automatic-resolution",
],
)
try await Subprocess.run(
buildConfig,
output: .currentStandardError,
Expand All @@ -84,8 +85,16 @@ public struct Packer: Sendable {

let outputURL = output.url

let binPath: String
switch buildSettings.buildSystem {
case .swiftPM:
binPath = "\(buildSettings.triple)/\(buildSettings.configuration.rawValue)"
case .swiftBuild:
let platformName = buildSettings.triple.contains("simulator") ? "iphonesimulator" : "iphoneos"
binPath = "out/Products/\(buildSettings.configuration.swiftBuildValue)-\(platformName)"
}
let binDir = URL(
fileURLWithPath: ".build/\(buildSettings.triple)/\(buildSettings.configuration.rawValue)",
fileURLWithPath: ".build/\(binPath)",
isDirectory: true
)

Expand Down
3 changes: 1 addition & 2 deletions Sources/PackLib/Planner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,9 @@ public struct Planner: Sendable {
}

private func _dumpAction(arguments: [String], path: String) async throws -> Data {
let dumpConfig = try await buildSettings.swiftPMInvocation(
let dumpConfig = try await buildSettings.withPackagePath(path).swiftPMInvocation(
forTool: "package",
arguments: arguments,
packagePathOverride: path
)
return try await Subprocess.run(
dumpConfig,
Expand Down
29 changes: 29 additions & 0 deletions Sources/XToolSupport/DevBSPCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ArgumentParser
import Foundation
import XKit
import PackLib
import Subprocess

struct DevBSPCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "build-server",
abstract: "Run build server",
)

@Option
var triple: String?

func run() async throws {
let settings = try await BuildSettings(
configuration: .debug,
triple: triple ?? PackOperation.defaultTriple
)
try await Subprocess.run(
settings.buildServerInvocation(),
input: .standardInput,
output: .currentStandardOutput,
error: .currentStandardError,
)
.checkSuccess()
}
}
1 change: 1 addition & 0 deletions Sources/XToolSupport/DevCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ struct DevCommand: AsyncParsableCommand {
DevXcodeCommand.self,
DevBuildCommand.self,
DevRunCommand.self,
DevBSPCommand.self,
],
defaultSubcommand: DevRunCommand.self
)
Expand Down
Loading
Loading