diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 6d74dd63c25..7310ba5bbfe 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -149,8 +149,7 @@ package struct SwiftBuildCommand: AsyncSwiftCommand { throw ExitCode.failure } if case .allIncludingTests = subset { - var buildParameters = try swiftCommandState.productsBuildParameters - for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) { + func updateTestingParameters(of buildParameters: inout BuildParameters, library: BuildParameters.Testing.Library) { buildParameters.testingParameters = .init( configuration: buildParameters.configuration, targetTriple: buildParameters.triple, @@ -161,18 +160,30 @@ package struct SwiftBuildCommand: AsyncSwiftCommand { testEntryPointPath: globalOptions.build.testEntryPointPath, library: library ) - try build(swiftCommandState, subset: subset, buildParameters: buildParameters) + } + var productsBuildParameters = try swiftCommandState.productsBuildParameters + var toolsBuildParameters = try swiftCommandState.toolsBuildParameters + for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) { + updateTestingParameters(of: &productsBuildParameters, library: library) + updateTestingParameters(of: &toolsBuildParameters, library: library) + try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) } } else { - try build(swiftCommandState, subset: subset) + try build(swiftCommandState, subset: subset, productsBuildParameters: nil, toolsBuildParameters: nil) } } - private func build(_ swiftCommandState: SwiftCommandState, subset: BuildSubset, buildParameters: BuildParameters? = nil) throws { + private func build( + _ swiftCommandState: SwiftCommandState, + subset: BuildSubset, + productsBuildParameters: BuildParameters?, + toolsBuildParameters: BuildParameters? + ) throws { let buildSystem = try swiftCommandState.createBuildSystem( explicitProduct: options.product, shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, - productsBuildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, // command result output goes on stdout // ie "swift build" should output to stdout outputStream: TSCBasic.stdoutStream diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index afe0e9e6c45..a4d09edd0cd 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -218,11 +218,11 @@ package struct SwiftTestCommand: AsyncSwiftCommand { throw TestError.xcodeNotInstalled } - let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: .xctest) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: .xctest) // Remove test output from prior runs and validate priors. - if self.options.enableExperimentalTestOutput && buildParameters.triple.supportsTestSummary { - _ = try? localFileSystem.removeFileTree(buildParameters.testOutputPath) + if self.options.enableExperimentalTestOutput && productsBuildParameters.triple.supportsTestSummary { + _ = try? localFileSystem.removeFileTree(productsBuildParameters.testOutputPath) } let testProducts = try buildTestsIfNeeded(swiftCommandState: swiftCommandState, library: .xctest) @@ -231,7 +231,7 @@ package struct SwiftTestCommand: AsyncSwiftCommand { try await runTestProducts( testProducts, additionalArguments: xctestArgs, - buildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, swiftCommandState: swiftCommandState, library: .xctest ) @@ -258,7 +258,7 @@ package struct SwiftTestCommand: AsyncSwiftCommand { // Clean out the code coverage directory that may contain stale // profraw files from a previous run of the code coverage tool. if self.options.enableCodeCoverage { - try swiftCommandState.fileSystem.removeFileTree(buildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(productsBuildParameters.codeCovPath) } // Run the tests using the parallel runner. @@ -268,7 +268,7 @@ package struct SwiftTestCommand: AsyncSwiftCommand { toolchain: toolchain, numJobs: options.numberOfWorkers ?? ProcessInfo.processInfo.activeProcessorCount, buildOptions: globalOptions.build, - buildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, shouldOutputSuccess: swiftCommandState.logLevel <= .info, observabilityScope: swiftCommandState.observabilityScope ) @@ -287,7 +287,7 @@ package struct SwiftTestCommand: AsyncSwiftCommand { } if self.options.enableExperimentalTestOutput, !runner.ranSuccessfully { - try Self.handleTestOutput(buildParameters: buildParameters, packagePath: testProducts[0].packagePath) + try Self.handleTestOutput(productsBuildParameters: productsBuildParameters, packagePath: testProducts[0].packagePath) } } } @@ -348,13 +348,13 @@ package struct SwiftTestCommand: AsyncSwiftCommand { // MARK: - swift-testing private func swiftTestingRun(_ swiftCommandState: SwiftCommandState) async throws { - let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: .swiftTesting) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: .swiftTesting) let testProducts = try buildTestsIfNeeded(swiftCommandState: swiftCommandState, library: .swiftTesting) let additionalArguments = Array(CommandLine.arguments.dropFirst()) try await runTestProducts( testProducts, additionalArguments: additionalArguments, - buildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, swiftCommandState: swiftCommandState, library: .swiftTesting ) @@ -390,20 +390,20 @@ package struct SwiftTestCommand: AsyncSwiftCommand { private func runTestProducts( _ testProducts: [BuiltTestProduct], additionalArguments: [String], - buildParameters: BuildParameters, + productsBuildParameters: BuildParameters, swiftCommandState: SwiftCommandState, library: BuildParameters.Testing.Library ) async throws { // Clean out the code coverage directory that may contain stale // profraw files from a previous run of the code coverage tool. if self.options.enableCodeCoverage { - try swiftCommandState.fileSystem.removeFileTree(buildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(productsBuildParameters.codeCovPath) } let toolchain = try swiftCommandState.getTargetToolchain() let testEnv = try TestingSupport.constructTestEnvironment( toolchain: toolchain, - destinationBuildParameters: buildParameters, + destinationBuildParameters: productsBuildParameters, sanitizers: globalOptions.build.sanitizers ) @@ -432,17 +432,17 @@ package struct SwiftTestCommand: AsyncSwiftCommand { } if self.options.enableExperimentalTestOutput, !ranSuccessfully { - try Self.handleTestOutput(buildParameters: buildParameters, packagePath: testProducts[0].packagePath) + try Self.handleTestOutput(productsBuildParameters: productsBuildParameters, packagePath: testProducts[0].packagePath) } } - private static func handleTestOutput(buildParameters: BuildParameters, packagePath: AbsolutePath) throws { - guard localFileSystem.exists(buildParameters.testOutputPath) else { + private static func handleTestOutput(productsBuildParameters: BuildParameters, packagePath: AbsolutePath) throws { + guard localFileSystem.exists(productsBuildParameters.testOutputPath) else { print("No existing test output found.") return } - let lines = try String(contentsOfFile: buildParameters.testOutputPath.pathString).components(separatedBy: "\n") + let lines = try String(contentsOfFile: productsBuildParameters.testOutputPath.pathString).components(separatedBy: "\n") let events = try lines.map { try JSONDecoder().decode(TestEventRecord.self, from: $0) } let caseEvents = events.compactMap { $0.caseEvent } @@ -486,10 +486,10 @@ package struct SwiftTestCommand: AsyncSwiftCommand { // Merge all the profraw files to produce a single profdata file. try mergeCodeCovRawDataFiles(swiftCommandState: swiftCommandState, library: library) - let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: library) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) for product in testProducts { // Export the codecov data as JSON. - let jsonPath = buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName) + let jsonPath = productsBuildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName) try exportCodeCovAsJSON(to: jsonPath, testBinary: product.binaryPath, swiftCommandState: swiftCommandState, library: library) } } @@ -500,18 +500,18 @@ package struct SwiftTestCommand: AsyncSwiftCommand { let llvmProf = try swiftCommandState.getTargetToolchain().getLLVMProf() // Get the profraw files. - let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: library) - let codeCovFiles = try swiftCommandState.fileSystem.getDirectoryContents(buildParameters.codeCovPath) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) + let codeCovFiles = try swiftCommandState.fileSystem.getDirectoryContents(productsBuildParameters.codeCovPath) // Construct arguments for invoking the llvm-prof tool. var args = [llvmProf.pathString, "merge", "-sparse"] for file in codeCovFiles { - let filePath = buildParameters.codeCovPath.appending(component: file) + let filePath = productsBuildParameters.codeCovPath.appending(component: file) if filePath.extension == "profraw" { args.append(filePath.pathString) } } - args += ["-o", buildParameters.codeCovDataFile.pathString] + args += ["-o", productsBuildParameters.codeCovDataFile.pathString] try TSCBasic.Process.checkNonZeroExit(arguments: args) } @@ -525,11 +525,11 @@ package struct SwiftTestCommand: AsyncSwiftCommand { ) throws { // Export using the llvm-cov tool. let llvmCov = try swiftCommandState.getTargetToolchain().getLLVMCov() - let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: library) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) let args = [ llvmCov.pathString, "export", - "-instr-profile=\(buildParameters.codeCovDataFile)", + "-instr-profile=\(productsBuildParameters.codeCovDataFile)", testBinary.pathString ] let result = try TSCBasic.Process.popen(arguments: args) @@ -548,10 +548,11 @@ package struct SwiftTestCommand: AsyncSwiftCommand { swiftCommandState: SwiftCommandState, library: BuildParameters.Testing.Library ) throws -> [BuiltTestProduct] { - let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: library) + let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) return try Commands.buildTestsIfNeeded( swiftCommandState: swiftCommandState, - buildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, testProduct: self.options.sharedOptions.testProduct ) } @@ -601,8 +602,8 @@ extension SwiftTestCommand { guard let rootManifest = rootManifests.values.first else { throw StringError("invalid manifests at \(root.packages)") } - let buildParameters = try swiftCommandState.buildParametersForTest(enableCodeCoverage: true, library: .xctest) - print(buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(enableCodeCoverage: true, library: .xctest) + print(productsBuildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) } } @@ -613,7 +614,7 @@ extension SwiftTestCommand { func run(_ swiftCommandState: SwiftCommandState) throws { try SwiftTestCommand.handleTestOutput( - buildParameters: try swiftCommandState.productsBuildParameters, + productsBuildParameters: try swiftCommandState.productsBuildParameters, packagePath: localFileSystem.currentWorkingDirectory ?? .root // by definition runs in the current working directory ) } @@ -641,12 +642,16 @@ extension SwiftTestCommand { // MARK: - XCTest private func xctestRun(_ swiftCommandState: SwiftCommandState) throws { - let buildParameters = try swiftCommandState.buildParametersForTest( + let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest( enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, library: .xctest ) - let testProducts = try buildTestsIfNeeded(swiftCommandState: swiftCommandState, buildParameters: buildParameters) + let testProducts = try buildTestsIfNeeded( + swiftCommandState: swiftCommandState, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters + ) let testSuites = try TestingSupport.getTestSuites( in: testProducts, swiftCommandState: swiftCommandState, @@ -665,20 +670,21 @@ extension SwiftTestCommand { // MARK: - swift-testing private func swiftTestingRun(_ swiftCommandState: SwiftCommandState) throws { - let buildParameters = try swiftCommandState.buildParametersForTest( + let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest( enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, library: .swiftTesting ) let testProducts = try buildTestsIfNeeded( swiftCommandState: swiftCommandState, - buildParameters: buildParameters + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters ) let toolchain = try swiftCommandState.getTargetToolchain() let testEnv = try TestingSupport.constructTestEnvironment( toolchain: toolchain, - destinationBuildParameters: buildParameters, + destinationBuildParameters: productsBuildParameters, sanitizers: globalOptions.build.sanitizers ) @@ -716,11 +722,13 @@ extension SwiftTestCommand { private func buildTestsIfNeeded( swiftCommandState: SwiftCommandState, - buildParameters: BuildParameters + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters ) throws -> [BuiltTestProduct] { return try Commands.buildTestsIfNeeded( swiftCommandState: swiftCommandState, - buildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, testProduct: self.sharedOptions.testProduct ) } @@ -913,7 +921,7 @@ final class ParallelTestRunner { private let toolchain: UserToolchain private let buildOptions: BuildOptions - private let buildParameters: BuildParameters + private let productsBuildParameters: BuildParameters /// Number of tests to execute in parallel. private let numJobs: Int @@ -930,7 +938,7 @@ final class ParallelTestRunner { toolchain: UserToolchain, numJobs: Int, buildOptions: BuildOptions, - buildParameters: BuildParameters, + productsBuildParameters: BuildParameters, shouldOutputSuccess: Bool, observabilityScope: ObservabilityScope ) { @@ -957,7 +965,7 @@ final class ParallelTestRunner { } self.buildOptions = buildOptions - self.buildParameters = buildParameters + self.productsBuildParameters = productsBuildParameters assert(numJobs > 0, "num jobs should be > 0") } @@ -987,7 +995,7 @@ final class ParallelTestRunner { let testEnv = try TestingSupport.constructTestEnvironment( toolchain: self.toolchain, - destinationBuildParameters: self.buildParameters, + destinationBuildParameters: self.productsBuildParameters, sanitizers: self.buildOptions.sanitizers ) @@ -1054,7 +1062,7 @@ final class ParallelTestRunner { // Print test results. for test in processedTests.get() { - if (!test.success || shouldOutputSuccess) && !buildParameters.testingParameters.experimentalTestOutput { + if (!test.success || shouldOutputSuccess) && !productsBuildParameters.testingParameters.experimentalTestOutput { // command's result output goes on stdout // ie "swift test" should output to stdout print(test.output) @@ -1273,7 +1281,7 @@ extension SwiftCommandState { func buildParametersForTest( options: TestCommandOptions, library: BuildParameters.Testing.Library - ) throws -> BuildParameters { + ) throws -> (productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters) { var result = try self.buildParametersForTest( enableCodeCoverage: options.enableCodeCoverage, enableTestability: options.enableTestableImports, @@ -1282,7 +1290,8 @@ extension SwiftCommandState { library: library ) if try options.testLibraryOptions.enableSwiftTestingLibrarySupport(swiftCommandState: self) { - result.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] + result.productsBuildParameters.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] + result.toolsBuildParameters.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] } return result } @@ -1342,10 +1351,14 @@ private extension Basics.Diagnostic { /// - Returns: The paths to the build test products. private func buildTestsIfNeeded( swiftCommandState: SwiftCommandState, - buildParameters: BuildParameters, + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, testProduct: String? ) throws -> [BuiltTestProduct] { - let buildSystem = try swiftCommandState.createBuildSystem(productsBuildParameters: buildParameters) + let buildSystem = try swiftCommandState.createBuildSystem( + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters + ) let subset = testProduct.map(BuildSubset.product) ?? .allIncludingTests try buildSystem.build(subset: subset) diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index d582223fc08..fccd9e1e959 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -122,7 +122,7 @@ enum TestingSupport { shouldSkipBuilding: shouldSkipBuilding, experimentalTestOutput: experimentalTestOutput, library: .xctest - ), + ).productsBuildParameters, sanitizers: sanitizers ) @@ -137,7 +137,7 @@ enum TestingSupport { enableCodeCoverage: enableCodeCoverage, shouldSkipBuilding: shouldSkipBuilding, library: .xctest - ), + ).productsBuildParameters, sanitizers: sanitizers ) args = [path.description, "--dump-tests-json"] @@ -218,8 +218,35 @@ extension SwiftCommandState { shouldSkipBuilding: Bool = false, experimentalTestOutput: Bool = false, library: BuildParameters.Testing.Library - ) throws -> BuildParameters { - var parameters = try self.productsBuildParameters + ) throws -> (productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters) { + let productsBuildParameters = buildParametersForTest( + modifying: try productsBuildParameters, + enableCodeCoverage: enableCodeCoverage, + enableTestability: enableTestability, + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, + library: library + ) + let toolsBuildParameters = buildParametersForTest( + modifying: try toolsBuildParameters, + enableCodeCoverage: enableCodeCoverage, + enableTestability: enableTestability, + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, + library: library + ) + return (productsBuildParameters, toolsBuildParameters) + } + + private func buildParametersForTest( + modifying parameters: BuildParameters, + enableCodeCoverage: Bool, + enableTestability: Bool?, + shouldSkipBuilding: Bool, + experimentalTestOutput: Bool, + library: BuildParameters.Testing.Library + ) -> BuildParameters { + var parameters = parameters var explicitlyEnabledDiscovery = false var explicitlySpecifiedPath: AbsolutePath?