diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index 0504e6c63d2..07d6b3b138f 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -350,10 +350,10 @@ public struct SwiftRunCommand: AsyncSwiftCommand { #else let number_fds = getdtablesize() #endif /* os(Android) */ - - // 2. close all file descriptors. + + // 2. set to close all file descriptors on exec for i in 3.. AbsolutePath? { @@ -1202,3 +1221,4 @@ extension Basics.Diagnostic { .error(arguments.map { "'\($0)'" }.spm_localizedJoin(type: .conjunction) + " are mutually exclusive") } } + diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index b6fccafdcc0..e4650254a14 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -122,8 +122,7 @@ public struct BuildParameters: Encodable { } } - /// Whether the Xcode build system is used. - public var isXcodeBuildSystemEnabled: Bool + public var buildSystemKind: BuildSystemProvider.Kind public var shouldSkipBuilding: Bool @@ -152,13 +151,13 @@ public struct BuildParameters: Encodable { toolchain: Toolchain, triple: Triple? = nil, flags: BuildFlags, + buildSystemKind: BuildSystemProvider.Kind = .native, pkgConfigDirectories: [AbsolutePath] = [], architectures: [String]? = nil, workers: UInt32 = UInt32(ProcessInfo.processInfo.activeProcessorCount), shouldCreateDylibForDynamicProducts: Bool = true, sanitizers: EnabledSanitizers = EnabledSanitizers(), indexStoreMode: IndexStoreMode = .auto, - isXcodeBuildSystemEnabled: Bool = false, shouldSkipBuilding: Bool = false, prepareForIndexing: PrepareForIndexingMode = .off, debuggingParameters: Debugging? = nil, @@ -179,6 +178,7 @@ public struct BuildParameters: Encodable { self.configuration = configuration self._toolchain = _Toolchain(toolchain: toolchain) self.triple = triple + self.buildSystemKind = buildSystemKind switch self.debuggingParameters.debugInfoFormat { case .dwarf: var flags = flags @@ -214,7 +214,6 @@ public struct BuildParameters: Encodable { self.shouldCreateDylibForDynamicProducts = shouldCreateDylibForDynamicProducts self.sanitizers = sanitizers self.indexStoreMode = indexStoreMode - self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled self.shouldSkipBuilding = shouldSkipBuilding self.prepareForIndexing = prepareForIndexing self.driverParameters = driverParameters @@ -225,9 +224,17 @@ public struct BuildParameters: Encodable { /// The path to the build directory (inside the data directory). public var buildPath: AbsolutePath { - if isXcodeBuildSystemEnabled { - return dataPath.appending(components: "Products", configuration.dirname.capitalized) - } else { + // TODO: query the build system for this. + switch buildSystemKind { + case .xcode, .swiftbuild: + var configDir: String = configuration.dirname.capitalized + if self.triple.isWindows() { + configDir += "-windows" + } else if self.triple.isLinux() { + configDir += "-linux" + } + return dataPath.appending(components: "Products", configDir) + case .native: return dataPath.appending(component: configuration.dirname) } } diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index c4a79954872..c874ef1a9d5 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -128,7 +128,7 @@ public protocol BuildSystemFactory { public struct BuildSystemProvider { // TODO: In the future, we may want this to be about specific capabilities of a build system rather than choosing a concrete one. - public enum Kind: String, CaseIterable { + public enum Kind: String, Codable, CaseIterable { case native case swiftbuild case xcode @@ -169,15 +169,6 @@ public struct BuildSystemProvider { } } -extension BuildSystemProvider.Kind { - public var usesXcodeBuildEngine: Bool { - switch self { - case .native: return false - case .swiftbuild: return false - case .xcode: return true - } - } -} private enum Errors: Swift.Error { case buildSystemProviderNotRegistered(kind: BuildSystemProvider.Kind) } diff --git a/Sources/SPMBuildCore/Triple+Extensions.swift b/Sources/SPMBuildCore/Triple+Extensions.swift index b012fac5eb4..191419bb5dd 100644 --- a/Sources/SPMBuildCore/Triple+Extensions.swift +++ b/Sources/SPMBuildCore/Triple+Extensions.swift @@ -26,9 +26,14 @@ extension Triple { extension Triple { public func platformBuildPathComponent(buildSystem: BuildSystemProvider.Kind) -> String { - // Use "apple" as the subdirectory because in theory Xcode build system - // can be used to build for any Apple platform and it has its own - // conventions for build subpaths based on platforms. - buildSystem.usesXcodeBuildEngine ? "apple" : self.platformBuildPathComponent + switch buildSystem { + case .xcode: + // Use "apple" as the subdirectory because in theory Xcode build system + // can be used to build for any Apple platform and it has its own + // conventions for build subpaths based on platforms. + return "apple" + case .swiftbuild, .native: + return self.platformBuildPathComponent + } } } diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 4613a77b728..a5e5f58a8a4 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -43,6 +43,22 @@ struct SessionFailedError: Error { var diagnostics: [SwiftBuild.SwiftBuildMessage.DiagnosticInfo] } +func withService( + connectionMode: SWBBuildServiceConnectionMode = .default, + variant: SWBBuildServiceVariant = .default, + serviceBundleURL: URL? = nil, + body: @escaping (_ service: SWBBuildService) async throws -> Void +) async throws { + let service = try await SWBBuildService(connectionMode: connectionMode, variant: variant, serviceBundleURL: serviceBundleURL) + do { + try await body(service) + } catch { + await service.close() + throw error + } + await service.close() +} + func withSession( service: SWBBuildService, name: String, @@ -234,160 +250,161 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { #if canImport(SwiftBuild) private func startSWBuildOperation(pifTargetName: String) async throws { - let service = try await SWBBuildService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint)) - let parameters = try makeBuildParameters() - let derivedDataPath = buildParameters.dataPath.pathString - - let progressAnimation = ProgressAnimation.percent( - stream: self.outputStream, - verbose: self.logLevel.isVerbose, - header: "" - ) + try await withService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint)) { service in + let parameters = try self.makeBuildParameters() + let derivedDataPath = self.buildParameters.dataPath.pathString + + let progressAnimation = ProgressAnimation.percent( + stream: self.outputStream, + verbose: self.logLevel.isVerbose, + header: "" + ) - do { - try await withSession(service: service, name: buildParameters.pifManifest.pathString, packageManagerResourcesDirectory: packageManagerResourcesDirectory) { session, _ in - // Load the workspace, and set the system information to the default - do { - try await session.loadWorkspace(containerPath: self.buildParameters.pifManifest.pathString) - try await session.setSystemInfo(.default()) - } catch { - self.observabilityScope.emit(error: error.localizedDescription) - throw error - } + do { + try await withSession(service: service, name: self.buildParameters.pifManifest.pathString, packageManagerResourcesDirectory: self.packageManagerResourcesDirectory) { session, _ in + // Load the workspace, and set the system information to the default + do { + try await session.loadWorkspace(containerPath: self.buildParameters.pifManifest.pathString) + try await session.setSystemInfo(.default()) + } catch { + self.observabilityScope.emit(error: error.localizedDescription) + throw error + } - // Find the targets to build. - let configuredTargets: [SWBConfiguredTarget] - do { - let workspaceInfo = try await session.workspaceInfo() - - configuredTargets = try [pifTargetName].map { targetName in - let infos = workspaceInfo.targetInfos.filter { $0.targetName == targetName } - switch infos.count { - case 0: - self.observabilityScope.emit(error: "Could not find target named '\(targetName)'") - throw Diagnostics.fatalError - case 1: - return SWBConfiguredTarget(guid: infos[0].guid, parameters: parameters) - default: - self.observabilityScope.emit(error: "Found multiple targets named '\(targetName)'") - throw Diagnostics.fatalError + // Find the targets to build. + let configuredTargets: [SWBConfiguredTarget] + do { + let workspaceInfo = try await session.workspaceInfo() + + configuredTargets = try [pifTargetName].map { targetName in + let infos = workspaceInfo.targetInfos.filter { $0.targetName == targetName } + switch infos.count { + case 0: + self.observabilityScope.emit(error: "Could not find target named '\(targetName)'") + throw Diagnostics.fatalError + case 1: + return SWBConfiguredTarget(guid: infos[0].guid, parameters: parameters) + default: + self.observabilityScope.emit(error: "Found multiple targets named '\(targetName)'") + throw Diagnostics.fatalError + } } + } catch { + self.observabilityScope.emit(error: error.localizedDescription) + throw error } - } catch { - self.observabilityScope.emit(error: error.localizedDescription) - throw error - } - var request = SWBBuildRequest() - request.parameters = parameters - request.configuredTargets = configuredTargets - request.useParallelTargets = true - request.useImplicitDependencies = false - request.useDryRun = false - request.hideShellScriptEnvironment = true - request.showNonLoggedProgress = true - - // Override the arena. We need to apply the arena info to both the request-global build - // parameters as well as the target-specific build parameters, since they may have been - // deserialized from the build request file above overwriting the build parameters we set - // up earlier in this method. - - #if os(Windows) - let ddPathPrefix = derivedDataPath.replacingOccurrences(of: "\\", with: "/") - #else - let ddPathPrefix = derivedDataPath - #endif - - let arenaInfo = SWBArenaInfo( - derivedDataPath: ddPathPrefix, - buildProductsPath: ddPathPrefix + "/Products", - buildIntermediatesPath: ddPathPrefix + "/Intermediates.noindex", - pchPath: ddPathPrefix + "/PCH", - indexRegularBuildProductsPath: nil, - indexRegularBuildIntermediatesPath: nil, - indexPCHPath: ddPathPrefix, - indexDataStoreFolderPath: ddPathPrefix, - indexEnableDataStore: request.parameters.arenaInfo?.indexEnableDataStore ?? false - ) - - request.parameters.arenaInfo = arenaInfo - request.configuredTargets = request.configuredTargets.map { configuredTarget in - var configuredTarget = configuredTarget - configuredTarget.parameters?.arenaInfo = arenaInfo - return configuredTarget - } + var request = SWBBuildRequest() + request.parameters = parameters + request.configuredTargets = configuredTargets + request.useParallelTargets = true + request.useImplicitDependencies = false + request.useDryRun = false + request.hideShellScriptEnvironment = true + request.showNonLoggedProgress = true + + // Override the arena. We need to apply the arena info to both the request-global build + // parameters as well as the target-specific build parameters, since they may have been + // deserialized from the build request file above overwriting the build parameters we set + // up earlier in this method. + + #if os(Windows) + let ddPathPrefix = derivedDataPath.replacingOccurrences(of: "\\", with: "/") + #else + let ddPathPrefix = derivedDataPath + #endif + + let arenaInfo = SWBArenaInfo( + derivedDataPath: ddPathPrefix, + buildProductsPath: ddPathPrefix + "/Products", + buildIntermediatesPath: ddPathPrefix + "/Intermediates.noindex", + pchPath: ddPathPrefix + "/PCH", + indexRegularBuildProductsPath: nil, + indexRegularBuildIntermediatesPath: nil, + indexPCHPath: ddPathPrefix, + indexDataStoreFolderPath: ddPathPrefix, + indexEnableDataStore: request.parameters.arenaInfo?.indexEnableDataStore ?? false + ) + + request.parameters.arenaInfo = arenaInfo + request.configuredTargets = request.configuredTargets.map { configuredTarget in + var configuredTarget = configuredTarget + configuredTarget.parameters?.arenaInfo = arenaInfo + return configuredTarget + } - func emitEvent(_ message: SwiftBuild.SwiftBuildMessage) throws { - switch message { - case .buildCompleted: - progressAnimation.complete(success: true) - case .didUpdateProgress(let progressInfo): - var step = Int(progressInfo.percentComplete) - if step < 0 { step = 0 } - let message = if let targetName = progressInfo.targetName { - "\(targetName) \(progressInfo.message)" - } else { - "\(progressInfo.message)" - } - progressAnimation.update(step: step, total: 100, text: message) - case .diagnostic(let info): - if info.kind == .error { - self.observabilityScope.emit(error: "\(info.location) \(info.message) \(info.fixIts)") - } else if info.kind == .warning { - self.observabilityScope.emit(warning: "\(info.location) \(info.message) \(info.fixIts)") - } else if info.kind == .note { - self.observabilityScope.emit(info: "\(info.location) \(info.message) \(info.fixIts)") - } else if info.kind == .remark { - self.observabilityScope.emit(debug: "\(info.location) \(info.message) \(info.fixIts)") - } - case .taskOutput(let info): - self.observabilityScope.emit(info: "\(info.data)") - case .taskStarted(let info): - if let commandLineDisplay = info.commandLineDisplayString { - self.observabilityScope.emit(info: "\(info.executionDescription)\n\(commandLineDisplay)") - } else { - self.observabilityScope.emit(info: "\(info.executionDescription)") + func emitEvent(_ message: SwiftBuild.SwiftBuildMessage) throws { + switch message { + case .buildCompleted: + progressAnimation.complete(success: true) + case .didUpdateProgress(let progressInfo): + var step = Int(progressInfo.percentComplete) + if step < 0 { step = 0 } + let message = if let targetName = progressInfo.targetName { + "\(targetName) \(progressInfo.message)" + } else { + "\(progressInfo.message)" + } + progressAnimation.update(step: step, total: 100, text: message) + case .diagnostic(let info): + if info.kind == .error { + self.observabilityScope.emit(error: "\(info.location) \(info.message) \(info.fixIts)") + } else if info.kind == .warning { + self.observabilityScope.emit(warning: "\(info.location) \(info.message) \(info.fixIts)") + } else if info.kind == .note { + self.observabilityScope.emit(info: "\(info.location) \(info.message) \(info.fixIts)") + } else if info.kind == .remark { + self.observabilityScope.emit(debug: "\(info.location) \(info.message) \(info.fixIts)") + } + case .taskOutput(let info): + self.observabilityScope.emit(info: "\(info.data)") + case .taskStarted(let info): + if let commandLineDisplay = info.commandLineDisplayString { + self.observabilityScope.emit(info: "\(info.executionDescription)\n\(commandLineDisplay)") + } else { + self.observabilityScope.emit(info: "\(info.executionDescription)") + } + default: + break } - default: - break } - } - let operation = try await session.createBuildOperation( - request: request, - delegate: PlanningOperationDelegate() - ) + let operation = try await session.createBuildOperation( + request: request, + delegate: PlanningOperationDelegate() + ) - for try await event in try await operation.start() { - try emitEvent(event) - } + for try await event in try await operation.start() { + try emitEvent(event) + } - await operation.waitForCompletion() - - switch operation.state { - case .succeeded: - progressAnimation.update(step: 100, total: 100, text: "") - progressAnimation.complete(success: true) - self.outputStream.send("Build complete!\n") - self.outputStream.flush() - case .failed: - self.observabilityScope.emit(error: "Build failed") - throw Diagnostics.fatalError - case .cancelled: - self.observabilityScope.emit(error: "Build was cancelled") - throw Diagnostics.fatalError - case .requested, .running, .aborted: - self.observabilityScope.emit(error: "Unexpected build state") - throw Diagnostics.fatalError + await operation.waitForCompletion() + + switch operation.state { + case .succeeded: + progressAnimation.update(step: 100, total: 100, text: "") + progressAnimation.complete(success: true) + self.outputStream.send("Build complete!\n") + self.outputStream.flush() + case .failed: + self.observabilityScope.emit(error: "Build failed") + throw Diagnostics.fatalError + case .cancelled: + self.observabilityScope.emit(error: "Build was cancelled") + throw Diagnostics.fatalError + case .requested, .running, .aborted: + self.observabilityScope.emit(error: "Unexpected build state") + throw Diagnostics.fatalError + } } + } catch let sessError as SessionFailedError { + for diagnostic in sessError.diagnostics { + self.observabilityScope.emit(error: diagnostic.message) + } + throw sessError.error + } catch { + throw error } - } catch let sessError as SessionFailedError { - for diagnostic in sessError.diagnostics { - self.observabilityScope.emit(error: diagnostic.message) - } - throw sessError.error - } catch { - throw error } } diff --git a/Sources/_InternalTestSupport/MockBuildTestHelper.swift b/Sources/_InternalTestSupport/MockBuildTestHelper.swift index 97d9514482b..47dc0cb4e1a 100644 --- a/Sources/_InternalTestSupport/MockBuildTestHelper.swift +++ b/Sources/_InternalTestSupport/MockBuildTestHelper.swift @@ -82,6 +82,7 @@ public func mockBuildParameters( config: BuildConfiguration = .debug, toolchain: PackageModel.Toolchain = MockToolchain(), flags: PackageModel.BuildFlags = PackageModel.BuildFlags(), + buildSystemKind: BuildSystemProvider.Kind = .native, shouldLinkStaticSwiftStdlib: Bool = false, shouldDisableLocalRpath: Bool = false, canRenameEntrypointFunctionName: Bool = false, @@ -100,6 +101,7 @@ public func mockBuildParameters( toolchain: toolchain, triple: triple, flags: flags, + buildSystemKind: buildSystemKind, pkgConfigDirectories: [], workers: 3, indexStoreMode: indexStoreMode, diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 533341f7354..963835de4a9 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -282,8 +282,6 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { shouldDisableLocalRpath: Bool, logLevel: Basics.Diagnostic.Severity ) throws -> BuildSystem { - var buildFlags = buildFlags - let dataPath = scratchDirectory.appending( component: self.targetToolchain.targetTriple.platformBuildPathComponent(buildSystem: buildSystem) ) @@ -295,8 +293,8 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { toolchain: self.targetToolchain, triple: self.hostToolchain.targetTriple, flags: buildFlags, + buildSystemKind: buildSystem, architectures: architectures, - isXcodeBuildSystemEnabled: buildSystem.usesXcodeBuildEngine, driverParameters: .init( explicitTargetDependencyImportCheckingMode: explicitTargetDependencyImportCheck == .error ? .error : .none, useIntegratedSwiftDriver: useIntegratedSwiftDriver,