diff --git a/Sources/Build/AsyncBuildOperation.swift b/Sources/Build/AsyncBuildOperation.swift new file mode 100644 index 00000000000..c4d9893754b --- /dev/null +++ b/Sources/Build/AsyncBuildOperation.swift @@ -0,0 +1,836 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import _Concurrency + +@_spi(SwiftPMInternal) +import Basics + +import LLBuildManifest +import PackageGraph +import PackageLoading +import PackageModel + +@_spi(SwiftPMInternal) +import SPMBuildCore + +import SPMLLBuild +import Foundation + +import class TSCBasic.DiagnosticsEngine +import protocol TSCBasic.OutputByteStream +import class TSCBasic.Process +import enum TSCBasic.ProcessEnv +import struct TSCBasic.RegEx + +import enum TSCUtility.Diagnostics + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import DriverSupport +@_implementationOnly import SwiftDriver +#else +import DriverSupport +import SwiftDriver +#endif + +@_spi(SwiftPMInternal) +public final class AsyncBuildOperation: PackageStructureDelegate, SPMBuildCore.AsyncBuildSystem, BuildErrorAdviceProvider { + /// Build parameters for products. + let productsBuildParameters: BuildParameters + + /// Build parameters for build tools: plugins and macros. + let toolsBuildParameters: BuildParameters + + /// The closure for loading the package graph. + let packageGraphLoader: () throws -> ModulesGraph + + /// the plugin configuration for build plugins + let pluginConfiguration: PluginConfiguration? + + /// The llbuild build delegate reference. + private var buildSystemDelegate: AsyncLLBuildDelegate? + + /// The llbuild build system reference. + private var buildSystem: SPMLLBuild.BuildSystem? + + /// If build manifest caching should be enabled. + public let cacheBuildManifest: Bool + + /// The build plan that was computed, if any. + public private(set) var _buildPlan: BuildPlan? + + public var buildPlan: SPMBuildCore.BuildPlan { + get throws { + if let buildPlan = _buildPlan { + return buildPlan + } else { + throw StringError("did not compute a build plan yet") + } + } + } + + /// The build description resulting from planing. + private let buildDescription = ThreadSafeBox() + + /// The loaded package graph. + private let packageGraph = ThreadSafeBox() + + /// The output stream for the build delegate. + private let outputStream: OutputByteStream + + /// The verbosity level to use for diagnostics. + private let logLevel: Basics.Diagnostic.Severity + + /// File system to operate on. + private let fileSystem: Basics.FileSystem + + /// ObservabilityScope with which to emit diagnostics. + private let observabilityScope: ObservabilityScope + + public var builtTestProducts: [BuiltTestProduct] { + (try? getBuildDescription())?.builtTestProducts ?? [] + } + + /// File rules to determine resource handling behavior. + private let additionalFileRules: [FileRuleDescription] + + /// Alternative path to search for pkg-config `.pc` files. + private let pkgConfigDirectories: [AbsolutePath] + + /// Map of dependency package identities by root packages that depend on them. + private let dependenciesByRootPackageIdentity: [PackageIdentity: [PackageIdentity]] + + /// Map of root package identities by target names which are declared in them. + private let rootPackageIdentityByTargetName: [String: PackageIdentity] + + private let eventsContinuation: AsyncStream.Continuation + + public init( + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, + cacheBuildManifest: Bool, + packageGraphLoader: @escaping () throws -> ModulesGraph, + pluginConfiguration: PluginConfiguration? = .none, + additionalFileRules: [FileRuleDescription], + pkgConfigDirectories: [AbsolutePath], + dependenciesByRootPackageIdentity: [PackageIdentity: [PackageIdentity]], + targetsByRootPackageIdentity: [PackageIdentity: [String]], + eventsContinuation: AsyncStream.Continuation, + outputStream: OutputByteStream, + logLevel: Basics.Diagnostic.Severity, + fileSystem: Basics.FileSystem, + observabilityScope: ObservabilityScope + ) { + /// Checks if stdout stream is tty. + var productsBuildParameters = productsBuildParameters + productsBuildParameters.outputParameters.isColorized = outputStream.isTTY + + var toolsBuildParameters = toolsBuildParameters + toolsBuildParameters.outputParameters.isColorized = outputStream.isTTY + + self.productsBuildParameters = productsBuildParameters + self.toolsBuildParameters = toolsBuildParameters + self.cacheBuildManifest = cacheBuildManifest + self.packageGraphLoader = packageGraphLoader + self.additionalFileRules = additionalFileRules + self.pluginConfiguration = pluginConfiguration + self.pkgConfigDirectories = pkgConfigDirectories + self.dependenciesByRootPackageIdentity = dependenciesByRootPackageIdentity + self.rootPackageIdentityByTargetName = (try? Dictionary( + throwingUniqueKeysWithValues: targetsByRootPackageIdentity.lazy.flatMap { e in e.value.map { ($0, e.key) } }) + ) ?? [:] + self.eventsContinuation = eventsContinuation + self.outputStream = outputStream + self.logLevel = logLevel + self.fileSystem = fileSystem + self.observabilityScope = observabilityScope.makeChildScope(description: "Build Operation") + } + + public var modulesGraph: ModulesGraph { + get throws { + try self.packageGraph.memoize { + try self.packageGraphLoader() + } + } + } + + /// Compute and return the latest build description. + /// + /// This will try skip build planning if build manifest caching is enabled + /// and the package structure hasn't changed. + public func getBuildDescription(subset: BuildSubset? = nil) throws -> BuildDescription { + return try self.buildDescription.memoize { + if self.cacheBuildManifest { + do { + // if buildPackageStructure returns a valid description we use that, otherwise we perform full planning + if try self.buildPackageStructure() { + // confirm the step above created the build description as expected + // we trust it to update the build description when needed + let buildDescriptionPath = self.productsBuildParameters.buildDescriptionPath + guard self.fileSystem.exists(buildDescriptionPath) else { + throw InternalError("could not find build descriptor at \(buildDescriptionPath)") + } + // return the build description that's on disk. + return try BuildDescription.load(fileSystem: self.fileSystem, path: buildDescriptionPath) + } + } catch { + // since caching is an optimization, warn about failing to load the cached version + self.observabilityScope.emit( + warning: "failed to load the cached build description", + underlyingError: error + ) + } + } + // We need to perform actual planning if we reach here. + return try self.plan(subset: subset).description + } + } + + public func getBuildManifest() throws -> LLBuildManifest { + return try self.plan().manifest + } + + /// Cancel the active build operation. + public func cancel(deadline: DispatchTime) throws { + buildSystem?.cancel() + } + + // Emit a warning if a target imports another target in this build + // without specifying it as a dependency in the manifest + private func verifyTargetImports(in description: BuildDescription) throws { + let checkingMode = description.explicitTargetDependencyImportCheckingMode + guard checkingMode != .none else { + return + } + // Ensure the compiler supports the import-scan operation + guard DriverSupport.checkSupportedFrontendFlags( + flags: ["import-prescan"], + toolchain: self.productsBuildParameters.toolchain, + fileSystem: localFileSystem + ) else { + return + } + + for (target, commandLine) in description.swiftTargetScanArgs { + do { + guard let dependencies = description.targetDependencyMap[target] else { + // Skip target if no dependency information is present + continue + } + let targetDependenciesSet = Set(dependencies) + guard !description.generatedSourceTargetSet.contains(target), + targetDependenciesSet.intersection(description.generatedSourceTargetSet).isEmpty else { + // Skip targets which contain, or depend-on-targets, with generated source-code. + // Such as test discovery targets and targets with plugins. + continue + } + let resolver = try ArgsResolver(fileSystem: localFileSystem) + let executor = SPMSwiftDriverExecutor(resolver: resolver, + fileSystem: localFileSystem, + env: ProcessEnv.vars) + + let consumeDiagnostics: DiagnosticsEngine = DiagnosticsEngine(handlers: []) + var driver = try Driver(args: commandLine, + diagnosticsOutput: .engine(consumeDiagnostics), + fileSystem: localFileSystem, + executor: executor) + guard !consumeDiagnostics.hasErrors else { + // If we could not init the driver with this command, something went wrong, + // proceed without checking this target. + continue + } + let imports = try driver.performImportPrescan().imports + let nonDependencyTargetsSet = + Set(description.targetDependencyMap.keys.filter { !targetDependenciesSet.contains($0) }) + let importedTargetsMissingDependency = Set(imports).intersection(nonDependencyTargetsSet) + if let missedDependency = importedTargetsMissingDependency.first { + switch checkingMode { + case .error: + self.observabilityScope.emit(error: "Target \(target) imports another target (\(missedDependency)) in the package without declaring it a dependency.") + case .warn: + self.observabilityScope.emit(warning: "Target \(target) imports another target (\(missedDependency)) in the package without declaring it a dependency.") + case .none: + fatalError("Explicit import checking is disabled.") + } + } + } catch { + // The above verification is a best-effort attempt to warn the user about a potential manifest + // error. If something went wrong during the import-prescan, proceed silently. + return + } + } + } + + private static var didEmitUnexpressedDependencies = false + + private func detectUnexpressedDependencies() { + return self.detectUnexpressedDependencies( + // Note: once we switch from the toolchain global metadata, we will have to ensure we can match the right metadata used during the build. + availableLibraries: self.productsBuildParameters.toolchain.providedLibraries, + targetDependencyMap: self.buildDescription.targetDependencyMap + ) + } + + // TODO: Currently this function will only match frameworks. + internal func detectUnexpressedDependencies( + availableLibraries: [LibraryMetadata], + targetDependencyMap: [String: [String]]? + ) { + // Ensure we only emit these once, regardless of how many builds are being done. + guard !Self.didEmitUnexpressedDependencies else { + return + } + Self.didEmitUnexpressedDependencies = true + + let availableFrameworks = Dictionary(uniqueKeysWithValues: availableLibraries.compactMap { + if let identity = Set($0.identities.map(\.identity)).spm_only { + return ("\($0.productName!).framework", identity) + } else { + return nil + } + }) + + targetDependencyMap?.keys.forEach { targetName in + let c99name = targetName.spm_mangledToC99ExtendedIdentifier() + // Since we're analysing post-facto, we don't know which parameters are the correct ones. + let possibleTempsPaths = [productsBuildParameters, toolsBuildParameters].map { + $0.buildPath.appending(component: "\(c99name).build") + } + + let usedSDKDependencies: [String] = Set(possibleTempsPaths).flatMap { possibleTempsPath in + guard let contents = try? self.fileSystem.readFileContents(possibleTempsPath.appending(component: "\(c99name).d")) else { + return [String]() + } + + // FIXME: We need a real makefile deps parser here... + let deps = contents.description.split(whereSeparator: { $0.isWhitespace }) + return deps.filter { + !$0.hasPrefix(possibleTempsPath.parentDirectory.pathString) + }.compactMap { + try? AbsolutePath(validating: String($0)) + }.compactMap { + return $0.components.first(where: { $0.hasSuffix(".framework") }) + } + } + + let dependencies: [PackageIdentity] + if let rootPackageIdentity = self.rootPackageIdentityByTargetName[targetName] { + dependencies = self.dependenciesByRootPackageIdentity[rootPackageIdentity] ?? [] + } else { + dependencies = [] + } + + Set(usedSDKDependencies).forEach { + if availableFrameworks.keys.contains($0) { + if let availableFrameworkPackageIdentity = availableFrameworks[$0], !dependencies.contains( + availableFrameworkPackageIdentity + ) { + observabilityScope.emit( + warning: "target '\(targetName)' has an unexpressed depedency on '\(availableFrameworkPackageIdentity)'" + ) + } + } + } + } + } + + /// Perform a build using the given build description and subset. + public func build(subset: BuildSubset) throws { + guard !self.productsBuildParameters.shouldSkipBuilding else { + return + } + + let buildStartTime = DispatchTime.now() + + // Get the build description (either a cached one or newly created). + + // Get the build description + let buildDescription = try getBuildDescription(subset: subset) + + // Verify dependency imports on the described targets + try verifyTargetImports(in: buildDescription) + + // Create the build system. + let buildSystem = try self.createBuildSystem(buildDescription: buildDescription) + self.buildSystem = buildSystem + + // If any plugins are part of the build set, compile them now to surface + // any errors up-front. Returns true if we should proceed with the build + // or false if not. It will already have thrown any appropriate error. + guard try self.compilePlugins(in: subset) else { + return + } + + // delegate is only available after createBuildSystem is called + self.buildSystemDelegate?.buildStart(configuration: self.productsBuildParameters.configuration) + + // Perform the build. + let llbuildTarget = try computeLLBuildTargetName(for: subset) + let success = buildSystem.build(target: llbuildTarget) + + let duration = buildStartTime.distance(to: .now()) + + self.detectUnexpressedDependencies() + + let subsetDescriptor: String? + switch subset { + case .product(let productName): + subsetDescriptor = "product '\(productName)'" + case .target(let targetName): + subsetDescriptor = "target: '\(targetName)'" + case .allExcludingTests, .allIncludingTests: + subsetDescriptor = nil + } + + self.buildSystemDelegate?.buildComplete( + success: success, + duration: duration, + subsetDescriptor: subsetDescriptor + ) + self.eventsContinuation.yield(.didFinishWithResult(success: success)) + + guard success else { throw Diagnostics.fatalError } + + // Create backwards-compatibility symlink to old build path. + let oldBuildPath = productsBuildParameters.dataPath.parentDirectory.appending( + component: productsBuildParameters.configuration.dirname + ) + if self.fileSystem.exists(oldBuildPath) { + do { try self.fileSystem.removeFileTree(oldBuildPath) } + catch { + self.observabilityScope.emit( + warning: "unable to delete \(oldBuildPath), skip creating symbolic link", + underlyingError: error + ) + return + } + } + + do { + try self.fileSystem.createSymbolicLink(oldBuildPath, pointingAt: productsBuildParameters.buildPath, relative: true) + } catch { + self.observabilityScope.emit( + warning: "unable to create symbolic link at \(oldBuildPath)", + underlyingError: error + ) + } + } + + /// Compiles any plugins specified or implied by the build subset, returning + /// true if the build should proceed. Throws an error in case of failure. A + /// reason why the build might not proceed even on success is if only plugins + /// should be compiled. + func compilePlugins(in subset: BuildSubset) throws -> Bool { + // Figure out what, if any, plugin descriptions to compile, and whether + // to continue building after that based on the subset. + let allPlugins = try getBuildDescription().pluginDescriptions + let pluginsToCompile: [PluginDescription] + let continueBuilding: Bool + switch subset { + case .allExcludingTests, .allIncludingTests: + pluginsToCompile = allPlugins + continueBuilding = true + case .product(let productName): + pluginsToCompile = allPlugins.filter{ $0.productNames.contains(productName) } + continueBuilding = pluginsToCompile.isEmpty + case .target(let targetName): + pluginsToCompile = allPlugins.filter{ $0.targetName == targetName } + continueBuilding = pluginsToCompile.isEmpty + } + + // Compile any plugins we ended up with. If any of them fails, it will + // throw. + for plugin in pluginsToCompile { + try compilePlugin(plugin) + } + + // If we get this far they all succeeded. Return whether to continue the + // build, based on the subset. + return continueBuilding + } + + // Compiles a single plugin, emitting its output and throwing an error if it + // fails. + func compilePlugin(_ plugin: PluginDescription) throws { + guard let pluginConfiguration else { + throw InternalError("unknown plugin script runner") + } + // Compile the plugin, getting back a PluginCompilationResult. + class Delegate: PluginScriptCompilerDelegate { + let preparationStepName: String + let buildSystemDelegate: AsyncLLBuildDelegate? + init(preparationStepName: String, buildSystemDelegate: AsyncLLBuildDelegate?) { + self.preparationStepName = preparationStepName + self.buildSystemDelegate = buildSystemDelegate + } + func willCompilePlugin(commandLine: [String], environment: EnvironmentVariables) { + self.buildSystemDelegate?.preparationStepStarted(preparationStepName) + } + func didCompilePlugin(result: PluginCompilationResult) { + self.buildSystemDelegate?.preparationStepHadOutput( + preparationStepName, + output: result.commandLine.joined(separator: " "), + verboseOnly: true + ) + if !result.compilerOutput.isEmpty { + self.buildSystemDelegate?.preparationStepHadOutput( + preparationStepName, + output: result.compilerOutput, + verboseOnly: false + ) + } + self.buildSystemDelegate?.preparationStepFinished(preparationStepName, result: (result.succeeded ? .succeeded : .failed)) + } + func skippedCompilingPlugin(cachedResult: PluginCompilationResult) { + // Historically we have emitted log info about cached plugins that are used. We should reconsider whether this is the right thing to do. + self.buildSystemDelegate?.preparationStepStarted(preparationStepName) + if !cachedResult.compilerOutput.isEmpty { + self.buildSystemDelegate?.preparationStepHadOutput( + preparationStepName, + output: cachedResult.compilerOutput, + verboseOnly: false + ) + } + self.buildSystemDelegate?.preparationStepFinished(preparationStepName, result: (cachedResult.succeeded ? .succeeded : .failed)) + } + } + let delegate = Delegate( + preparationStepName: "Compiling plugin \(plugin.targetName)", + buildSystemDelegate: self.buildSystemDelegate + ) + let result = try temp_await { + pluginConfiguration.scriptRunner.compilePluginScript( + sourceFiles: plugin.sources.paths, + pluginName: plugin.targetName, + toolsVersion: plugin.toolsVersion, + observabilityScope: self.observabilityScope, + callbackQueue: DispatchQueue.sharedConcurrent, + delegate: delegate, + completion: $0) + } + + // Throw an error on failure; we will already have emitted the compiler's output in this case. + if !result.succeeded { + throw Diagnostics.fatalError + } + } + + /// Compute the llbuild target name using the given subset. + private func computeLLBuildTargetName(for subset: BuildSubset) throws -> String { + switch subset { + case .allExcludingTests: + return LLBuildManifestBuilder.TargetKind.main.targetName + case .allIncludingTests: + return LLBuildManifestBuilder.TargetKind.test.targetName + default: + // FIXME: This is super unfortunate that we might need to load the package graph. + let graph = try self.modulesGraph + if let result = subset.llbuildTargetName( + for: graph, + config: self.productsBuildParameters.configuration.dirname, + observabilityScope: self.observabilityScope + ) { + return result + } + throw Diagnostics.fatalError + } + } + + /// Create the build plan and return the build description. + private func plan(subset: BuildSubset? = nil) throws -> (description: BuildDescription, manifest: LLBuildManifest) { + // Load the package graph. + let graph = try self.modulesGraph + let buildToolPluginInvocationResults: [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] + let prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] + // Invoke any build tool plugins in the graph to generate prebuild commands and build commands. + if let pluginConfiguration, !self.productsBuildParameters.shouldSkipBuilding { + // Hacky workaround for rdar://120560817, but it replicates precisely enough the original behavior before + // products/tools build parameters were split. Ideally we want to have specify the correct path at the time + // when `toolsBuildParameters` is initialized, but we have too many places in the codebase where that's + // done, which makes it hard to realign them all at once. + var pluginsBuildParameters = self.toolsBuildParameters + pluginsBuildParameters.dataPath = pluginsBuildParameters.dataPath.parentDirectory.appending(components: ["plugins", "tools"]) + let buildOperationForPluginDependencies = BuildOperation( + // FIXME: this doesn't maintain the products/tools split cleanly + productsBuildParameters: pluginsBuildParameters, + toolsBuildParameters: pluginsBuildParameters, + cacheBuildManifest: false, + packageGraphLoader: { return graph }, + additionalFileRules: self.additionalFileRules, + pkgConfigDirectories: self.pkgConfigDirectories, + dependenciesByRootPackageIdentity: [:], + targetsByRootPackageIdentity: [:], + outputStream: self.outputStream, + logLevel: self.logLevel, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope + ) + buildToolPluginInvocationResults = try graph.invokeBuildToolPlugins( + outputDir: pluginConfiguration.workDirectory.appending("outputs"), + buildParameters: pluginsBuildParameters, + additionalFileRules: self.additionalFileRules, + toolSearchDirectories: [self.toolsBuildParameters.toolchain.swiftCompilerPath.parentDirectory], + pkgConfigDirectories: self.pkgConfigDirectories, + pluginScriptRunner: pluginConfiguration.scriptRunner, + observabilityScope: self.observabilityScope, + fileSystem: self.fileSystem + ) { name, path in + try buildOperationForPluginDependencies.build(subset: .product(name)) + if let builtTool = try buildOperationForPluginDependencies.buildPlan.buildProducts.first(where: { $0.product.name == name}) { + return try builtTool.binaryPath + } else { + return nil + } + } + + + // Surface any diagnostics from build tool plugins. + var succeeded = true + for (_, (target, results)) in buildToolPluginInvocationResults { + // There is one result for each plugin that gets applied to a target. + for result in results { + let diagnosticsEmitter = self.observabilityScope.makeDiagnosticsEmitter { + var metadata = ObservabilityMetadata() + metadata.targetName = target.name + metadata.pluginName = result.plugin.name + return metadata + } + for line in result.textOutput.split(whereSeparator: { $0.isNewline }) { + diagnosticsEmitter.emit(info: line) + } + for diag in result.diagnostics { + diagnosticsEmitter.emit(diag) + } + succeeded = succeeded && result.succeeded + } + + if !succeeded { + throw StringError("build stopped due to build-tool plugin failures") + } + } + + // Run any prebuild commands provided by build tool plugins. Any failure stops the build. + prebuildCommandResults = try graph.reachableTargets.reduce(into: [:], { partial, target in + partial[target.id] = try buildToolPluginInvocationResults[target.id].map { + try self.runPrebuildCommands(for: $0.results) + } + }) + } else { + buildToolPluginInvocationResults = [:] + prebuildCommandResults = [:] + } + + // Emit warnings about any unhandled files in authored packages. We do this after applying build tool plugins, once we know what files they handled. + // rdar://113256834 This fix works for the plugins that do not have PreBuildCommands. + let targetsToConsider: [ResolvedTarget] + if let subset = subset, let recursiveDependencies = try + subset.recursiveDependencies(for: graph, observabilityScope: observabilityScope) { + targetsToConsider = recursiveDependencies + } else { + targetsToConsider = Array(graph.reachableTargets) + } + + for target in targetsToConsider { + guard let package = graph.package(for: target), package.manifest.toolsVersion >= .v5_3 else { + continue + } + + // Get the set of unhandled files in targets. + var unhandledFiles = Set(target.underlying.others) + if unhandledFiles.isEmpty { continue } + + // Subtract out any that were inputs to any commands generated by plugins. + if let result = buildToolPluginInvocationResults[target.id]?.results { + let handledFiles = result.flatMap{ $0.buildCommands.flatMap{ $0.inputFiles } } + unhandledFiles.subtract(handledFiles) + } + if unhandledFiles.isEmpty { continue } + + // Emit a diagnostic if any remain. This is kept the same as the previous message for now, but this could be improved. + let diagnosticsEmitter = self.observabilityScope.makeDiagnosticsEmitter { + var metadata = ObservabilityMetadata() + metadata.packageIdentity = package.identity + metadata.packageKind = package.manifest.packageKind + metadata.targetName = target.name + return metadata + } + var warning = "found \(unhandledFiles.count) file(s) which are unhandled; explicitly declare them as resources or exclude from the target\n" + for file in unhandledFiles { + warning += " " + file.pathString + "\n" + } + diagnosticsEmitter.emit(warning: warning) + } + + // Create the build plan based, on the graph and any information from plugins. + let plan = try BuildPlan( + productsBuildParameters: self.productsBuildParameters, + toolsBuildParameters: self.toolsBuildParameters, + graph: graph, + additionalFileRules: additionalFileRules, + buildToolPluginInvocationResults: buildToolPluginInvocationResults.mapValues(\.results), + prebuildCommandResults: prebuildCommandResults, + disableSandbox: self.pluginConfiguration?.disableSandbox ?? false, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope + ) + self._buildPlan = plan + + let (buildDescription, buildManifest) = try BuildDescription.create( + with: plan, + disableSandboxForPluginCommands: self.pluginConfiguration?.disableSandbox ?? false, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope + ) + + // Finally create the llbuild manifest from the plan. + return (buildDescription, buildManifest) + } + + /// Build the package structure target. + private func buildPackageStructure() throws -> Bool { + let buildSystem = try self.createBuildSystem(buildDescription: .none) + self.buildSystem = buildSystem + + // Build the package structure target which will re-generate the llbuild manifest, if necessary. + return buildSystem.build(target: "PackageStructure") + } + + /// Create the build system using the given build description. + /// + /// The build description should only be omitted when creating the build system for + /// building the package structure target. + private func createBuildSystem(buildDescription: BuildDescription?) throws -> SPMLLBuild.BuildSystem { + // Figure out which progress bar we have to use during the build. + let progressAnimation = ProgressAnimation.ninja( + stream: self.outputStream, + verbose: self.logLevel.isVerbose + ) + let buildExecutionContext = BuildExecutionContext( + productsBuildParameters: self.productsBuildParameters, + toolsBuildParameters: self.toolsBuildParameters, + buildDescription: buildDescription, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope, + packageStructureDelegate: self, + buildErrorAdviceProvider: self + ) + + // Create the build delegate. + let buildSystemDelegate = AsyncLLBuildDelegate( + buildSystem: self, + buildExecutionContext: buildExecutionContext, + eventsContinuation: self.eventsContinuation, + outputStream: self.outputStream, + progressAnimation: progressAnimation, + logLevel: self.logLevel, + observabilityScope: self.observabilityScope + ) + self.buildSystemDelegate = buildSystemDelegate + + let databasePath = self.productsBuildParameters.dataPath.appending("build.db").pathString + let buildSystem = SPMLLBuild.BuildSystem( + buildFile: self.productsBuildParameters.llbuildManifest.pathString, + databaseFile: databasePath, + delegate: buildSystemDelegate, + schedulerLanes: self.productsBuildParameters.workers + ) + + // TODO: this seems fragile, perhaps we replace commandFailureHandler by adding relevant calls in the delegates chain + buildSystemDelegate.commandFailureHandler = { + buildSystem.cancel() + self.eventsContinuation.yield(.didCancel) + } + + return buildSystem + } + + /// Runs any prebuild commands associated with the given list of plugin invocation results, in order, and returns the + /// results of running those prebuild commands. + private func runPrebuildCommands(for pluginResults: [BuildToolPluginInvocationResult]) throws -> [PrebuildCommandResult] { + guard let pluginConfiguration = self.pluginConfiguration else { + throw InternalError("unknown plugin script runner") + + } + // Run through all the commands from all the plugin usages in the target. + return try pluginResults.map { pluginResult in + // As we go we will collect a list of prebuild output directories whose contents should be input to the build, + // and a list of the files in those directories after running the commands. + var derivedFiles: [AbsolutePath] = [] + var prebuildOutputDirs: [AbsolutePath] = [] + for command in pluginResult.prebuildCommands { + self.observabilityScope.emit(info: "Running " + (command.configuration.displayName ?? command.configuration.executable.basename)) + + // Run the command configuration as a subshell. This doesn't return until it is done. + // TODO: We need to also use any working directory, but that support isn't yet available on all platforms at a lower level. + var commandLine = [command.configuration.executable.pathString] + command.configuration.arguments + if !pluginConfiguration.disableSandbox { + commandLine = try Sandbox.apply(command: commandLine, fileSystem: self.fileSystem, strictness: .writableTemporaryDirectory, writableDirectories: [pluginResult.pluginOutputDirectory]) + } + let processResult = try Process.popen(arguments: commandLine, environment: command.configuration.environment) + let output = try processResult.utf8Output() + processResult.utf8stderrOutput() + if processResult.exitStatus != .terminated(code: 0) { + throw StringError("failed: \(command)\n\n\(output)") + } + + // Add any files found in the output directory declared for the prebuild command after the command ends. + let outputFilesDir = command.outputFilesDirectory + if let swiftFiles = try? self.fileSystem.getDirectoryContents(outputFilesDir).sorted() { + derivedFiles.append(contentsOf: swiftFiles.map{ outputFilesDir.appending(component: $0) }) + } + + // Add the output directory to the list of directories whose structure should affect the build plan. + prebuildOutputDirs.append(outputFilesDir) + } + + // Add the results of running any prebuild commands for this invocation. + return PrebuildCommandResult(derivedFiles: derivedFiles, outputDirectories: prebuildOutputDirs) + } + } + + public func provideBuildErrorAdvice(for target: String, command: String, message: String) -> String? { + // Find the target for which the error was emitted. If we don't find it, we can't give any advice. + guard let _ = self._buildPlan?.targets.first(where: { $0.target.name == target }) else { return nil } + + // Check for cases involving modules that cannot be found. + if let importedModule = try? RegEx(pattern: "no such module '(.+)'").matchGroups(in: message).first?.first { + // A target is importing a module that can't be found. We take a look at the build plan and see if can offer any advice. + + // Look for a target with the same module name as the one that's being imported. + if let importedTarget = self._buildPlan?.targets.first(where: { $0.target.c99name == importedModule }) { + // For the moment we just check for executables that other targets try to import. + if importedTarget.target.type == .executable { + return "module '\(importedModule)' is the main module of an executable, and cannot be imported by tests and other targets" + } + if importedTarget.target.type == .macro { + return "module '\(importedModule)' is a macro, and cannot be imported by tests and other targets" + } + + // Here we can add more checks in the future. + } + } + return nil + } + + public func packageStructureChanged() -> Bool { + do { + _ = try self.plan() + } + catch Diagnostics.fatalError { + return false + } + catch { + self.observabilityScope.emit(error) + return false + } + return true + } +} diff --git a/Sources/Build/AsyncLLBuildDelegate.swift b/Sources/Build/AsyncLLBuildDelegate.swift new file mode 100644 index 00000000000..d1a06b42d0a --- /dev/null +++ b/Sources/Build/AsyncLLBuildDelegate.swift @@ -0,0 +1,353 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2018-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import _Concurrency +import Basics +import LLBuildManifest +import enum PackageModel.BuildConfiguration +import TSCBasic +import TSCUtility + +@_spi(SwiftPMInternal) +import SPMBuildCore + +import SPMLLBuild + +import enum Dispatch.DispatchTimeInterval +import protocol Foundation.LocalizedError + +/// Async-friendly llbuild delegate implementation +final class AsyncLLBuildDelegate: LLBuildBuildSystemDelegate, SwiftCompilerOutputParserDelegate { + private let outputStream: ThreadSafeOutputByteStream + private let progressAnimation: ProgressAnimationProtocol + var commandFailureHandler: (() -> Void)? + private let logLevel: Basics.Diagnostic.Severity + private let eventsContinuation: AsyncStream.Continuation + private let buildSystem: AsyncBuildOperation + private var taskTracker = CommandTaskTracker() + private var errorMessagesByTarget: [String: [String]] = [:] + private let observabilityScope: ObservabilityScope + private var cancelled: Bool = false + + /// Swift parsers keyed by llbuild command name. + private var swiftParsers: [String: SwiftCompilerOutputParser] = [:] + + /// Buffer to accumulate non-swift output until command is finished + private var nonSwiftMessageBuffers: [String: [UInt8]] = [:] + + /// The build execution context. + private let buildExecutionContext: BuildExecutionContext + + init( + buildSystem: AsyncBuildOperation, + buildExecutionContext: BuildExecutionContext, + eventsContinuation: AsyncStream.Continuation, + outputStream: OutputByteStream, + progressAnimation: ProgressAnimationProtocol, + logLevel: Basics.Diagnostic.Severity, + observabilityScope: ObservabilityScope + ) { + self.buildSystem = buildSystem + self.buildExecutionContext = buildExecutionContext + // FIXME: Implement a class convenience initializer that does this once they are supported + // https://forums.swift.org/t/allow-self-x-in-class-convenience-initializers/15924 + self.outputStream = outputStream as? ThreadSafeOutputByteStream ?? ThreadSafeOutputByteStream(outputStream) + self.progressAnimation = progressAnimation + self.logLevel = logLevel + self.observabilityScope = observabilityScope + self.eventsContinuation = eventsContinuation + + let swiftParsers = buildExecutionContext.buildDescription?.swiftCommands.mapValues { tool in + SwiftCompilerOutputParser(targetName: tool.moduleName, delegate: self) + } ?? [:] + self.swiftParsers = swiftParsers + + self.taskTracker.onTaskProgressUpdateText = { progressText, _ in + self.eventsContinuation.yield(.didUpdateTaskProgress(text: progressText)) + } + } + + // MARK: llbuildSwift.BuildSystemDelegate + + var fs: SPMLLBuild.FileSystem? { + nil + } + + func lookupTool(_ name: String) -> Tool? { + switch name { + case TestDiscoveryTool.name: + return InProcessTool(buildExecutionContext, type: TestDiscoveryCommand.self) + case TestEntryPointTool.name: + return InProcessTool(buildExecutionContext, type: TestEntryPointCommand.self) + case PackageStructureTool.name: + return InProcessTool(buildExecutionContext, type: PackageStructureCommand.self) + case CopyTool.name: + return InProcessTool(buildExecutionContext, type: CopyCommand.self) + case WriteAuxiliaryFile.name: + return InProcessTool(buildExecutionContext, type: WriteAuxiliaryFileCommand.self) + default: + return nil + } + } + + func hadCommandFailure() { + self.commandFailureHandler?() + } + + func handleDiagnostic(_ diagnostic: SPMLLBuild.Diagnostic) { + switch diagnostic.kind { + case .note: + self.observabilityScope.emit(info: diagnostic.message) + case .warning: + self.observabilityScope.emit(warning: diagnostic.message) + case .error: + self.observabilityScope.emit(error: diagnostic.message) + @unknown default: + self.observabilityScope.emit(info: diagnostic.message) + } + } + + func commandStatusChanged(_ command: SPMLLBuild.Command, kind: CommandStatusKind) { + guard !self.logLevel.isVerbose else { return } + guard command.shouldShowStatus else { return } + guard !swiftParsers.keys.contains(command.name) else { return } + + self.taskTracker.commandStatusChanged(command, kind: kind) + self.updateProgress() + } + + func commandPreparing(_ command: SPMLLBuild.Command) { + self.eventsContinuation.yield(.willStart(command: .init(command))) + } + + func commandStarted(_ command: SPMLLBuild.Command) { + guard command.shouldShowStatus else { return } + + self.eventsContinuation.yield(.didStart(command: .init(command))) + if self.logLevel.isVerbose { + self.outputStream.send("\(command.verboseDescription)\n") + self.outputStream.flush() + } + } + + func shouldCommandStart(_: SPMLLBuild.Command) -> Bool { + true + } + + func commandFinished(_ command: SPMLLBuild.Command, result: CommandResult) { + guard command.shouldShowStatus else { return } + guard !swiftParsers.keys.contains(command.name) else { return } + + if result == .cancelled { + self.cancelled = true + self.eventsContinuation.yield(.didCancel) + } + + self.eventsContinuation.yield(.didFinish(command: .init(command))) + + if !self.logLevel.isVerbose { + let targetName = self.swiftParsers[command.name]?.targetName + self.taskTracker.commandFinished(command, result: result, targetName: targetName) + self.updateProgress() + } + } + + func commandHadError(_ command: SPMLLBuild.Command, message: String) { + self.observabilityScope.emit(error: message) + } + + func commandHadNote(_ command: SPMLLBuild.Command, message: String) { + self.observabilityScope.emit(info: message) + } + + func commandHadWarning(_ command: SPMLLBuild.Command, message: String) { + self.observabilityScope.emit(warning: message) + } + + func commandCannotBuildOutputDueToMissingInputs( + _ command: SPMLLBuild.Command, + output: BuildKey, + inputs: [BuildKey] + ) { + self.observabilityScope.emit(.missingInputs(output: output, inputs: inputs)) + } + + func cannotBuildNodeDueToMultipleProducers(output: BuildKey, commands: [SPMLLBuild.Command]) { + self.observabilityScope.emit(.multipleProducers(output: output, commands: commands)) + } + + func commandProcessStarted(_ command: SPMLLBuild.Command, process: ProcessHandle) {} + + func commandProcessHadError(_ command: SPMLLBuild.Command, process: ProcessHandle, message: String) { + self.observabilityScope.emit(.commandError(command: command, message: message)) + } + + func commandProcessHadOutput(_ command: SPMLLBuild.Command, process: ProcessHandle, data: [UInt8]) { + guard command.shouldShowStatus else { return } + + if let swiftParser = swiftParsers[command.name] { + swiftParser.parse(bytes: data) + } else { + self.nonSwiftMessageBuffers[command.name, default: []] += data + } + } + + func commandProcessFinished( + _ command: SPMLLBuild.Command, + process: ProcessHandle, + result: CommandExtendedResult + ) { + // FIXME: This should really happen at the command-level and is just a stopgap measure. + let shouldFilterOutput = !self.logLevel.isVerbose && command.verboseDescription.hasPrefix("codesign ") && result.result != .failed + if let buffer = self.nonSwiftMessageBuffers[command.name], !shouldFilterOutput { + self.progressAnimation.clear() + self.outputStream.send(buffer) + self.outputStream.flush() + self.nonSwiftMessageBuffers[command.name] = nil + } + + switch result.result { + case .cancelled: + self.cancelled = true + self.eventsContinuation.yield(.didCancel) + case .failed: + // The command failed, so we queue up an asynchronous task to see if we have any error messages from the + // target to provide advice about. + guard let target = self.swiftParsers[command.name]?.targetName else { return } + guard let errorMessages = self.errorMessagesByTarget[target] else { return } + for errorMessage in errorMessages { + // Emit any advice that's provided for each error message. + if let adviceMessage = self.buildExecutionContext.buildErrorAdviceProvider?.provideBuildErrorAdvice( + for: target, + command: command.name, + message: errorMessage + ) { + self.outputStream.send("note: \(adviceMessage)\n") + self.outputStream.flush() + } + } + case .succeeded, .skipped: + break + @unknown default: + break + } + } + + func cycleDetected(rules: [BuildKey]) { + self.observabilityScope.emit(.cycleError(rules: rules)) + + self.eventsContinuation.yield(.didDetectCycleInRules) + } + + func shouldResolveCycle(rules: [BuildKey], candidate: BuildKey, action: CycleAction) -> Bool { + false + } + + /// Invoked right before running an action taken before building. + func preparationStepStarted(_ name: String) { + self.taskTracker.buildPreparationStepStarted(name) + self.updateProgress() + } + + /// Invoked when an action taken before building emits output. + /// when verboseOnly is set to true, the output will only be printed in verbose logging mode + func preparationStepHadOutput(_ name: String, output: String, verboseOnly: Bool) { + self.progressAnimation.clear() + if !verboseOnly || self.logLevel.isVerbose { + self.outputStream.send("\(output.spm_chomp())\n") + self.outputStream.flush() + } + } + + /// Invoked right after running an action taken before building. The result + /// indicates whether the action succeeded, failed, or was cancelled. + func preparationStepFinished(_ name: String, result: CommandResult) { + self.taskTracker.buildPreparationStepFinished(name) + self.updateProgress() + } + + // MARK: SwiftCompilerOutputParserDelegate + + func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didParse message: SwiftCompilerMessage) { + if self.logLevel.isVerbose { + if let text = message.verboseProgressText { + self.outputStream.send("\(text)\n") + self.outputStream.flush() + } + } else { + self.taskTracker.swiftCompilerDidOutputMessage(message, targetName: parser.targetName) + self.updateProgress() + } + + if let output = message.standardOutput { + // first we want to print the output so users have it handy + if !self.logLevel.isVerbose { + self.progressAnimation.clear() + } + + self.outputStream.send(output) + self.outputStream.flush() + + // next we want to try and scoop out any errors from the output (if reasonable size, otherwise this + // will be very slow), so they can later be passed to the advice provider in case of failure. + if output.utf8.count < 1024 * 10 { + let regex = try! RegEx(pattern: #".*(error:[^\n]*)\n.*"#, options: .dotMatchesLineSeparators) + for match in regex.matchGroups(in: output) { + self.errorMessagesByTarget[parser.targetName] = ( + self.errorMessagesByTarget[parser.targetName] ?? [] + ) + [match[0]] + } + } + } + } + + func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didFailWith error: Error) { + let message = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription + self.observabilityScope.emit(.swiftCompilerOutputParsingError(message)) + self.commandFailureHandler?() + } + + func buildStart(configuration: BuildConfiguration) { + self.progressAnimation.clear() + self.outputStream.send("Building for \(configuration == .debug ? "debugging" : "production")...\n") + self.outputStream.flush() + } + + func buildComplete(success: Bool, duration: DispatchTimeInterval, subsetDescriptor: String? = nil) { + let subsetString: String + if let subsetDescriptor { + subsetString = "of \(subsetDescriptor) " + } else { + subsetString = "" + } + + self.progressAnimation.complete(success: success) + if success { + let message = cancelled ? "Build \(subsetString)cancelled!" : "Build \(subsetString)complete!" + self.progressAnimation.clear() + self.outputStream.send("\(message) (\(duration.descriptionInSeconds))\n") + self.outputStream.flush() + } + } + + // MARK: Private + + private func updateProgress() { + if let progressText = taskTracker.latestFinishedText { + self.progressAnimation.update( + step: taskTracker.finishedCount, + total: taskTracker.totalCount, + text: progressText + ) + } + } +} diff --git a/Sources/Build/BuildExecutionContext.swift b/Sources/Build/BuildExecutionContext.swift new file mode 100644 index 00000000000..4e2d228049e --- /dev/null +++ b/Sources/Build/BuildExecutionContext.swift @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import class Basics.ObservabilityScope +import protocol Basics.FileSystem +import class Basics.ThreadSafeBox +import typealias Basics.TSCAbsolutePath +import struct SPMBuildCore.BuildParameters +import class TSCUtility.IndexStoreAPI + +/// The context available during build execution. +public final class BuildExecutionContext { + /// Build parameters for products. + let productsBuildParameters: BuildParameters + + /// Build parameters for build tools. + let toolsBuildParameters: BuildParameters + + /// The build description. + /// + /// This is optional because we might not have a valid build description when performing the + /// build for PackageStructure target. + let buildDescription: BuildDescription? + + /// The package structure delegate. + let packageStructureDelegate: PackageStructureDelegate + + /// Optional provider of build error resolution advice. + let buildErrorAdviceProvider: BuildErrorAdviceProvider? + + let fileSystem: Basics.FileSystem + + let observabilityScope: ObservabilityScope + + public init( + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, + buildDescription: BuildDescription? = nil, + fileSystem: Basics.FileSystem, + observabilityScope: ObservabilityScope, + packageStructureDelegate: PackageStructureDelegate, + buildErrorAdviceProvider: BuildErrorAdviceProvider? = nil + ) { + self.productsBuildParameters = productsBuildParameters + self.toolsBuildParameters = toolsBuildParameters + self.buildDescription = buildDescription + self.fileSystem = fileSystem + self.observabilityScope = observabilityScope + self.packageStructureDelegate = packageStructureDelegate + self.buildErrorAdviceProvider = buildErrorAdviceProvider + } + + // MARK: - Private + + private var indexStoreAPICache = ThreadSafeBox>() + + /// Reference to the index store API. + var indexStoreAPI: Result { + self.indexStoreAPICache.memoize { + do { + #if os(Windows) + // The library's runtime component is in the `bin` directory on + // Windows rather than the `lib` directory as on Unix. The `lib` + // directory contains the import library (and possibly static + // archives) which are used for linking. The runtime component is + // not (necessarily) part of the SDK distributions. + // + // NOTE: the library name here `libIndexStore.dll` is technically + // incorrect as per the Windows naming convention. However, the + // library is currently installed as `libIndexStore.dll` rather than + // `IndexStore.dll`. In the future, this may require a fallback + // search, preferring `IndexStore.dll` over `libIndexStore.dll`. + let indexStoreLib = toolsBuildParameters.toolchain.swiftCompilerPath + .parentDirectory + .appending("libIndexStore.dll") + #else + let ext = toolsBuildParameters.triple.dynamicLibraryExtension + let indexStoreLib = try toolsBuildParameters.toolchain.toolchainLibDir + .appending("libIndexStore" + ext) + #endif + return try .success(IndexStoreAPI(dylib: TSCAbsolutePath(indexStoreLib))) + } catch { + return .failure(error) + } + } + } +} diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 3d92d1676c2..8e87fc724cd 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2015-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2015-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -12,11 +12,18 @@ @_spi(SwiftPMInternal) import Basics + +@_spi(SwiftPMInternal) +import Build + import LLBuildManifest import PackageGraph import PackageLoading import PackageModel + +@_spi(SwiftPMInternal) import SPMBuildCore + import SPMLLBuild import Foundation @@ -36,6 +43,7 @@ import DriverSupport import SwiftDriver #endif +@_spi(SwiftPMInternal) public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildSystem, BuildErrorAdviceProvider { /// The delegate used by the build system. public weak var delegate: SPMBuildCore.BuildSystemDelegate? @@ -820,22 +828,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } extension BuildOperation { - public struct PluginConfiguration { - /// Entity responsible for compiling and running plugin scripts. - let scriptRunner: PluginScriptRunner - - /// Directory where plugin intermediate files are stored. - let workDirectory: AbsolutePath - - /// Whether to sandbox commands from build tool plugins. - let disableSandbox: Bool - - public init(scriptRunner: PluginScriptRunner, workDirectory: AbsolutePath, disableSandbox: Bool) { - self.scriptRunner = scriptRunner - self.workDirectory = workDirectory - self.disableSandbox = disableSandbox - } - } + public typealias PluginConfiguration = Build.PluginConfiguration } extension BuildDescription { diff --git a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift index 77d1b3cf5b8..a8fb9f936ad 100644 --- a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift +++ b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift @@ -16,7 +16,10 @@ import Dispatch import Foundation import LLBuildManifest import PackageModel + +@_spi(SwiftPMInternal) import SPMBuildCore + import SPMLLBuild import struct TSCBasic.ByteString @@ -305,7 +308,7 @@ final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand { private protocol TestBuildCommand {} -private final class InProcessTool: Tool { +final class InProcessTool: Tool { let context: BuildExecutionContext let type: CustomLLBuildCommand.Type @@ -439,84 +442,6 @@ public protocol BuildErrorAdviceProvider { func provideBuildErrorAdvice(for target: String, command: String, message: String) -> String? } -/// The context available during build execution. -public final class BuildExecutionContext { - /// Build parameters for products. - let productsBuildParameters: BuildParameters - - /// Build parameters for build tools. - let toolsBuildParameters: BuildParameters - - /// The build description. - /// - /// This is optional because we might not have a valid build description when performing the - /// build for PackageStructure target. - let buildDescription: BuildDescription? - - /// The package structure delegate. - let packageStructureDelegate: PackageStructureDelegate - - /// Optional provider of build error resolution advice. - let buildErrorAdviceProvider: BuildErrorAdviceProvider? - - let fileSystem: Basics.FileSystem - - let observabilityScope: ObservabilityScope - - public init( - productsBuildParameters: BuildParameters, - toolsBuildParameters: BuildParameters, - buildDescription: BuildDescription? = nil, - fileSystem: Basics.FileSystem, - observabilityScope: ObservabilityScope, - packageStructureDelegate: PackageStructureDelegate, - buildErrorAdviceProvider: BuildErrorAdviceProvider? = nil - ) { - self.productsBuildParameters = productsBuildParameters - self.toolsBuildParameters = toolsBuildParameters - self.buildDescription = buildDescription - self.fileSystem = fileSystem - self.observabilityScope = observabilityScope - self.packageStructureDelegate = packageStructureDelegate - self.buildErrorAdviceProvider = buildErrorAdviceProvider - } - - // MARK: - Private - - private var indexStoreAPICache = ThreadSafeBox>() - - /// Reference to the index store API. - var indexStoreAPI: Result { - self.indexStoreAPICache.memoize { - do { - #if os(Windows) - // The library's runtime component is in the `bin` directory on - // Windows rather than the `lib` directory as on Unix. The `lib` - // directory contains the import library (and possibly static - // archives) which are used for linking. The runtime component is - // not (necessarily) part of the SDK distributions. - // - // NOTE: the library name here `libIndexStore.dll` is technically - // incorrect as per the Windows naming convention. However, the - // library is currently installed as `libIndexStore.dll` rather than - // `IndexStore.dll`. In the future, this may require a fallback - // search, preferring `IndexStore.dll` over `libIndexStore.dll`. - let indexStoreLib = toolsBuildParameters.toolchain.swiftCompilerPath - .parentDirectory - .appending("libIndexStore.dll") - #else - let ext = toolsBuildParameters.triple.dynamicLibraryExtension - let indexStoreLib = try toolsBuildParameters.toolchain.toolchainLibDir - .appending("libIndexStore" + ext) - #endif - return try .success(IndexStoreAPI(dylib: TSCAbsolutePath(indexStoreLib))) - } catch { - return .failure(error) - } - } - } -} - final class WriteAuxiliaryFileCommand: CustomLLBuildCommand { override func getSignature(_ command: SPMLLBuild.Command) -> [UInt8] { guard let buildDescription = self.context.buildDescription else { @@ -998,7 +923,7 @@ final class BuildOperationBuildSystemDelegateHandler: LLBuildBuildSystemDelegate } /// Tracks tasks based on command status and swift compiler output. -private struct CommandTaskTracker { +struct CommandTaskTracker { private(set) var totalCount = 0 private(set) var finishedCount = 0 private var swiftTaskProgressTexts: [Int: String] = [:] @@ -1105,7 +1030,7 @@ private struct CommandTaskTracker { } extension SwiftCompilerMessage { - fileprivate var verboseProgressText: String? { + var verboseProgressText: String? { switch kind { case .began(let info): return ([info.commandExecutable] + info.commandArguments).joined(separator: " ") @@ -1114,7 +1039,7 @@ extension SwiftCompilerMessage { } } - fileprivate var standardOutput: String? { + var standardOutput: String? { switch kind { case .finished(let info), .signalled(let info): @@ -1128,31 +1053,31 @@ extension SwiftCompilerMessage { } extension Basics.Diagnostic { - fileprivate static func cycleError(rules: [BuildKey]) -> Self { + static func cycleError(rules: [BuildKey]) -> Self { .error("build cycle detected: " + rules.map(\.key).joined(separator: ", ")) } - fileprivate static func missingInputs(output: BuildKey, inputs: [BuildKey]) -> Self { + static func missingInputs(output: BuildKey, inputs: [BuildKey]) -> Self { let missingInputs = inputs.map(\.key).joined(separator: ", ") return .error("couldn't build \(output.key) because of missing inputs: \(missingInputs)") } - fileprivate static func multipleProducers(output: BuildKey, commands: [SPMLLBuild.Command]) -> Self { + static func multipleProducers(output: BuildKey, commands: [SPMLLBuild.Command]) -> Self { let producers = commands.map(\.description).joined(separator: ", ") return .error("couldn't build \(output.key) because of multiple producers: \(producers)") } - fileprivate static func commandError(command: SPMLLBuild.Command, message: String) -> Self { + static func commandError(command: SPMLLBuild.Command, message: String) -> Self { .error("command \(command.description) failed: \(message)") } - fileprivate static func swiftCompilerOutputParsingError(_ error: String) -> Self { + static func swiftCompilerOutputParsingError(_ error: String) -> Self { .error("failed parsing the Swift compiler output: \(error)") } } extension BuildSystemCommand { - fileprivate init(_ command: SPMLLBuild.Command) { + init(_ command: SPMLLBuild.Command) { self.init( name: command.name, description: command.description, diff --git a/Sources/Build/CMakeLists.txt b/Sources/Build/CMakeLists.txt index fcc51aed76d..e2f7778c1bd 100644 --- a/Sources/Build/CMakeLists.txt +++ b/Sources/Build/CMakeLists.txt @@ -7,6 +7,8 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_library(Build + AsyncBuildOperation.swift + AsyncLLBuildDelegate.swift BuildDescription/ClangTargetBuildDescription.swift BuildDescription/PluginDescription.swift BuildDescription/ProductBuildDescription.swift @@ -17,6 +19,7 @@ add_library(Build BuildManifest/LLBuildManifestBuilder+Product.swift BuildManifest/LLBuildManifestBuilder+Resources.swift BuildManifest/LLBuildManifestBuilder+Swift.swift + BuildExecutionContext.swift BuildOperationBuildSystemDelegateHandler.swift BuildOperation.swift BuildPlan/BuildPlan.swift @@ -25,6 +28,7 @@ add_library(Build BuildPlan/BuildPlan+Swift.swift BuildPlan/BuildPlan+Test.swift ClangSupport.swift + PluginConfiguration.swift SwiftCompilerOutputParser.swift TestObservation.swift) target_link_libraries(Build PUBLIC diff --git a/Sources/Build/PluginConfiguration.swift b/Sources/Build/PluginConfiguration.swift new file mode 100644 index 00000000000..75872c5f902 --- /dev/null +++ b/Sources/Build/PluginConfiguration.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath +import protocol SPMBuildCore.PluginScriptRunner + +public struct PluginConfiguration { + /// Entity responsible for compiling and running plugin scripts. + let scriptRunner: PluginScriptRunner + + /// Directory where plugin intermediate files are stored. + let workDirectory: AbsolutePath + + /// Whether to sandbox commands from build tool plugins. + let disableSandbox: Bool + + public init(scriptRunner: PluginScriptRunner, workDirectory: AbsolutePath, disableSandbox: Bool) { + self.scriptRunner = scriptRunner + self.workDirectory = workDirectory + self.disableSandbox = disableSandbox + } +} diff --git a/Sources/Commands/CommandWorkspaceDelegate.swift b/Sources/Commands/CommandWorkspaceDelegate.swift index accec9fe9a3..e5e5983fb0e 100644 --- a/Sources/Commands/CommandWorkspaceDelegate.swift +++ b/Sources/Commands/CommandWorkspaceDelegate.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Dispatch import class Foundation.NSLock import struct Foundation.URL diff --git a/Sources/Commands/PackageCommands/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift index acb38e87ed0..8606732616d 100644 --- a/Sources/Commands/PackageCommands/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -12,12 +12,18 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Dispatch import PackageGraph import PackageModel import SourceControl +@_spi(SwiftPMInternal) +import SPMBuildCore + struct DeprecatedAPIDiff: ParsableCommand { static let configuration = CommandConfiguration(commandName: "experimental-api-diff", abstract: "Deprecated - use `swift package diagnose-api-breaking-changes` instead", diff --git a/Sources/Commands/PackageCommands/ArchiveSource.swift b/Sources/Commands/PackageCommands/ArchiveSource.swift index 01d2b7695ef..9dd95058c0a 100644 --- a/Sources/Commands/PackageCommands/ArchiveSource.swift +++ b/Sources/Commands/PackageCommands/ArchiveSource.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import SourceControl extension SwiftPackageCommand { diff --git a/Sources/Commands/PackageCommands/CompletionCommand.swift b/Sources/Commands/PackageCommands/CompletionCommand.swift index 1326d005383..6eadce8a34f 100644 --- a/Sources/Commands/PackageCommands/CompletionCommand.swift +++ b/Sources/Commands/PackageCommands/CompletionCommand.swift @@ -11,6 +11,8 @@ //===----------------------------------------------------------------------===// import ArgumentParser + +@_spi(SwiftPMInternal) import CoreCommands import var TSCBasic.stdoutStream diff --git a/Sources/Commands/PackageCommands/ComputeChecksum.swift b/Sources/Commands/PackageCommands/ComputeChecksum.swift index 9a0b92e78e0..73ca9541d97 100644 --- a/Sources/Commands/PackageCommands/ComputeChecksum.swift +++ b/Sources/Commands/PackageCommands/ComputeChecksum.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Workspace import struct TSCBasic.SHA256 diff --git a/Sources/Commands/PackageCommands/Config.swift b/Sources/Commands/PackageCommands/Config.swift index 0d61c59a449..83eb1d4d91d 100644 --- a/Sources/Commands/PackageCommands/Config.swift +++ b/Sources/Commands/PackageCommands/Config.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Workspace import var TSCBasic.stderrStream diff --git a/Sources/Commands/PackageCommands/Describe.swift b/Sources/Commands/PackageCommands/Describe.swift index ef8001c6e7e..d8d283fbb7a 100644 --- a/Sources/Commands/PackageCommands/Describe.swift +++ b/Sources/Commands/PackageCommands/Describe.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index 8727d1dd0e5..b8f660ba73b 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -12,9 +12,16 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel + +@_spi(SwiftPMInternal) +import SPMBuildCore + import XCBuildSupport struct DumpSymbolGraph: SwiftCommand { diff --git a/Sources/Commands/PackageCommands/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift index 42195f7037d..a86d3889467 100644 --- a/Sources/Commands/PackageCommands/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import SourceControl extension SwiftPackageCommand { diff --git a/Sources/Commands/PackageCommands/Format.swift b/Sources/Commands/PackageCommands/Format.swift index f4bfdce769a..dc577594418 100644 --- a/Sources/Commands/PackageCommands/Format.swift +++ b/Sources/Commands/PackageCommands/Format.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import PackageModel import class TSCBasic.Process diff --git a/Sources/Commands/PackageCommands/Init.swift b/Sources/Commands/PackageCommands/Init.swift index a8555b00f0a..847af06b803 100644 --- a/Sources/Commands/PackageCommands/Init.swift +++ b/Sources/Commands/PackageCommands/Init.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Workspace import SPMBuildCore diff --git a/Sources/Commands/PackageCommands/InstalledPackages.swift b/Sources/Commands/PackageCommands/InstalledPackages.swift index 0ad9603eb7b..0e304e788a8 100644 --- a/Sources/Commands/PackageCommands/InstalledPackages.swift +++ b/Sources/Commands/PackageCommands/InstalledPackages.swift @@ -11,9 +11,16 @@ //===----------------------------------------------------------------------===// import ArgumentParser + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel + +@_spi(SwiftPMInternal) +import SPMBuildCore + import TSCBasic extension SwiftPackageCommand { diff --git a/Sources/Commands/PackageCommands/Learn.swift b/Sources/Commands/PackageCommands/Learn.swift index 3632c297d6e..df6bbc758e6 100644 --- a/Sources/Commands/PackageCommands/Learn.swift +++ b/Sources/Commands/PackageCommands/Learn.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import PackageGraph import PackageModel diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index 2fac7c1498d..0c472b05efb 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -12,7 +12,13 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + +@_spi(SwiftPMInternal) +import SPMBuildCore + import Dispatch import PackageGraph import PackageModel diff --git a/Sources/Commands/PackageCommands/ResetCommands.swift b/Sources/Commands/PackageCommands/ResetCommands.swift index fd2a872c228..81b07745524 100644 --- a/Sources/Commands/PackageCommands/ResetCommands.swift +++ b/Sources/Commands/PackageCommands/ResetCommands.swift @@ -11,6 +11,8 @@ //===----------------------------------------------------------------------===// import ArgumentParser + +@_spi(SwiftPMInternal) import CoreCommands extension SwiftPackageCommand { diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index 2107e60bc67..bae46153e9f 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import ArgumentParser + +@_spi(SwiftPMInternal) import CoreCommands + import TSCUtility extension SwiftPackageCommand { diff --git a/Sources/Commands/PackageCommands/ShowDependencies.swift b/Sources/Commands/PackageCommands/ShowDependencies.swift index 1572c6e0f63..086b0815b4e 100644 --- a/Sources/Commands/PackageCommands/ShowDependencies.swift +++ b/Sources/Commands/PackageCommands/ShowDependencies.swift @@ -13,7 +13,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import PackageGraph import class TSCBasic.LocalFileOutputByteStream diff --git a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift index 8c2170e2f1f..b8497dcbc09 100644 --- a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift +++ b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageGraph import PackageLoading diff --git a/Sources/Commands/PackageCommands/ToolsVersionCommand.swift b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift index 035a47ef7f4..8c167a4a8a6 100644 --- a/Sources/Commands/PackageCommands/ToolsVersionCommand.swift +++ b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import ArgumentParser + +@_spi(SwiftPMInternal) import CoreCommands + import PackageLoading import PackageModel import Workspace diff --git a/Sources/Commands/PackageCommands/Update.swift b/Sources/Commands/PackageCommands/Update.swift index 50a325ab660..c222c469d56 100644 --- a/Sources/Commands/PackageCommands/Update.swift +++ b/Sources/Commands/PackageCommands/Update.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import ArgumentParser + +@_spi(SwiftPMInternal) import CoreCommands + import Dispatch import PackageModel import PackageGraph diff --git a/Sources/Commands/Snippets/CardStack.swift b/Sources/Commands/Snippets/CardStack.swift index 72d9c5d0c5b..b9c02a80b77 100644 --- a/Sources/Commands/Snippets/CardStack.swift +++ b/Sources/Commands/Snippets/CardStack.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import PackageGraph import PackageModel diff --git a/Sources/Commands/Snippets/Cards/SnippetCard.swift b/Sources/Commands/Snippets/Cards/SnippetCard.swift index 72f1afa1017..bf87796d5f9 100644 --- a/Sources/Commands/Snippets/Cards/SnippetCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetCard.swift @@ -11,7 +11,14 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) import CoreCommands + + +@_spi(SwiftPMInternal) +import SPMBuildCore + import PackageModel import enum TSCBasic.ProcessEnv diff --git a/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift b/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift index 2636d31d586..42f21be615c 100644 --- a/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift @@ -10,7 +10,9 @@ // //===----------------------------------------------------------------------===// +@_spi(SwiftPMInternal) import CoreCommands + import PackageModel /// A card showing the snippets in a ``SnippetGroup``. diff --git a/Sources/Commands/Snippets/Cards/TopCard.swift b/Sources/Commands/Snippets/Cards/TopCard.swift index 4b7ed38a418..89fe006d487 100644 --- a/Sources/Commands/Snippets/Cards/TopCard.swift +++ b/Sources/Commands/Snippets/Cards/TopCard.swift @@ -10,7 +10,10 @@ // //===----------------------------------------------------------------------===// + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel import PackageGraph diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 96b9a813d23..b2d122e0bab 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -12,9 +12,16 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import Build + +@_spi(SwiftPMInternal) import CoreCommands + import PackageGraph + +@_spi(SwiftPMInternal) import SPMBuildCore import XCBuildSupport @@ -92,6 +99,7 @@ struct BuildCommandOptions: ParsableArguments { } /// swift-build command namespace +@_spi(SwiftPMInternal) public struct SwiftBuildCommand: AsyncSwiftCommand { public static var configuration = CommandConfiguration( commandName: "build", @@ -129,17 +137,33 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { guard let subset = options.buildSubset(observabilityScope: swiftCommandState.observabilityScope) else { throw ExitCode.failure } - let buildSystem = try swiftCommandState.createBuildSystem( - explicitProduct: options.product, - shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, - // command result output goes on stdout - // ie "swift build" should output to stdout - outputStream: TSCBasic.stdoutStream - ) - do { - try buildSystem.build(subset: subset) - } catch _ as Diagnostics { - throw ExitCode.failure + + if self.globalOptions.build.buildSystem == .experimentalAsync { + let buildSystem = try swiftCommandState.createAsyncBuildSystem( + explicitProduct: options.product, + shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, + // command result output goes on stdout + // ie "swift build" should output to stdout + outputStream: TSCBasic.stdoutStream + ) + do { + try await buildSystem.build(subset: subset) + } catch _ as Diagnostics { + throw ExitCode.failure + } + } else { + let buildSystem = try swiftCommandState.createBuildSystem( + explicitProduct: options.product, + shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, + // command result output goes on stdout + // ie "swift build" should output to stdout + outputStream: TSCBasic.stdoutStream + ) + do { + try buildSystem.build(subset: subset) + } catch _ as Diagnostics { + throw ExitCode.failure + } } } diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index de7e9077090..c92431cf908 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -12,11 +12,17 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageGraph import PackageModel +@_spi(SwiftPMInternal) +import SPMBuildCore + import enum TSCBasic.ProcessEnv import func TSCBasic.exec @@ -90,6 +96,7 @@ struct RunCommandOptions: ParsableArguments { } /// swift-run command namespace +@_spi(SwiftPMInternal) public struct SwiftRunCommand: AsyncSwiftCommand { public static var configuration = CommandConfiguration( commandName: "run", diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index 45bdca6fd70..76c76258e70 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -11,14 +11,21 @@ //===----------------------------------------------------------------------===// import ArgumentParser + @_spi(SwiftPMInternal) import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Dispatch import Foundation import PackageGraph import PackageModel + +@_spi(SwiftPMInternal) import SPMBuildCore + import func TSCLibc.exit import Workspace @@ -228,6 +235,7 @@ public enum TestOutput: String, ExpressibleByArgument { } /// swift-test tool namespace +@_spi(SwiftPMInternal) public struct SwiftTestCommand: SwiftCommand { public static var configuration = CommandConfiguration( commandName: "test", diff --git a/Sources/Commands/Utilities/APIDigester.swift b/Sources/Commands/Utilities/APIDigester.swift index 2826d9cd266..e229a775718 100644 --- a/Sources/Commands/Utilities/APIDigester.swift +++ b/Sources/Commands/Utilities/APIDigester.swift @@ -13,9 +13,14 @@ import Dispatch import Foundation +@_spi(SwiftPMInternal) import SPMBuildCore + import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import PackageGraph import PackageModel import SourceControl diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index 72eb0c7d387..962786756e9 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -11,9 +11,14 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel + +@_spi(SwiftPMInternal) import SPMBuildCore import protocol TSCBasic.OutputByteStream diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index 335b0a9995d..9d1d15b93cf 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) import CoreCommands + import PackageModel import SPMBuildCore import Workspace diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index 3df40912808..5471d924e3e 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -10,8 +10,13 @@ // //===----------------------------------------------------------------------===// +@_spi(SwiftPMInternal) import Build + +@_spi(SwiftPMInternal) import SPMBuildCore + +@_spi(SwiftPMInternal) import XCBuildSupport import class Basics.ObservabilityScope @@ -89,6 +94,7 @@ private struct XcodeBuildSystemFactory: BuildSystemFactory { } extension SwiftCommandState { + @_spi(SwiftPMInternal) public var defaultBuildSystemProvider: BuildSystemProvider { .init(providers: [ .native: NativeBuildSystemFactory(swiftCommandState: self), diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index 006372edfff..b44bcb1e85b 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -24,6 +24,7 @@ import struct PackageModel.EnabledSanitizers import struct PackageModel.PackageIdentity import enum PackageModel.Sanitizer +@_spi(SwiftPMInternal) import struct SPMBuildCore.BuildSystemProvider import struct TSCBasic.StringError @@ -32,6 +33,7 @@ import struct TSCUtility.Version import struct Workspace.WorkspaceConfiguration +@_spi(SwiftPMInternal) public struct GlobalOptions: ParsableArguments { public init() {} @@ -295,6 +297,7 @@ public struct ResolverOptions: ParsableArguments { } } +@_spi(SwiftPMInternal) public struct BuildOptions: ParsableArguments { public init() {} diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 835d6100b4c..da6580e4233 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -10,16 +10,25 @@ // //===----------------------------------------------------------------------===// +import _Concurrency import ArgumentParser import Basics + +@_spi(SwiftPMInternal) +import Build + import Dispatch import class Foundation.NSLock import class Foundation.ProcessInfo import PackageGraph import PackageLoading + @_spi(SwiftPMInternal) import PackageModel + +@_spi(SwiftPMInternal) import SPMBuildCore + import Workspace #if USE_IMPL_ONLY_IMPORTS @@ -80,6 +89,7 @@ public typealias WorkspaceDelegateProvider = ( public typealias WorkspaceLoaderProvider = (_ fileSystem: FileSystem, _ observabilityScope: ObservabilityScope) -> WorkspaceLoader +@_spi(SwiftPMInternal) public protocol _SwiftCommand { var globalOptions: GlobalOptions { get } var toolWorkspaceConfiguration: ToolWorkspaceConfiguration { get } @@ -94,6 +104,7 @@ extension _SwiftCommand { } } +@_spi(SwiftPMInternal) public protocol SwiftCommand: ParsableCommand, _SwiftCommand { func run(_ swiftCommandState: SwiftCommandState) throws } @@ -134,6 +145,7 @@ extension SwiftCommand { } } +@_spi(SwiftPMInternal) public protocol AsyncSwiftCommand: AsyncParsableCommand, _SwiftCommand { func run(_ swiftCommandState: SwiftCommandState) async throws } @@ -175,6 +187,7 @@ extension AsyncSwiftCommand { } } +@_spi(SwiftPMInternal) public final class SwiftCommandState { #if os(Windows) // unfortunately this is needed for C callback handlers used by Windows shutdown handler @@ -691,7 +704,7 @@ public final class SwiftCommandState { outputStream: OutputByteStream? = .none, logLevel: Basics.Diagnostic.Severity? = .none, observabilityScope: ObservabilityScope? = .none - ) throws -> BuildSystem { + ) throws -> any BuildSystem { guard let buildSystemProvider else { fatalError("build system provider not initialized") } @@ -716,6 +729,60 @@ public final class SwiftCommandState { return buildSystem } + // note: do not customize the OutputStream unless absolutely necessary + // "customOutputStream" is designed to support build output redirection + // but it is only expected to be used when invoking builds from "swift build" command. + // in all other cases, the build output should go to the default which is stderr + @_spi(SwiftPMInternal) + public func createAsyncBuildSystem( + explicitProduct: String? = .none, + cacheBuildManifest: Bool = true, + shouldLinkStaticSwiftStdlib: Bool = false, + productsBuildParameters: BuildParameters? = .none, + toolsBuildParameters: BuildParameters? = .none, + packageGraphLoader: (() throws -> ModulesGraph)? = .none, + outputStream: OutputByteStream? = .none, + logLevel: Basics.Diagnostic.Severity? = .none, + observabilityScope: ObservabilityScope? = .none + ) throws -> any AsyncBuildSystem { + guard let buildSystemProvider else { + fatalError("build system provider not initialized") + } + + var productsParameters = try productsBuildParameters ?? self.productsBuildParameters + productsParameters.linkingParameters.shouldLinkStaticSwiftStdlib = shouldLinkStaticSwiftStdlib + + let rootPackageInfo = try self.getRootPackageInformation() + let testEntryPointPath = productsBuildParameters?.testingParameters.testProductStyle.explicitlySpecifiedEntryPointPath + let (stream, continuation) = AsyncStream.makeStream(of: BuildSystemEvent.self) + + return try AsyncBuildOperation( + productsBuildParameters: try productsBuildParameters ?? self.productsBuildParameters, + toolsBuildParameters: try toolsBuildParameters ?? self.toolsBuildParameters, + cacheBuildManifest: cacheBuildManifest && self.canUseCachedBuildManifest(), + packageGraphLoader: packageGraphLoader ?? { + try self.loadPackageGraph( + explicitProduct: explicitProduct, + testEntryPointPath: testEntryPointPath + ) + }, + pluginConfiguration: .init( + scriptRunner: self.getPluginScriptRunner(), + workDirectory: try self.getActiveWorkspace().location.pluginWorkingDirectory, + disableSandbox: self.shouldDisableSandbox + ), + additionalFileRules: FileRuleDescription.swiftpmFileTypes, + pkgConfigDirectories: self.options.locations.pkgConfigDirectories, + dependenciesByRootPackageIdentity: rootPackageInfo.dependecies, + targetsByRootPackageIdentity: rootPackageInfo.targets, + eventsContinuation: continuation, + outputStream: outputStream ?? self.outputStream, + logLevel: logLevel ?? self.logLevel, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope ?? self.observabilityScope + ) + } + static let entitlementsMacOSWarning = """ `--disable-get-task-allow-entitlement` and `--disable-get-task-allow-entitlement` only have an effect \ when building on macOS. diff --git a/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift b/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift index d12e15240fe..9af6dd3cbbd 100644 --- a/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift +++ b/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift @@ -12,8 +12,13 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import Commands + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageCollections import PackageModel diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift index fb15d965cb6..01079b9dff3 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift @@ -12,8 +12,13 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import Commands + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel import PackageRegistry diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift index 6e09e335d2d..2a5458c196c 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift @@ -12,8 +12,13 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import Commands + +@_spi(SwiftPMInternal) import CoreCommands + import Foundation import PackageModel import PackageRegistry diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift index 2861b53e336..25a8e3bb3ad 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift @@ -12,7 +12,13 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import CoreCommands + +@_spi(SwiftPMInternal) +import Commands + import Foundation import PackageModel import PackageRegistry diff --git a/Sources/SPMBuildCore/BuildSystem/AsyncBuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/AsyncBuildSystem.swift new file mode 100644 index 00000000000..f5aefad3008 --- /dev/null +++ b/Sources/SPMBuildCore/BuildSystem/AsyncBuildSystem.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import _Concurrency +import struct PackageGraph.ModulesGraph + +/// A async-first protocol that represents a build system used by SwiftPM for all build operations. +/// This allows factoring out the implementation details between SwiftPM's `BuildOperation` and the XCBuild +/// backed `XCBuildSystem`. +@_spi(SwiftPMInternal) +public protocol AsyncBuildSystem { + /// The test products that this build system will build. + var builtTestProducts: [BuiltTestProduct] { get } + + /// Returns the modules graph used by the build system. + var modulesGraph: ModulesGraph { get throws } + + /// Builds a subset of the modules graph. + /// - Parameters: + /// - subset: The subset of the modules graph to build. + func build(subset: BuildSubset) async throws + + var buildPlan: any BuildPlan { get throws } +} + +extension AsyncBuildSystem { + /// Builds the default subset: all targets excluding tests. + public func build() async throws { + try await build(subset: .allExcludingTests) + } +} diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index 8725be2e786..68b9d6b465e 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -33,6 +33,7 @@ public enum BuildSubset { /// A protocol that represents a build system used by SwiftPM for all build operations. This allows factoring out the /// implementation details between SwiftPM's `BuildOperation` and the XCBuild backed `XCBuildSystem`. +@_spi(SwiftPMInternal) public protocol BuildSystem: Cancellable { /// The delegate used by the build system. @@ -114,6 +115,7 @@ extension BuildPlan { } } +@_spi(SwiftPMInternal) public protocol BuildSystemFactory { func makeBuildSystem( explicitProduct: String?, @@ -127,11 +129,13 @@ public protocol BuildSystemFactory { ) throws -> any BuildSystem } +@_spi(SwiftPMInternal) 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 { case native case xcode + case experimentalAsync = "experimental-async" } public let providers: [Kind: any BuildSystemFactory] @@ -171,6 +175,7 @@ private enum Errors: Swift.Error { case buildSystemProviderNotRegistered(kind: BuildSystemProvider.Kind) } +@_spi(SwiftPMInternal) public enum BuildSystemUtilities { /// Returns the build path from the environment, if present. public static func getEnvBuildPath(workingDir: AbsolutePath) throws -> AbsolutePath? { diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift index 4fd1273ead4..59bdc266b4d 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +@_spi(SwiftPMInternal) public struct BuildSystemCommand: Hashable { public let name: String public let description: String diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift index 163c16f16e3..b153190ea1f 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift @@ -12,7 +12,8 @@ import Foundation -/// BuildSystem delegate +/// ``BuildSystem`` delegate +@_spi(SwiftPMInternal) public protocol BuildSystemDelegate: AnyObject { ///Called when build command is about to start. func buildSystem(_ buildSystem: BuildSystem, willStartCommand command: BuildSystemCommand) diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystemEvent.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemEvent.swift new file mode 100644 index 00000000000..016a5a94c72 --- /dev/null +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystemEvent.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2018-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@_spi(SwiftPMInternal) +public enum BuildSystemEvent { + /// Called when build command is about to start. + case willStart(command: BuildSystemCommand) + + /// Called when build command did start. + case didStart(command: BuildSystemCommand) + + /// Called when build task did update progress. + case didUpdateTaskProgress(text: String) + + /// Called when build command did finish. + case didFinish(command: BuildSystemCommand) + + case didDetectCycleInRules + + /// Called when build did finish. + case didFinishWithResult(success: Bool) + + /// Called when build did cancel + case didCancel +} diff --git a/Sources/SPMBuildCore/CMakeLists.txt b/Sources/SPMBuildCore/CMakeLists.txt index ed5d5d5d828..9eb866a14b7 100644 --- a/Sources/SPMBuildCore/CMakeLists.txt +++ b/Sources/SPMBuildCore/CMakeLists.txt @@ -14,9 +14,11 @@ add_library(SPMBuildCore BuildParameters/BuildParameters+Linking.swift BuildParameters/BuildParameters+Output.swift BuildParameters/BuildParameters+Testing.swift + BuildSystem/AsyncBuildSystem.swift BuildSystem/BuildSystem.swift BuildSystem/BuildSystemCommand.swift BuildSystem/BuildSystemDelegate.swift + BuildSystem/BuildSystemEvent.swift BuiltTestProduct.swift Plugins/PluginContextSerializer.swift Plugins/PluginInvocation.swift diff --git a/Sources/SPMBuildCore/Triple+Extensions.swift b/Sources/SPMBuildCore/Triple+Extensions.swift index bf9a1216c3d..cdaa1f4cd5c 100644 --- a/Sources/SPMBuildCore/Triple+Extensions.swift +++ b/Sources/SPMBuildCore/Triple+Extensions.swift @@ -23,6 +23,7 @@ extension Triple { } extension Triple { + @_spi(SwiftPMInternal) 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 diff --git a/Sources/SPMTestSupport/misc.swift b/Sources/SPMTestSupport/misc.swift index 536a0c670c6..f4d8e10dea1 100644 --- a/Sources/SPMTestSupport/misc.swift +++ b/Sources/SPMTestSupport/misc.swift @@ -319,7 +319,7 @@ private func swiftArgs( @available(*, deprecated, renamed: "loadModulesGraph", - message: "Rename for consistency: the type of this functions return value is named `ModulesGraph`." + message: "Renamed for consistency: the type of this functions return value is named `ModulesGraph`." ) public func loadPackageGraph( identityResolver: IdentityResolver = DefaultIdentityResolver(), diff --git a/Sources/XCBuildSupport/XCBuildDelegate.swift b/Sources/XCBuildSupport/XCBuildDelegate.swift index acf7f0c7317..cf96f3ef569 100644 --- a/Sources/XCBuildSupport/XCBuildDelegate.swift +++ b/Sources/XCBuildSupport/XCBuildDelegate.swift @@ -12,6 +12,8 @@ import Basics import Foundation + +@_spi(SwiftPMInternal) import SPMBuildCore import class TSCBasic.ThreadSafeOutputByteStream @@ -20,6 +22,7 @@ import protocol TSCBasic.OutputByteStream import enum TSCUtility.Diagnostics import protocol TSCUtility.ProgressAnimationProtocol +@_spi(SwiftPMInternal) public class XCBuildDelegate { private let buildSystem: SPMBuildCore.BuildSystem private var parser: XCBuildOutputParser! diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index 263027977f5..1ba41f012c3 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -28,6 +28,7 @@ import func TSCBasic.memoize import enum TSCUtility.Diagnostics +@_spi(SwiftPMInternal) public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { private let buildParameters: BuildParameters private let packageGraphLoader: () throws -> ModulesGraph diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 03d505d269d..74d58fde3e4 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -12,7 +12,10 @@ import ArgumentParser import Basics + +@_spi(SwiftPMInternal) import Build + import Dispatch @_spi(SwiftPMInternal) @@ -23,7 +26,11 @@ import OrderedCollections import PackageGraph import PackageLoading import PackageModel + +@_spi(SwiftPMInternal) import SPMBuildCore + +@_spi(SwiftPMInternal) import XCBuildSupport import struct TSCBasic.KeyedPair @@ -337,6 +344,8 @@ struct SwiftBootstrapBuildTool: ParsableCommand { fileSystem: self.fileSystem, observabilityScope: self.observabilityScope ) + case .experimentalAsync: + throw InternalError("Experimental async build system can't be created in this codepath.") } } diff --git a/Sources/swift-build/CMakeLists.txt b/Sources/swift-build/CMakeLists.txt index 936298a6fad..a5935196253 100644 --- a/Sources/swift-build/CMakeLists.txt +++ b/Sources/swift-build/CMakeLists.txt @@ -10,7 +10,6 @@ add_executable(swift-build Entrypoint.swift) target_link_libraries(swift-build PRIVATE Commands) - target_compile_options(swift-build PRIVATE -parse-as-library) diff --git a/Sources/swift-build/Entrypoint.swift b/Sources/swift-build/Entrypoint.swift index 0dcb53302ec..12dfee5167b 100644 --- a/Sources/swift-build/Entrypoint.swift +++ b/Sources/swift-build/Entrypoint.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +@_spi(SwiftPMInternal) import Commands @main diff --git a/Sources/swift-package-manager/SwiftPM.swift b/Sources/swift-package-manager/SwiftPM.swift index 213c5264ccb..3b0523ad728 100644 --- a/Sources/swift-package-manager/SwiftPM.swift +++ b/Sources/swift-package-manager/SwiftPM.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) import Commands + import SwiftSDKCommand import PackageCollectionsCommand import PackageRegistryCommand diff --git a/Sources/swift-run/Entrypoint.swift b/Sources/swift-run/Entrypoint.swift index 4976cccdb15..8ddc1ea3da7 100644 --- a/Sources/swift-run/Entrypoint.swift +++ b/Sources/swift-run/Entrypoint.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +@_spi(SwiftPMInternal) import Commands @main diff --git a/Sources/swift-test/main.swift b/Sources/swift-test/main.swift index 01cef92cb73..a74668facc3 100644 --- a/Sources/swift-test/main.swift +++ b/Sources/swift-test/main.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +@_spi(SwiftPMInternal) import Commands SwiftTestCommand.main() diff --git a/Tests/BuildTests/BuildOperationTests.swift b/Tests/BuildTests/BuildOperationTests.swift index 3f196dd41fa..94061628aee 100644 --- a/Tests/BuildTests/BuildOperationTests.swift +++ b/Tests/BuildTests/BuildOperationTests.swift @@ -10,11 +10,19 @@ // //===----------------------------------------------------------------------===// -@testable import Build -@testable import PackageModel +@_spi(SwiftPMInternal) +@testable +import Build + +@testable +import PackageModel import Basics import SPMTestSupport + +@_spi(SwiftPMInternal) +import SPMBuildCore + import XCTest import class TSCBasic.BufferedOutputByteStream diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 3221369719f..9af7b42a3a0 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -12,7 +12,11 @@ import Basics @testable import Commands -@testable import CoreCommands + +@_spi(SwiftPMInternal) +@testable +import CoreCommands + import PackageGraph import PackageLoading import PackageModel diff --git a/Tests/CommandsTests/MermaidPackageSerializerTests.swift b/Tests/CommandsTests/MermaidPackageSerializerTests.swift index 7427267895f..f8524d28c04 100644 --- a/Tests/CommandsTests/MermaidPackageSerializerTests.swift +++ b/Tests/CommandsTests/MermaidPackageSerializerTests.swift @@ -15,7 +15,7 @@ import class PackageModel.Manifest import struct PackageModel.ProductDescription import struct PackageModel.TargetDescription import class TSCBasic.InMemoryFileSystem -import func SPMTestSupport.loadPackageGraph +import func SPMTestSupport.loadModulesGraph import func SPMTestSupport.XCTAssertNoDiagnostics @testable @@ -31,7 +31,7 @@ final class MermaidPackageSerializerTests: XCTestCase { "/A/Sources/ATarget/main.swift", "/A/Tests/ATargetTests/TestCases.swift" ) - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -76,7 +76,7 @@ final class MermaidPackageSerializerTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -135,7 +135,7 @@ final class MermaidPackageSerializerTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index c9b2f34b8a4..bc5ed0ccaf9 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -11,8 +11,14 @@ //===----------------------------------------------------------------------===// import Basics -@testable import CoreCommands -@testable import Commands + +@_spi(SwiftPMInternal) +@testable +import CoreCommands + +@testable +import Commands + import Foundation import PackageGraph import PackageLoading @@ -630,7 +636,7 @@ final class PackageCommandTests: CommandsTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [manifestA, manifestB, manifestC, manifestD], observabilityScope: observability.topScope diff --git a/Tests/CommandsTests/SwiftCommandStateTests.swift b/Tests/CommandsTests/SwiftCommandStateTests.swift index cbd580fa1fc..331cbfe1cc5 100644 --- a/Tests/CommandsTests/SwiftCommandStateTests.swift +++ b/Tests/CommandsTests/SwiftCommandStateTests.swift @@ -12,7 +12,11 @@ @testable import Basics @testable import Build -@testable import CoreCommands + +@_spi(SwiftPMInternal) +@testable +import CoreCommands + @testable import Commands @testable import PackageModel import SPMTestSupport @@ -242,7 +246,7 @@ final class SwiftCommandStateTests: CommandsTestCase { ]) let observer = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph(fileSystem: fs, manifests: [ + let graph = try loadModulesGraph(fileSystem: fs, manifests: [ Manifest.createRootManifest(displayName: "Pkg", path: "/Pkg", targets: [TargetDescription(name: "exe")])