From fa4e9cf6249d3bea7bd99a20dd9f18f6fa17db7f Mon Sep 17 00:00:00 2001 From: Bassam Khouri Date: Fri, 1 May 2026 11:36:35 -0400 Subject: [PATCH 01/11] GHA: Simplify and reduct number of jobs Now that SwiftPM on SwiftBuild is in a good shape, let's update the GitHub actions to only build against the default build system. SwiftBuild is the default on nightly-main, while the native build system is the default on nightly 6.2. The GitHub actions are still running against nightly 6.2 to have the same environment as the Jenkins CI self hosted pipeline equivalent. Relates to #9427 Issue: rdar://165491718 (cherry picked from commit 89c6c4c0b2d6e8d449771f18b444b4f08e63230e) --- .github/workflows/pull_request.yml | 69 +++---------------- .../pull_request_dependency_check.yaml | 2 + 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 685cb068c36..9f78f0697b6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,62 +16,24 @@ jobs: needs: [soundness] strategy: fail-fast: false - matrix: - executableTargetBuildSystem: ["native"] - buildSystem: ["native", "swiftbuild"] - linuxSwiftVersion: ['["nightly-main", "nightly-6.2"]', '["nightly-main"]'] - exclude: - - buildSystem: "swiftbuild" - linuxSwiftVersion: '["nightly-main", "nightly-6.2"]' - - buildSystem: "native" - linuxSwiftVersion: '["nightly-main"]' - name: Build (${{ matrix.buildSystem }}) (exectable target built using ${{ matrix.executableTargetBuildSystem }}) + name: Build uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.11 with: enable_cross_pr_testing: true linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]' - linux_swift_versions: ${{ matrix.linuxSwiftVersion }} + linux_swift_versions: '["nightly-main", "nightly-6.2"]' linux_pre_build_command: ./.github/scripts/prebuild.sh - linux_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem}}' - # linux_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem}} && swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-test --parallel --build-system ${{ matrix.buildSystem}}' + linux_build_command: 'swift run swift-build --build-tests' windows_build_timeout: 180 - windows_swift_versions: '["nightly-main"]' + windows_swift_versions: '["nightly-main", "nightly-6.2"]' windows_pre_build_command: 'Invoke-Program .\.github\scripts\prebuild.ps1' - windows_build_command: 'Invoke-Program swift run -Xlinker /ignore:4217 --configuration release --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests -Xlinker /ignore:4217 --build-system ${{ matrix.buildSystem}}' - # windows_build_command: 'Invoke-Program swift run -Xlinker /ignore:4217 --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests -Xlinker /ignore:4217 --build-system ${{ matrix.buildSystem}}; Invoke-Program swift run --build-system ${{ matrix.executableTargetBuildSystem }} -Xlinker /ignore:4217 swift-test -Xlinker /ignore:4217 --parallel --build-system ${{ matrix.buildSystem}}' + windows_build_command: 'Invoke-Program swift run -Xlinker /ignore:4217 --configuration release swift-build --build-tests --scratch-path .tests' enable_windows_checks: true - enable_ios_checks: false enable_macos_checks: true macos_exclude_xcode_versions: "[{\"xcode_version\": \"16.4\"}]" - macos_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem}}' - # macos_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem}} && swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-test --parallel --build-system ${{ matrix.buildSystem}}' - ios_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem}} --sdk \"$(xcrun --sdk iphoneos --show-sdk-path)\" --triple arm64-apple-ios' - # ios_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem}} --sdk \"$(xcrun --sdk iphoneos --show-sdk-path)\" --triple arm64-apple-ios && swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-test --parallel --build-system ${{ matrix.buildSystem }} --sdk \"$(xcrun --sdk iphoneos --show-sdk-path)\" --triple arm64-apple-ios' - - build-using-swiftbuild: - strategy: - fail-fast: false - matrix: - executableTargetBuildSystem: ["swiftbuild"] - buildSystem: ["swiftbuild"] - name: Build (${{ matrix.buildSystem }}) (exectable target built using ${{ matrix.executableTargetBuildSystem }}) - needs: [soundness] - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.11 - with: - linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]' - linux_swift_versions: '["nightly-main"]' - linux_pre_build_command: ./.github/scripts/prebuild.sh - linux_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem }}' - enable_windows_checks: false - windows_build_timeout: 180 - windows_swift_versions: '["nightly-main"]' - windows_pre_build_command: 'Invoke-Program .\.github\scripts\prebuild.ps1' - windows_build_command: 'Invoke-Program swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem }}' + macos_build_command: 'swift run swift-build --build-tests' enable_ios_checks: false - enable_macos_checks: true - macos_exclude_xcode_versions: "[{\"xcode_version\": \"16.3\"}, {\"xcode_version\": \"16.4\"}]" - macos_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem }}' - ios_build_command: 'swift run --build-system ${{ matrix.executableTargetBuildSystem }} swift-build --build-tests --build-system ${{ matrix.buildSystem }} --sdk \"$(xcrun --sdk iphoneos --show-sdk-path)\" --triple arm64-apple-ios' + # ios_build_command: 'swift run swift-build --build-tests --sdk \"$(xcrun --sdk iphoneos --show-sdk-path)\" --triple arm64-apple-ios' soundness: name: Soundness @@ -88,8 +50,8 @@ jobs: python_lint_check_enabled: true yamllint_check_enabled: true - wasm-integration-tests: - name: Wasm Integration Tests + integration-tests: + name: Integration Tests needs: [soundness] uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.11 with: @@ -100,18 +62,9 @@ jobs: wasm_sdk_versions: '["nightly-main"]' wasm_sdk_pre_build_command: ./.github/scripts/prebuild.sh # This is a hack - replace the build command with a test command and drop the --swift-sdk arg the workflow appends. - wasm_sdk_build_command: "swift test --filter WebAssemblyIntegrationTests #" - - static-linux-integration-tests: - name: Static Linux Integration Tests - needs: [soundness] - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.11 - with: - enable_linux_checks: false - enable_windows_checks: false - enable_macos_checks: false + wasm_sdk_build_command: "swift test --parallel --experimental-xunit-message-failure --xunit-output wasm-xunit.xml --filter WebAssemblyIntegrationTests #" enable_linux_static_sdk_build: true linux_static_sdk_versions: '["nightly-main"]' linux_static_sdk_pre_build_command: ./.github/scripts/prebuild.sh # This is a hack - replace the build command with a test command and drop the --swift-sdk arg the workflow appends. - linux_static_sdk_build_command: "swift test --filter StaticLinuxIntegrationTests #" + linux_static_sdk_build_command: "swift test --parallel --experimental-xunit-message-failure --xunit-output static-linux-xunit.xml --filter StaticLinuxIntegrationTests #" diff --git a/.github/workflows/pull_request_dependency_check.yaml b/.github/workflows/pull_request_dependency_check.yaml index c9d5671b3fb..b58a4df6518 100644 --- a/.github/workflows/pull_request_dependency_check.yaml +++ b/.github/workflows/pull_request_dependency_check.yaml @@ -1,3 +1,5 @@ +name: Pull Request + on: pull_request_target: types: [opened, edited, reopened, labeled, unlabeled, synchronize] From b8bf0fa736339c9e9a897643847df765ec591b41 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 12 May 2026 04:43:36 -0400 Subject: [PATCH 02/11] Update Package branch-based dependencies (#10027) Update the Package.swift dependencies to have their branch dependency be `release/6.4.x`, to match the projects branch name. Depends on: https://github.com/swiftlang/swift-build/pull/1370 --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 925beab99fe..9cd6e4f09b7 100644 --- a/Package.swift +++ b/Package.swift @@ -1100,7 +1100,7 @@ func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] { // this right now. /// When not using local dependencies, the branch to use for llbuild and TSC repositories. -let relatedDependenciesBranch = "main" +let relatedDependenciesBranch = "release/6.4.x" if ProcessInfo.processInfo.environment["SWIFTPM_LLBUILD_FWK"] == nil { if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { @@ -1130,7 +1130,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/apple/swift-system.git", revision: "1.5.0"), .package(url: "https://github.com/apple/swift-collections.git", revision: "1.1.6"), .package(url: "https://github.com/apple/swift-certificates.git", revision: "1.10.1"), - .package(url: "https://github.com/swiftlang/swift-toolchain-sqlite.git", revision: "1.0.7"), + .package(url: "https://github.com/swiftlang/swift-toolchain-sqlite.git", revision: "1.0.9"), // Not in toolchain, used for use in previewing documentation .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), ] From f02442eb0ae722dd8d7a4de81faa4d3079d06b6d Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 12 May 2026 15:41:07 -0400 Subject: [PATCH 03/11] [6.4] Tests: Update withKnownIssue conditional (#10048) A Windows tests fails with Swift 6.2, but is fixed with Swift 6.3. Update the withKnownIssue when bloc to reflect this --- Tests/IntegrationTests/SwiftPMTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/IntegrationTests/SwiftPMTests.swift b/Tests/IntegrationTests/SwiftPMTests.swift index ffc324ca176..effad96a1b7 100644 --- a/Tests/IntegrationTests/SwiftPMTests.swift +++ b/Tests/IntegrationTests/SwiftPMTests.swift @@ -251,7 +251,7 @@ private struct SwiftPMTests { buildSystem: BuildSystemProvider.Kind, ) async throws { let config = BuildConfiguration.debug - try await withKnownIssue(isIntermittent: true) { + try await withKnownIssue { try await withTemporaryDirectory(removeTreeOnDeinit: false) { tmpDir in let packagePath = tmpDir.appending(component: "test-package-coverage") try localFileSystem.createDirectory(packagePath) @@ -331,15 +331,11 @@ private struct SwiftPMTests { let coverage = try JSONDecoder().decode(Coverage.self, from: Data(coverageJSON.contents)) // Check for 100% coverage for Subject.swift, which should happen because the per-PID files got merged. - try withKnownIssue(isIntermittent: true) { let data = try #require(coverage.data.first, "coverage JSON = \(coverage)") let subjectCoverage = try #require(data.files.first(where: { $0.filename.hasSuffix("Subject.swift") }), "coverage data files JSON = \(data.files)") #expect(subjectCoverage.summary.functions.count == 2) #expect(subjectCoverage.summary.functions.covered == 2) #expect(subjectCoverage.summary.functions.percent == 100) - } when: { - [.windows, .linux].contains(ProcessInfo.hostOperatingSystem) && buildSystem == .swiftbuild - } // Check the directory with the coverage path contains the profraw files. let coverageDirectory = coveragePath.parentDirectory @@ -370,7 +366,11 @@ private struct SwiftPMTests { } } } when: { + #if compiler(>=6.3) + false + #else ProcessInfo.hostOperatingSystem == .windows && buildSystem == .swiftbuild + #endif } } } From 092e8fa6d8646dea70c55aed75adbc3424d1ff1d Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 12 May 2026 16:12:12 -0700 Subject: [PATCH 04/11] Remove BuildParameters.buildPath in favor of a new protocol requirement on BuildSystem --- .../ClangModuleBuildDescription.swift | 2 +- .../ProductBuildDescription.swift | 11 +- .../ResolvedModule+BuildDescription.swift | 2 +- .../SwiftModuleBuildDescription.swift | 8 +- .../LLBuildManifestBuilder+Swift.swift | 2 +- .../LLBuildManifestBuilder.swift | 2 +- Sources/Build/BuildOperation.swift | 57 +++++++-- Sources/Build/BuildPlan/BuildPlan+Test.swift | 4 +- Sources/Build/BuildPlan/BuildPlan.swift | 8 +- Sources/Build/LLBuildCommands.swift | 2 +- Sources/Build/TestObservation.swift | 2 +- .../PackageCommands/DumpCommands.swift | 2 +- .../PackageCommands/GenerateSBOM.swift | 3 +- .../Commands/PackageCommands/Install.swift | 7 +- .../PackageCommands/PluginCommand.swift | 2 +- .../Commands/Snippets/Cards/SnippetCard.swift | 3 +- Sources/Commands/SwiftBuildCommand.swift | 5 +- Sources/Commands/SwiftRunCommand.swift | 9 +- Sources/Commands/SwiftTestCommand.swift | 116 ++++++++++-------- .../Commands/Utilities/PluginDelegate.swift | 20 +-- .../Commands/Utilities/TestingSupport.swift | 32 +++-- Sources/CoreCommands/BuildSystemSupport.swift | 4 +- Sources/CoreCommands/SwiftCommandState.swift | 4 +- .../BuildParameters/BuildParameters.swift | 61 +-------- .../BuildSystem/BuildSystem.swift | 35 +++++- Sources/SwiftBuildSupport/PIFBuilder.swift | 38 +++--- .../SwiftBuildSupport/SwiftBuildSystem.swift | 28 +++-- Sources/XCBuildSupport/XcodeBuildSystem.swift | 16 ++- .../MockBuildTestHelper.swift | 2 +- Sources/swift-bootstrap/main.swift | 15 ++- Tests/BuildTests/BuildPlanTests.swift | 2 +- .../SwiftBuildSupportTests/CGenPIFTests.swift | 4 - .../PIFBuilderTests.swift | 38 ++---- .../PrebuiltsPIFTests.swift | 6 +- .../SwiftBuildSystemTests.swift | 2 +- 35 files changed, 313 insertions(+), 241 deletions(-) diff --git a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift index 80f84c5f8fb..ffe0bae92ea 100644 --- a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift @@ -335,7 +335,7 @@ public final class ClangModuleBuildDescription { // Only add the build path to the framework search path if there are binary frameworks to link against. if !libraryBinaryPaths.isEmpty { - args += ["-F", buildParameters.buildPath.pathString] + args += ["-F", BuildOperation.buildProductsPath(for: buildParameters).pathString] } args += ["-I", clangTarget.includeDir.pathString] diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index 33747a5a117..551bcb01357 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -67,10 +67,15 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription /// Paths to tools shipped in binary dependencies var availableTools: [String: AbsolutePath] = [:] + /// Path to the build products directory. + public var productsPath: AbsolutePath { + BuildOperation.buildProductsPath(for: self.buildParameters) + } + /// Path to the temporary directory for this product. var tempsPath: AbsolutePath { let suffix = buildParameters.suffix - return self.buildParameters.buildPath.appending(component: "\(self.product.name)\(suffix).product") + return self.productsPath.appending(component: "\(self.product.name)\(suffix).product") } /// Path to the link filelist file. @@ -161,10 +166,10 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // Only add the build path to the framework search path if there are binary frameworks to link against. if !self.libraryBinaryPaths.isEmpty { - args += ["-F", self.buildParameters.buildPath.pathString] + args += ["-F", BuildOperation.buildProductsPath(for: self.buildParameters).pathString] } - args += ["-L", self.buildParameters.buildPath.pathString] + args += ["-L", BuildOperation.buildProductsPath(for: self.buildParameters).pathString] args += try ["-o", binaryPath.pathString] args += ["-module-name", self.product.name.spm_mangledToC99ExtendedIdentifier()] args += self.dylibs.map { "-l" + $0.product.name + $0.buildParameters.suffix } diff --git a/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift b/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift index 7701ea32e28..f91beafdd99 100644 --- a/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift +++ b/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift @@ -18,6 +18,6 @@ import SPMBuildCore extension ResolvedModule { func tempsPath(_ buildParameters: BuildParameters) -> AbsolutePath { let suffix = buildParameters.suffix - return buildParameters.buildPath.appending(component: "\(self.c99name)\(suffix).build") + return BuildOperation.buildProductsPath(for: buildParameters).appending(component: "\(self.c99name)\(suffix).build") } } diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index b4e1cf938ec..85ef4edd15e 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -138,7 +138,7 @@ public final class SwiftModuleBuildDescription { var modulesPath: AbsolutePath { let suffix = self.buildParameters.suffix - return self.buildParameters.buildPath.appending(component: "Modules\(suffix)") + return BuildOperation.buildProductsPath(for: self.buildParameters).appending(component: "Modules\(suffix)") } /// The path to the swiftmodule file after compilation. @@ -459,13 +459,13 @@ public final class SwiftModuleBuildDescription { try self.requiredMacros.forEach { macro in args += [ "-Xfrontend", "-load-plugin-library", - "-Xfrontend", macroBuildParameters.macroBinaryPath(macro).pathString + "-Xfrontend", BuildOperation.macroBinaryPath(for: macro, parameters: macroBuildParameters).pathString ] } #else let macroModules = try self.requiredMacros try macroModules.forEach { macro in - let executablePath = try macroBuildParameters.macroBinaryPath(macro).pathString + let executablePath = try BuildOperation.macroBinaryPath(for: macro, parameters: macroBuildParameters).pathString args += ["-Xfrontend", "-load-plugin-executable", "-Xfrontend", "\(executablePath)#\(macro.c99name)"] } #endif @@ -563,7 +563,7 @@ public final class SwiftModuleBuildDescription { // Only add the build path to the framework search path if there are binary frameworks to link against. if !self.libraryBinaryPaths.isEmpty { - args += ["-F", self.buildParameters.buildPath.pathString] + args += ["-F", BuildOperation.buildProductsPath(for: self.buildParameters).pathString] } // Emit the ObjC compatibility header if enabled. diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index 7475d4d3a1e..af9a4484372 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -381,7 +381,7 @@ extension LLBuildManifestBuilder { // Otherwise, come up with a path for the new file and generate a command to populate it. let swiftCompilerPathHash = String(swiftCompilerPath.pathString.hash, radix: 16, uppercase: true) - let swiftVersionFilePath = buildParameters.buildPath.appending(component: "swift-version-\(swiftCompilerPathHash).txt") + let swiftVersionFilePath = BuildOperation.buildProductsPath(for: buildParameters).appending(component: "swift-version-\(swiftCompilerPathHash).txt") self.manifest.addSwiftGetVersionCommand(swiftCompilerPath: swiftCompilerPath, swiftVersionFilePath: swiftVersionFilePath) swiftGetVersionFiles[swiftCompilerPath] = swiftVersionFilePath return swiftVersionFilePath diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift index 4d15e70cdef..72752bc9599 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift @@ -342,7 +342,7 @@ extension LLBuildManifestBuilder { extension BuildParameters { func destinationPath(forBinaryAt path: AbsolutePath) -> AbsolutePath { - self.buildPath.appending(component: path.basename) + BuildOperation.buildProductsPath(for: self).appending(component: path.basename) } var buildConfig: String { self.configuration.dirname } diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index ccd651f4afc..5cd76e3aec4 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -74,7 +74,7 @@ package struct LLBuildSystemConfiguration { self.traitConfiguration = traitConfiguration self.manifestPath = manifestPath ?? destinationBuildParameters.llbuildManifest self.databasePath = databasePath ?? scratchDirectory.appending("build.db") - self.buildDescriptionPath = buildDescriptionPath ?? destinationBuildParameters.buildDescriptionPath + self.buildDescriptionPath = buildDescriptionPath ?? BuildOperation.buildDescriptionPath(for: destinationBuildParameters) self.fileSystem = fileSystem self.logLevel = logLevel self.outputStream = outputStream @@ -109,13 +109,6 @@ package struct LLBuildSystemConfiguration { } } - func buildPath(for description: BuildParameters.Destination) -> AbsolutePath { - switch description { - case .host: self.toolsBuildParameters.buildPath - case .target: self.destinationBuildParameters.buildPath - } - } - func dataPath(for description: BuildParameters.Destination) -> AbsolutePath { switch description { case .host: self.toolsBuildParameters.dataPath @@ -125,8 +118,8 @@ package struct LLBuildSystemConfiguration { func buildDescriptionPath(for description: BuildParameters.Destination) -> AbsolutePath { switch description { - case .host: self.toolsBuildParameters.buildDescriptionPath - case .target: self.destinationBuildParameters.buildDescriptionPath + case .host: BuildOperation.buildDescriptionPath(for: self.toolsBuildParameters) + case .target: BuildOperation.buildDescriptionPath(for: self.destinationBuildParameters) } } @@ -200,6 +193,44 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS public var hasIntegratedAPIDigesterSupport: Bool { false } + public func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath { + Self.buildProductsPath(for: parameters) + } + + public static func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath { + parameters.dataPath.appending(component: parameters.configuration.dirname) + } + + /// The path to the index store directory for the given build parameters under the native build system. + public static func indexStore(for parameters: BuildParameters) -> AbsolutePath { + buildProductsPath(for: parameters).appending(components: "index", "store") + } + + /// The path to the build description for the given build parameters under the native build system. + public static func buildDescriptionPath(for parameters: BuildParameters) -> AbsolutePath { + buildProductsPath(for: parameters).appending(components: "description.json") + } + + /// The path to the test output file for the given build parameters under the native build system. + public static func testOutputPath(for parameters: BuildParameters) -> AbsolutePath { + buildProductsPath(for: parameters).appending(component: "testOutput.txt") + } + + /// Returns the path to the binary of a product for the given build parameters under the native build system. + public static func binaryPath(for product: ResolvedProduct, parameters: BuildParameters) throws -> AbsolutePath { + try buildProductsPath(for: parameters).appending(parameters.binaryRelativePath(for: product)) + } + + /// Returns the path to the built binary for a macro module under the native build system. + public static func macroBinaryPath(for module: ResolvedModule, parameters: BuildParameters) throws -> AbsolutePath { + assert(module.type == .macro) + #if BUILD_MACROS_AS_DYLIBS + return try buildProductsPath(for: parameters).appending(parameters.dynamicLibraryPath(for: module.name)) + #else + return try buildProductsPath(for: parameters).appending(parameters.executablePath(for: module.name)) + #endif + } + public convenience init( productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters, @@ -524,7 +555,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS do { try self.fileSystem.createSymbolicLink( oldBuildPath, - pointingAt: self.config.buildPath(for: .target), + pointingAt: self.buildProductsPath(for: self.config.destinationBuildParameters), relative: true ) } catch { @@ -984,7 +1015,7 @@ extension BuildOperation { // `buildPackageStructure` to recognize the split. config.databasePath = config.scratchDirectory.appending("plugin-tools.db") - config.buildDescriptionPath = config.buildPath(for: .host).appending( + config.buildDescriptionPath = self.buildProductsPath(for: self.config.toolsBuildParameters).appending( component: "plugin-tools-description.json" ) @@ -1034,7 +1065,7 @@ extension BuildOperation { if let result = try await buildToolBuilder(name, path) { return result } else { - return config.buildPath(for: .host).appending(path) + return self.buildProductsPath(for: config.toolsBuildParameters).appending(path) } } diff --git a/Sources/Build/BuildPlan/BuildPlan+Test.swift b/Sources/Build/BuildPlan/BuildPlan+Test.swift index fb65efd813b..5b835b4f15c 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Test.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Test.swift @@ -77,7 +77,7 @@ extension BuildPlan { /// Generates test discovery modules, which contain derived sources listing the discovered tests. func generateDiscoveryTargets() throws -> (target: SwiftModule, resolved: ResolvedModule, buildDescription: SwiftModuleBuildDescription) { let discoveryTargetName = "\(package.manifest.displayName)PackageDiscoveredTests" - let discoveryDerivedDir = destinationBuildParameters.buildPath.appending(components: "\(discoveryTargetName).derived") + let discoveryDerivedDir = BuildOperation.buildProductsPath(for: destinationBuildParameters).appending(components: "\(discoveryTargetName).derived") let discoveryMainFile = discoveryDerivedDir.appending(component: TestDiscoveryTool.mainFileName) var discoveryPaths: [AbsolutePath] = [] @@ -136,7 +136,7 @@ extension BuildPlan { swiftTargetDependencies: [Module.Dependency], resolvedTargetDependencies: [ResolvedModule.Dependency] ) throws -> SwiftModuleBuildDescription { - let entryPointDerivedDir = destinationBuildParameters.buildPath.appending(components: "\(testProduct.name).derived") + let entryPointDerivedDir = BuildOperation.buildProductsPath(for: destinationBuildParameters).appending(components: "\(testProduct.name).derived") let entryPointMainFileName = TestEntryPointTool.mainFileName let entryPointMainFile = entryPointDerivedDir.appending(component: entryPointMainFileName) let entryPointSources = Sources(paths: [entryPointMainFile], root: entryPointDerivedDir) diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 43cd4343d4a..1960c4ed374 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -45,7 +45,7 @@ extension BuildParameters { if let path = Environment.current["SWIFTPM_TESTS_MODULECACHE"] { return try AbsolutePath(validating: path) } - return buildPath.appending("ModuleCache") + return BuildOperation.buildProductsPath(for: self).appending("ModuleCache") } } @@ -69,7 +69,7 @@ extension BuildParameters { } if addIndexStoreArguments { - return ["-index-store-path", indexStore.pathString] + return ["-index-store-path", BuildOperation.indexStore(for: self).pathString] } return [] } @@ -588,7 +588,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Creates arguments required to launch the Swift REPL that will allow /// importing the modules in the package graph. public func createREPLArguments() throws -> CLIArguments { - let buildPath = self.toolsBuildParameters.buildPath.pathString + let buildPath = BuildOperation.buildProductsPath(for: self.toolsBuildParameters).pathString var arguments = ["repl", "-I" + buildPath, "-L" + buildPath] // Link the special REPL product that contains all of the library targets. @@ -1336,7 +1336,7 @@ extension Basics.Diagnostic { extension BuildParameters { /// Returns a named bundle's path inside the build directory. func bundlePath(named name: String) -> Basics.AbsolutePath { - self.buildPath.appending(component: name + self.triple.nsbundleExtension) + BuildOperation.buildProductsPath(for: self).appending(component: name + self.triple.nsbundleExtension) } } diff --git a/Sources/Build/LLBuildCommands.swift b/Sources/Build/LLBuildCommands.swift index e5f02449c2a..e2f81a397a6 100644 --- a/Sources/Build/LLBuildCommands.swift +++ b/Sources/Build/LLBuildCommands.swift @@ -121,7 +121,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand { return } - let index = self.context.productsBuildParameters.indexStore + let index = BuildOperation.indexStore(for: self.context.productsBuildParameters) let api = try self.context.indexStoreAPI.get() let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api) diff --git a/Sources/Build/TestObservation.swift b/Sources/Build/TestObservation.swift index bef8ef999c1..ce0cf3ba98c 100644 --- a/Sources/Build/TestObservation.swift +++ b/Sources/Build/TestObservation.swift @@ -31,7 +31,7 @@ public func generateTestObservationCode(buildParameters: BuildParameters) -> Str extension SwiftPMXCTestObserver: XCTestObservation { var testOutputPath: String { - return "\(buildParameters.testOutputPath)" + return "\(BuildOperation.testOutputPath(for: buildParameters))" } private func write(record: any Encodable) { diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index 277d2fd5d75..b59ae552afb 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -86,7 +86,7 @@ struct DumpSymbolGraph: AsyncSwiftCommand { if let symbolGraph = buildResult.symbolGraph { // The build system produced symbol graphs for us, one for each target. - let buildPath = try swiftCommandState.productsBuildParameters.buildPath + let buildPath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) // Copy the symbol graphs from the target-specific locations to the single output directory for rootPackage in try await buildSystem.getPackageGraph().rootPackages { diff --git a/Sources/Commands/PackageCommands/GenerateSBOM.swift b/Sources/Commands/PackageCommands/GenerateSBOM.swift index 0cb6bddb7ad..6440bbee199 100644 --- a/Sources/Commands/PackageCommands/GenerateSBOM.swift +++ b/Sources/Commands/PackageCommands/GenerateSBOM.swift @@ -50,6 +50,7 @@ extension SwiftPackageCommand { let resolvedPackagesStore = try workspace.resolvedPackagesStore.load() let specs = try self.sbom.sbomSpecs + let buildSystem = try await swiftCommandState.createBuildSystem() let input = SBOMInput( modulesGraph: packageGraph, dependencyGraph: nil, @@ -57,7 +58,7 @@ extension SwiftPackageCommand { filter: try self.sbom.sbomFilter, product: self.product, specs: specs.isEmpty ? Spec.allCases : specs, - dir: await SBOMCreator.resolveSBOMDirectory(from: self.sbom.sbomDirectory, withDefault: try swiftCommandState.productsBuildParameters.buildPath), + dir: await SBOMCreator.resolveSBOMDirectory(from: self.sbom.sbomDirectory, withDefault: try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters)), observabilityScope: swiftCommandState.observabilityScope ) diff --git a/Sources/Commands/PackageCommands/Install.swift b/Sources/Commands/PackageCommands/Install.swift index 5a6b833a260..ea6533edd00 100644 --- a/Sources/Commands/PackageCommands/Install.swift +++ b/Sources/Commands/PackageCommands/Install.swift @@ -89,10 +89,11 @@ extension SwiftPackageCommand { commandState.preferredBuildConfiguration = .release } - try await commandState.createBuildSystem(explicitProduct: productToInstall.name) - .build(subset: .product(productToInstall.name), buildOutputs: []) + let buildSystem = try await commandState.createBuildSystem(explicitProduct: productToInstall.name) + try await buildSystem.build(subset: .product(productToInstall.name), buildOutputs: []) - let binPath = try commandState.productsBuildParameters.buildPath.appending(component: productToInstall.name) + let binPath = try buildSystem.buildProductsPath(for: commandState.productsBuildParameters) + .appending(component: productToInstall.name) let finalBinPath = swiftpmBinDir.appending(component: binPath.basename) try commandState.fileSystem.copy(from: binPath, to: finalBinPath) diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index 6b7b736b2a2..21421758687 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -359,7 +359,7 @@ struct PluginCommand: AsyncSwiftCommand { return nil } } else { - return buildParameters.buildPath.appending(path) + return buildSystem.buildProductsPath(for: buildParameters).appending(path) } } diff --git a/Sources/Commands/Snippets/Cards/SnippetCard.swift b/Sources/Commands/Snippets/Cards/SnippetCard.swift index e4fbe38b6d7..750c28f9eb3 100644 --- a/Sources/Commands/Snippets/Cards/SnippetCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetCard.swift @@ -114,7 +114,8 @@ struct SnippetCard: Card { print("Building '\(snippet.path)'\n") let buildSystem = try await swiftCommandState.createBuildSystem(explicitProduct: snippet.name) try await buildSystem.build(subset: .product(snippet.name), buildOutputs: []) - let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: snippet.name) + let executablePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + .appending(component: snippet.name) if let exampleTarget = try await buildSystem.getPackageGraph().module(for: snippet.name) { try ProcessEnv.chdir(exampleTarget.sources.paths[0].parentDirectory) } diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index e754db8630e..93fa1f0403a 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -140,7 +140,8 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { public func run(_ swiftCommandState: SwiftCommandState) async throws { if options.shouldPrintBinPath { - return try print(swiftCommandState.productsBuildParameters.buildPath.description) + let buildSystem = try await swiftCommandState.createBuildSystem() + return try print(buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters).description) } if options.printManifestGraphviz { @@ -256,7 +257,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { filter: try self.options.sbom.sbomFilter, product: options.product, specs: try self.options.sbom.sbomSpecs, - dir: await SBOMCreator.resolveSBOMDirectory(from: self.options.sbom.sbomDirectory, withDefault: try swiftCommandState.productsBuildParameters.buildPath), + dir: await SBOMCreator.resolveSBOMDirectory(from: self.options.sbom.sbomDirectory, withDefault: try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters)), observabilityScope: swiftCommandState.observabilityScope ) diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index 1b6d7448214..b0a76265d01 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -169,7 +169,8 @@ public struct SwiftRunCommand: AsyncSwiftCommand { } let productRelativePath = try swiftCommandState.productsBuildParameters.executablePath(for: productName) - let productAbsolutePath = try swiftCommandState.productsBuildParameters.buildPath.appending(productRelativePath) + let productAbsolutePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + .appending(productRelativePath) // Make sure we are running from the original working directory. let cwd: AbsolutePath? = swiftCommandState.fileSystem.currentWorkingDirectory @@ -224,10 +225,12 @@ public struct SwiftRunCommand: AsyncSwiftCommand { try await buildSystem.build(subset: .product(productName), buildOutputs: []) } - let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: productName) + let executablePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + .appending(component: productName) let productRelativePath = try swiftCommandState.productsBuildParameters.executablePath(for: productName) - let productAbsolutePath = try swiftCommandState.productsBuildParameters.buildPath.appending(productRelativePath) + let productAbsolutePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + .appending(productRelativePath) let runnerPath: AbsolutePath let arguments: [String] diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index 5e380ea8f43..40eb250ccce 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -309,10 +309,10 @@ public struct SwiftTestCommand: AsyncSwiftCommand { return note } - private func run(_ swiftCommandState: SwiftCommandState, buildParameters: BuildParameters, testProducts: [BuiltTestProduct]) async throws { + private func run(_ swiftCommandState: SwiftCommandState, buildParameters: BuildParameters, testProducts: [BuiltTestProduct], buildSystem: any BuildSystem) async throws { // Remove test output from prior runs and validate priors. if self.options.enableExperimentalTestOutput && buildParameters.triple.supportsTestSummary { - _ = try? localFileSystem.removeFileTree(buildParameters.testOutputPath) + _ = try? localFileSystem.removeFileTree(buildSystem.testOutputPath(for: buildParameters)) } var results = [TestProductResult]() @@ -334,7 +334,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { } if !self.options.shouldRunInParallel { - let (xctestArgs, testCount, testPaths) = try xctestArgs(for: testProducts, swiftCommandState: swiftCommandState) + let (xctestArgs, testCount, testPaths) = try xctestArgs(for: testProducts, swiftCommandState: swiftCommandState, buildSystem: buildSystem) // Tests have been filtered and/or skipped; assure that we only run test products // of the tests we must run. @@ -347,7 +347,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { additionalArguments: xctestArgs, productsBuildParameters: buildParameters, swiftCommandState: swiftCommandState, - library: .xctest + library: .xctest, + buildSystem: buildSystem ) if productResults.map(\.result).reduce() == .success, testCount == 0 { results.append(contentsOf: productResults.map { @@ -363,7 +364,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { enableCodeCoverage: options.enableCodeCoverage, shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, experimentalTestOutput: options.enableExperimentalTestOutput, - sanitizers: globalOptions.build.sanitizers + sanitizers: globalOptions.build.sanitizers, + buildSystem: buildSystem ) let tests = try testSuites .filteredTests(specifier: options.testCaseSpecifier) @@ -392,7 +394,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { productsBuildParameters: buildParameters, toolsVersion: toolsVersion, shouldOutputSuccess: swiftCommandState.logLevel <= .info, - observabilityScope: swiftCommandState.observabilityScope + observabilityScope: swiftCommandState.observabilityScope, + buildSystem: buildSystem ) testResults = try runner.run(tests) @@ -422,7 +425,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { in: testProducts, swiftCommandState: swiftCommandState, shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, - sanitizers: globalOptions.build.sanitizers + sanitizers: globalOptions.build.sanitizers, + buildSystem: buildSystem ) // Filter test cases based on specifiers. @@ -439,7 +443,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { additionalArguments: [], productsBuildParameters: buildParameters, swiftCommandState: swiftCommandState, - library: .swiftTesting + library: .swiftTesting, + buildSystem: buildSystem ) ) } else { @@ -472,14 +477,14 @@ public struct SwiftTestCommand: AsyncSwiftCommand { case .failure: swiftCommandState.executionStatus = .failure if self.options.enableExperimentalTestOutput { - try Self.handleTestOutput(productsBuildParameters: buildParameters, packagePath: testProducts[0].packagePath) + try Self.handleTestOutput(productsBuildParameters: buildParameters, packagePath: testProducts[0].packagePath, buildSystem: buildSystem) } case .noMatchingTests: swiftCommandState.observabilityScope.emit(.noMatchingTests) } } - private func xctestArgs(for testProducts: [BuiltTestProduct], swiftCommandState: SwiftCommandState) throws -> (arguments: [String], testCount: Int?, testPaths: Set?) { + private func xctestArgs(for testProducts: [BuiltTestProduct], swiftCommandState: SwiftCommandState, buildSystem: any BuildSystem) throws -> (arguments: [String], testCount: Int?, testPaths: Set?) { switch options.testCaseSpecifier { case .none: if case .skip = options.skippedTests(fileSystem: swiftCommandState.fileSystem) { @@ -501,7 +506,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { enableCodeCoverage: options.enableCodeCoverage, shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, experimentalTestOutput: options.enableExperimentalTestOutput, - sanitizers: globalOptions.build.sanitizers + sanitizers: globalOptions.build.sanitizers, + buildSystem: buildSystem ) let tests = try testSuites .filteredTests(specifier: options.testCaseSpecifier) @@ -549,20 +555,20 @@ public struct SwiftTestCommand: AsyncSwiftCommand { try await command.run(swiftCommandState) } else { let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options) - let testProducts = try await buildTestsIfNeeded(swiftCommandState: swiftCommandState) + let (buildSystem, testProducts) = try await buildTestsIfNeeded(swiftCommandState: swiftCommandState) // 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(productsBuildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(buildSystem.codeCovPath(for: productsBuildParameters)) } - try await run(swiftCommandState, buildParameters: productsBuildParameters, testProducts: testProducts) + try await run(swiftCommandState, buildParameters: productsBuildParameters, testProducts: testProducts, buildSystem: buildSystem) // Process code coverage if requested. We do not process it if the test run failed. // See https://github.com/swiftlang/swift-package-manager/pull/6894 for more info. if self.options.enableCodeCoverage, swiftCommandState.executionStatus != .failure { - try await processCodeCoverage(testProducts, swiftCommandState: swiftCommandState) + try await processCodeCoverage(testProducts, swiftCommandState: swiftCommandState, buildSystem: buildSystem) } } } @@ -572,7 +578,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { additionalArguments: [String], productsBuildParameters: BuildParameters, swiftCommandState: SwiftCommandState, - library: TestingLibrary + library: TestingLibrary, + buildSystem: any BuildSystem ) async throws -> [TestProductResult] { // Pass through all arguments from the command line to Swift Testing. var additionalArguments = additionalArguments @@ -629,7 +636,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { sanitizers: globalOptions.build.sanitizers, library: library, testProductPaths: testProducts.map(\.bundlePath), - interopMode: toolsVersion?.defaultInteropMode + interopMode: toolsVersion?.defaultInteropMode, + buildSystem: buildSystem ) let runner = TestRunner( @@ -650,13 +658,13 @@ public struct SwiftTestCommand: AsyncSwiftCommand { }) } - private static func handleTestOutput(productsBuildParameters: BuildParameters, packagePath: AbsolutePath) throws { - guard localFileSystem.exists(productsBuildParameters.testOutputPath) else { + private static func handleTestOutput(productsBuildParameters: BuildParameters, packagePath: AbsolutePath, buildSystem: any BuildSystem) throws { + guard localFileSystem.exists(buildSystem.testOutputPath(for: productsBuildParameters)) else { print("No existing test output found.") return } - let lines = try String(contentsOfFile: productsBuildParameters.testOutputPath.pathString).components(separatedBy: "\n") + let lines = try String(contentsOfFile: buildSystem.testOutputPath(for: productsBuildParameters).pathString).components(separatedBy: "\n") let events = try lines.map { try JSONDecoder().decode(TestEventRecord.self, from: $0) } let caseEvents = events.compactMap { $0.caseEvent } @@ -684,7 +692,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { /// Processes the code coverage data and emits a json. private func processCodeCoverage( _ testProducts: [BuiltTestProduct], - swiftCommandState: SwiftCommandState + swiftCommandState: SwiftCommandState, + buildSystem: any BuildSystem ) async throws { let workspace = try swiftCommandState.getActiveWorkspace() let root = try swiftCommandState.getWorkspaceRoot() @@ -697,38 +706,40 @@ public struct SwiftTestCommand: AsyncSwiftCommand { } // Merge all the profraw files to produce a single profdata file. - try await mergeCodeCovRawDataFiles(swiftCommandState: swiftCommandState) + try await mergeCodeCovRawDataFiles(swiftCommandState: swiftCommandState, buildSystem: buildSystem) let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options) for product in testProducts { // Export the codecov data as JSON. - let jsonPath = productsBuildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName) + let jsonPath = buildSystem.codeCovPath(for: productsBuildParameters).appending(component: rootManifest.displayName + ".json") try await exportCodeCovAsJSON( to: jsonPath, testBinary: product.coverageBinaryPath, swiftCommandState: swiftCommandState, + buildSystem: buildSystem ) } } /// Merges all profraw profiles in codecoverage directory into default.profdata file. - private func mergeCodeCovRawDataFiles(swiftCommandState: SwiftCommandState) async throws { + private func mergeCodeCovRawDataFiles(swiftCommandState: SwiftCommandState, buildSystem: any BuildSystem) async throws { // Get the llvm-prof tool. let llvmProf = try swiftCommandState.getTargetToolchain().getLLVMProf() // Get the profraw files. let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options) - let codeCovFiles = try swiftCommandState.fileSystem.getDirectoryContents(productsBuildParameters.codeCovPath) + let codeCovPath = buildSystem.codeCovPath(for: productsBuildParameters) + let codeCovFiles = try swiftCommandState.fileSystem.getDirectoryContents(codeCovPath) // Construct arguments for invoking the llvm-prof tool. var args = [llvmProf.pathString, "merge", "-sparse"] for file in codeCovFiles { - let filePath = productsBuildParameters.codeCovPath.appending(component: file) + let filePath = codeCovPath.appending(component: file) if filePath.extension == "profraw" { args.append(filePath.pathString) } } - args += ["-o", productsBuildParameters.codeCovDataFile.pathString] + args += ["-o", buildSystem.codeCovDataFile(for: productsBuildParameters).pathString] try await AsyncProcess.checkNonZeroExit(arguments: args) } @@ -736,7 +747,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { private func exportCodeCovAsJSON( to path: AbsolutePath, testBinary: AbsolutePath, - swiftCommandState: SwiftCommandState + swiftCommandState: SwiftCommandState, + buildSystem: any BuildSystem ) async throws { // Export using the llvm-cov tool. let llvmCov = try swiftCommandState.getTargetToolchain().getLLVMCov() @@ -749,7 +761,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { let args = [ llvmCov.pathString, "export", - "-instr-profile=\(productsBuildParameters.codeCovDataFile)", + "-instr-profile=\(buildSystem.codeCovDataFile(for: productsBuildParameters))", ] + archArgs + [ testBinary.pathString, ] @@ -767,7 +779,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { /// - Returns: The paths to the build test products. private func buildTestsIfNeeded( swiftCommandState: SwiftCommandState - ) async throws -> [BuiltTestProduct] { + ) async throws -> (buildSystem: any BuildSystem, testProducts: [BuiltTestProduct]) { let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest(options: self.options) return try await Commands.buildTestsIfNeeded( swiftCommandState: swiftCommandState, @@ -820,7 +832,8 @@ extension SwiftTestCommand { throw StringError("invalid manifests at \(root.packages)") } let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(enableCodeCoverage: true) - print(productsBuildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) + let buildSystem = try await swiftCommandState.createBuildSystem() + print(buildSystem.codeCovPath(for: productsBuildParameters).appending(component: rootManifest.displayName + ".json")) } } @@ -840,14 +853,16 @@ fileprivate extension Triple { } extension SwiftTestCommand { - struct Last: SwiftCommand { + struct Last: AsyncSwiftCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftCommandState: SwiftCommandState) throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { + let buildSystem = try await swiftCommandState.createBuildSystem() try SwiftTestCommand.handleTestOutput( productsBuildParameters: try swiftCommandState.productsBuildParameters, - packagePath: localFileSystem.currentWorkingDirectory ?? .root // by definition runs in the current working directory + packagePath: localFileSystem.currentWorkingDirectory ?? .root, // by definition runs in the current working directory + buildSystem: buildSystem ) } } @@ -897,7 +912,7 @@ extension SwiftTestCommand { enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding ) - let testProducts = try await buildTestsIfNeeded( + let (buildSystem, testProducts) = try await buildTestsIfNeeded( swiftCommandState: swiftCommandState, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters @@ -911,7 +926,8 @@ extension SwiftTestCommand { sanitizers: globalOptions.build.sanitizers, library: .swiftTesting, testProductPaths: testProducts.map(\.bundlePath), - interopMode: toolsVersion?.defaultInteropMode + interopMode: toolsVersion?.defaultInteropMode, + buildSystem: buildSystem ) if testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) { @@ -921,7 +937,8 @@ extension SwiftTestCommand { enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, experimentalTestOutput: false, - sanitizers: globalOptions.build.sanitizers + sanitizers: globalOptions.build.sanitizers, + buildSystem: buildSystem ) // Print the tests. @@ -974,7 +991,7 @@ extension SwiftTestCommand { swiftCommandState: SwiftCommandState, productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters - ) async throws -> [BuiltTestProduct] { + ) async throws -> (buildSystem: any BuildSystem, testProducts: [BuiltTestProduct]) { return try await Commands.buildTestsIfNeeded( swiftCommandState: swiftCommandState, productsBuildParameters: productsBuildParameters, @@ -1244,6 +1261,8 @@ final class ParallelTestRunner { /// ObservabilityScope to emit diagnostics. private let observabilityScope: ObservabilityScope + private let buildSystem: any BuildSystem + init( bundlePaths: [AbsolutePath], cancellator: Cancellator, @@ -1253,7 +1272,8 @@ final class ParallelTestRunner { productsBuildParameters: BuildParameters, toolsVersion: ToolsVersion?, shouldOutputSuccess: Bool, - observabilityScope: ObservabilityScope + observabilityScope: ObservabilityScope, + buildSystem: any BuildSystem ) { self.bundlePaths = bundlePaths self.cancellator = cancellator @@ -1262,6 +1282,7 @@ final class ParallelTestRunner { self.toolsVersion = toolsVersion self.shouldOutputSuccess = shouldOutputSuccess self.observabilityScope = observabilityScope.makeChildScope(description: "Parallel Test Runner") + self.buildSystem = buildSystem // command's result output goes on stdout // ie "swift test" should output to stdout @@ -1314,7 +1335,8 @@ final class ParallelTestRunner { sanitizers: self.buildOptions.sanitizers, library: .xctest, // swift-testing does not use ParallelTestRunner testProductPaths: bundlePaths, - interopMode: self.toolsVersion?.defaultInteropMode + interopMode: self.toolsVersion?.defaultInteropMode, + buildSystem: self.buildSystem ) // Enqueue all the tests. @@ -1711,12 +1733,6 @@ extension TestCommandOptions { } } -extension BuildParameters { - fileprivate func codeCovAsJSONPath(packageName: String) -> AbsolutePath { - return self.codeCovPath.appending(component: packageName + ".json") - } -} - private extension Basics.Diagnostic { static var noMatchingTests: Self { .warning("No matching test cases were run") @@ -1750,7 +1766,7 @@ private func buildTestsIfNeeded( toolsBuildParameters: BuildParameters, testProduct: String?, traitConfiguration: TraitConfiguration -) async throws -> [BuiltTestProduct] { +) async throws -> (buildSystem: any BuildSystem, testProducts: [BuiltTestProduct]) { let buildSystem = try await swiftCommandState.createBuildSystem( productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters @@ -1776,16 +1792,16 @@ private func buildTestsIfNeeded( if let testProductName = testProduct { if let selectedTestProduct = testProducts.first(where: { $0.productName == testProductName }) { - return [selectedTestProduct] + return (buildSystem, [selectedTestProduct]) } let selectedTestProducts = testProducts.filter({ $0.umbrellaProductName == testProductName }) if !selectedTestProducts.isEmpty { - return selectedTestProducts + return (buildSystem, selectedTestProducts) } throw TestError.testProductNotFound(productName: testProductName) } else { - return testProducts + return (buildSystem, testProducts) } } diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index 3baed57621c..63170893f59 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -239,7 +239,7 @@ final class PluginDelegate: PluginInvocationDelegate { // Clean out the code coverage directory that may contain stale `profraw` files from a previous run of // the code coverage tool. if parameters.enableCodeCoverage { - try swiftCommandState.fileSystem.removeFileTree(toolsBuildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(buildSystem.codeCovPath(for: toolsBuildParameters)) } // Construct the environment we'll pass down to the tests. @@ -250,7 +250,8 @@ final class PluginDelegate: PluginInvocationDelegate { sanitizers: swiftCommandState.options.build.sanitizers, library: .xctest, // FIXME: support both libraries testProductPaths: buildSystem.builtTestProducts.map(\.bundlePath), - interopMode: toolsVersion?.defaultInteropMode + interopMode: toolsVersion?.defaultInteropMode, + buildSystem: buildSystem ) // Iterate over the tests and run those that match the filter. @@ -264,7 +265,8 @@ final class PluginDelegate: PluginInvocationDelegate { enableCodeCoverage: parameters.enableCodeCoverage, shouldSkipBuilding: false, experimentalTestOutput: false, - sanitizers: swiftCommandState.options.build.sanitizers + sanitizers: swiftCommandState.options.build.sanitizers, + buildSystem: buildSystem ) for testSuite in testSuites { // Each test suite is just a container for test cases (confusingly called "tests", @@ -334,12 +336,12 @@ final class PluginDelegate: PluginInvocationDelegate { let codeCoverageDataFile: AbsolutePath? if parameters.enableCodeCoverage { // Use `llvm-prof` to merge all the `.profraw` files into a single `.profdata` file. - let mergedCovFile = toolsBuildParameters.codeCovDataFile - let codeCovFileNames = try swiftCommandState.fileSystem.getDirectoryContents(toolsBuildParameters.codeCovPath) + let mergedCovFile = buildSystem.codeCovDataFile(for: toolsBuildParameters) + let codeCovFileNames = try swiftCommandState.fileSystem.getDirectoryContents(buildSystem.codeCovPath(for: toolsBuildParameters)) var llvmProfCommand = [try toolchain.getLLVMProf().pathString] llvmProfCommand += ["merge", "-sparse"] for fileName in codeCovFileNames where fileName.hasSuffix(".profraw") { - let filePath = toolsBuildParameters.codeCovPath.appending(component: fileName) + let filePath = buildSystem.codeCovPath(for: toolsBuildParameters).appending(component: fileName) llvmProfCommand.append(filePath.pathString) } llvmProfCommand += ["-o", mergedCovFile.pathString] @@ -354,8 +356,8 @@ final class PluginDelegate: PluginInvocationDelegate { } // We get the output on stdout, and have to write it to a JSON ourselves. let jsonOutput = try await AsyncProcess.checkNonZeroExit(arguments: llvmCovCommand) - let jsonCovFile = toolsBuildParameters.codeCovDataFile.parentDirectory.appending( - component: toolsBuildParameters.codeCovDataFile.basenameWithoutExt + ".json" + let jsonCovFile = mergedCovFile.parentDirectory.appending( + component: mergedCovFile.basenameWithoutExt + ".json" ) try swiftCommandState.fileSystem.writeFileContents(jsonCovFile, string: jsonOutput) @@ -406,7 +408,7 @@ final class PluginDelegate: PluginInvocationDelegate { let buildResult = try await buildSystem.build(subset: .target(targetName), buildOutputs: [.symbolGraph(options), .buildPlan]) if let symbolGraph = buildResult.symbolGraph { - let path = (try swiftCommandState.productsBuildParameters.buildPath) + let path = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) return PluginInvocationSymbolGraphResult(directoryPath: "\(path)/\(symbolGraph.outputLocationForTarget(targetName, try swiftCommandState.productsBuildParameters).joined(separator:"/"))") } else if let buildPlan = buildResult.buildPlan { func lookupDescription( diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index cd43bedbc25..7e15f46742b 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -78,7 +78,8 @@ enum TestingSupport { enableCodeCoverage: Bool, shouldSkipBuilding: Bool, experimentalTestOutput: Bool, - sanitizers: [Sanitizer] + sanitizers: [Sanitizer], + buildSystem: any BuildSystem ) throws -> [BuiltTestProduct: [TestSuite]] { let testSuitesByProduct = try testProducts .map {( @@ -89,7 +90,8 @@ enum TestingSupport { enableCodeCoverage: enableCodeCoverage, shouldSkipBuilding: shouldSkipBuilding, experimentalTestOutput: experimentalTestOutput, - sanitizers: sanitizers + sanitizers: sanitizers, + buildSystem: buildSystem ) )} return try Dictionary(throwingUniqueKeysWithValues: testSuitesByProduct) @@ -111,7 +113,8 @@ enum TestingSupport { enableCodeCoverage: Bool, shouldSkipBuilding: Bool, experimentalTestOutput: Bool, - sanitizers: [Sanitizer] + sanitizers: [Sanitizer], + buildSystem: any BuildSystem ) throws -> [TestSuite] { // Run the correct tool. var args = [String]() @@ -128,7 +131,8 @@ enum TestingSupport { sanitizers: sanitizers, library: .xctest, testProductPaths: [path], - interopMode: nil // Interop not required when listing tests + interopMode: nil, // Interop not required when listing tests + buildSystem: buildSystem ) try Self.runProcessWithExistenceCheck( path: path, @@ -150,7 +154,8 @@ enum TestingSupport { sanitizers: sanitizers, library: .xctest, testProductPaths: [path], - interopMode: nil // Interop not required when listing tests + interopMode: nil, // Interop not required when listing tests + buildSystem: buildSystem ) args = [path.description, "--dump-tests-json"] let data = try Self.runProcessWithExistenceCheck( @@ -168,7 +173,8 @@ enum TestingSupport { in testProducts: [BuiltTestProduct], swiftCommandState: SwiftCommandState, shouldSkipBuilding: Bool, - sanitizers: [Sanitizer] + sanitizers: [Sanitizer], + buildSystem: any BuildSystem ) throws -> [AbsolutePath: [String]] { let suitesByProduct = try testProducts .map {( @@ -177,7 +183,8 @@ enum TestingSupport { testProduct: $0, swiftCommandState: swiftCommandState, shouldSkipBuilding: shouldSkipBuilding, - sanitizers: sanitizers + sanitizers: sanitizers, + buildSystem: buildSystem ) )} return try Dictionary(throwingUniqueKeysWithValues: suitesByProduct) @@ -197,7 +204,8 @@ enum TestingSupport { testProduct: BuiltTestProduct, swiftCommandState: SwiftCommandState, shouldSkipBuilding: Bool, - sanitizers: [Sanitizer] + sanitizers: [Sanitizer], + buildSystem: any BuildSystem ) throws -> [String] { let toolchain = try swiftCommandState.getTargetToolchain() let env = try Self.constructTestEnvironment( @@ -213,7 +221,8 @@ enum TestingSupport { sanitizers: sanitizers, library: .swiftTesting, testProductPaths: [testProduct.bundlePath], - interopMode: nil // Interop not required when listing tests + interopMode: nil, // Interop not required when listing tests + buildSystem: buildSystem ) var args: [String] @@ -300,7 +309,8 @@ enum TestingSupport { sanitizers: [Sanitizer], library: TestingLibrary, testProductPaths: [AbsolutePath], - interopMode: String? + interopMode: String?, + buildSystem: any BuildSystem ) throws -> Environment { var env = Environment.current @@ -341,7 +351,7 @@ enum TestingSupport { // // These are all merged using `llvm-profdata merge` once the outer test command has // completed. - let codecovProfile = buildParameters.buildPath.appending(components: "codecov", "\(library)%m.%p.profraw") + let codecovProfile = buildSystem.buildProductsPath(for: buildParameters).appending(components: "codecov", "\(library)%m.%p.profraw") env["LLVM_PROFILE_FILE"] = codecovProfile.pathString } diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index d35883ae71b..e3ddf294d40 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -42,7 +42,9 @@ private struct NativeBuildSystemFactory: BuildSystemFactory { _ = try await swiftCommandState.getRootPackageInformation(enableAllTraits) let testEntryPointPath = productsBuildParameters?.testProductStyle.explicitlySpecifiedEntryPointPath let cacheBuildManifest = if cacheBuildManifest { - try await self.swiftCommandState.canUseCachedBuildManifest() + try await self.swiftCommandState.canUseCachedBuildManifest( + buildDescriptionPath: BuildOperation.buildDescriptionPath(for: try productsBuildParameters ?? self.swiftCommandState.productsBuildParameters) + ) } else { false } diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 76ae931350b..6bf3525287d 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -902,7 +902,7 @@ public final class SwiftCommandState { try self._manifestLoader.get() } - public func canUseCachedBuildManifest(_ traitConfiguration: TraitConfiguration = .default) async throws -> Bool { + public func canUseCachedBuildManifest(_ traitConfiguration: TraitConfiguration = .default, buildDescriptionPath: AbsolutePath) async throws -> Bool { if !self.options.caching.cacheBuildManifest { return false } @@ -910,7 +910,7 @@ public final class SwiftCommandState { let buildParameters = try self.productsBuildParameters let haveBuildManifestAndDescription = self.fileSystem.exists(buildParameters.llbuildManifest) && - self.fileSystem.exists(buildParameters.buildDescriptionPath) + self.fileSystem.exists(buildDescriptionPath) if !haveBuildManifestAndDescription { return false diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index b7d0366167c..f250eb3a089 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -255,43 +255,6 @@ public struct BuildParameters: Encodable { self.apiDigesterMode = apiDigesterMode } - /// The path to the build directory (inside the data directory). - public var buildPath: Basics.AbsolutePath { - // TODO: query the build system for this. - switch buildSystemKind { - case .xcode, .swiftbuild: - var configDir: String = configuration.dirname.capitalized - if self.triple.isMacOSX { - // no suffix - } else if self.triple.isAndroid() { - configDir += "-android" - } else if self.triple.isWasm { - configDir += "-webassembly" - } else { - configDir += "-" + (self.triple.darwinPlatform?.platformName ?? self.triple.osNameUnversioned) - } - return dataPath.appending(components: "Products", configDir) - case .native: - return dataPath.appending(component: configuration.dirname) - } - } - - /// The path to the index store directory. - public var indexStore: Basics.AbsolutePath { - assert(indexStoreMode != .off, "index store is disabled") - return buildPath.appending(components: "index", "store") - } - - /// The path to the code coverage directory. - public var codeCovPath: Basics.AbsolutePath { - return buildPath.appending("codecov") - } - - /// The path to the code coverage profdata file. - public var codeCovDataFile: Basics.AbsolutePath { - return codeCovPath.appending("default.profdata") - } - public var llbuildManifest: Basics.AbsolutePath { // FIXME: this path isn't specific to `BuildParameters` due to its use of `..` // FIXME: it should be calculated in a different place @@ -304,30 +267,8 @@ public struct BuildParameters: Encodable { return dataPath.appending(components: "..", "manifest.pif") } - public var buildDescriptionPath: Basics.AbsolutePath { - // FIXME: this path isn't specific to `BuildParameters`, should be moved one directory level higher - return buildPath.appending(components: "description.json") - } - - public var testOutputPath: Basics.AbsolutePath { - return buildPath.appending(component: "testOutput.txt") - } - /// Returns the path to the binary of a product for the current build parameters. - public func binaryPath(for product: ResolvedProduct) throws -> Basics.AbsolutePath { - return try buildPath.appending(binaryRelativePath(for: product)) - } - - public func macroBinaryPath(_ module: ResolvedModule) throws -> Basics.AbsolutePath { - assert(module.type == .macro) - #if BUILD_MACROS_AS_DYLIBS - return buildPath.appending(try dynamicLibraryPath(for: module.name)) - #else - return buildPath.appending(try executablePath(for: module.name)) - #endif - } - /// Returns the path to the dynamic library of a product for the current build parameters. - private func dynamicLibraryPath(for name: String) throws -> Basics.RelativePath { + package func dynamicLibraryPath(for name: String) throws -> Basics.RelativePath { try RelativePath(validating: "\(self.triple.dynamicLibraryPrefix)\(name)\(self.suffix)\(self.triple.dynamicLibraryExtension)") } diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index f5125985439..48b8ed8ad42 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -97,6 +97,9 @@ public protocol BuildSystem: Cancellable { var hasIntegratedAPIDigesterSupport: Bool { get } func generatePIF(preserveStructure: Bool) async throws -> String + + /// The path to the build directory for the given build parameters. + func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath } extension BuildSystem { @@ -105,6 +108,32 @@ extension BuildSystem { public func build() async throws -> BuildResult { try await build(subset: .allExcludingTests, buildOutputs: []) } + + /// The path to the index store directory for the given build parameters. + public func indexStore(for parameters: BuildParameters) -> AbsolutePath { + assert(parameters.indexStoreMode != .off, "index store is disabled") + return buildProductsPath(for: parameters).appending(components: "index", "store") + } + + /// The path to the code coverage directory for the given build parameters. + public func codeCovPath(for parameters: BuildParameters) -> AbsolutePath { + buildProductsPath(for: parameters).appending("codecov") + } + + /// The path to the code coverage profdata file for the given build parameters. + public func codeCovDataFile(for parameters: BuildParameters) -> AbsolutePath { + codeCovPath(for: parameters).appending("default.profdata") + } + + /// The path to the test output file for the given build parameters. + public func testOutputPath(for parameters: BuildParameters) -> AbsolutePath { + buildProductsPath(for: parameters).appending(component: "testOutput.txt") + } + + /// Returns the path to the binary of a product for the given build parameters. + public func binaryPath(for product: ResolvedProduct, parameters: BuildParameters) throws -> AbsolutePath { + try buildProductsPath(for: parameters).appending(parameters.binaryRelativePath(for: product)) + } } public struct SymbolGraphResult { @@ -156,13 +185,17 @@ public protocol ProductBuildDescription { /// The build parameters. var buildParameters: BuildParameters { get } + + /// The build products directory — the path under which built binaries, libraries, and other + /// products are placed for the corresponding build system/parameters. + var productsPath: AbsolutePath { get } } extension ProductBuildDescription { /// The path to the product binary produced. public var binaryPath: AbsolutePath { get throws { - try self.buildParameters.binaryPath(for: product) + try self.productsPath.appending(self.buildParameters.binaryRelativePath(for: product)) } } } diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index bb4d90a4c97..e9d1d8f4ade 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -83,7 +83,11 @@ package struct PIFBuilderParameters { /// the build products to a different location. let addLocalRpaths: Bool - package init(isPackageAccessModifierSupported: Bool, enableTestability: Bool, shouldCreateDylibForDynamicProducts: Bool, materializeStaticArchiveProductsForRootPackages: Bool, createDynamicVariantsForLibraryProducts: Bool, toolchainLibDir: AbsolutePath, pkgConfigDirectories: [AbsolutePath], supportedSwiftVersions: [SwiftLanguageVersion], pluginScriptRunner: PluginScriptRunner, disableSandbox: Bool, pluginWorkingDirectory: AbsolutePath, additionalFileRules: [FileRuleDescription], addLocalRPaths: Bool) { + /// The directory in which host-destination build products (e.g. plugin tools) are placed, as + /// reported by the build system for the host `BuildParameters`. + let hostBuildProductsPath: AbsolutePath + + package init(isPackageAccessModifierSupported: Bool, enableTestability: Bool, shouldCreateDylibForDynamicProducts: Bool, materializeStaticArchiveProductsForRootPackages: Bool, createDynamicVariantsForLibraryProducts: Bool, toolchainLibDir: AbsolutePath, pkgConfigDirectories: [AbsolutePath], supportedSwiftVersions: [SwiftLanguageVersion], pluginScriptRunner: PluginScriptRunner, disableSandbox: Bool, pluginWorkingDirectory: AbsolutePath, additionalFileRules: [FileRuleDescription], addLocalRPaths: Bool, hostBuildProductsPath: AbsolutePath) { self.isPackageAccessModifierSupported = isPackageAccessModifierSupported self.enableTestability = enableTestability self.shouldCreateDylibForDynamicProducts = shouldCreateDylibForDynamicProducts @@ -97,6 +101,7 @@ package struct PIFBuilderParameters { self.pluginWorkingDirectory = pluginWorkingDirectory self.additionalFileRules = additionalFileRules self.addLocalRpaths = addLocalRPaths + self.hostBuildProductsPath = hostBuildProductsPath } } @@ -156,8 +161,7 @@ public final class PIFBuilder { prettyPrint: Bool = true, preservePIFModelStructure: Bool = false, printPIFManifestGraphviz: Bool = false, - buildParameters: BuildParameters, - hostBuildParameters: BuildParameters + buildParameters: BuildParameters ) async throws -> PIFGenerationResult { let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder() @@ -165,7 +169,7 @@ public final class PIFBuilder { encoder.userInfo[.encodeForSwiftBuild] = true } - let (topLevelObject, modulesAndProducts) = try await self.constructPIF(buildParameters: buildParameters, hostBuildParameters: hostBuildParameters) + let (topLevelObject, modulesAndProducts) = try await self.constructPIF(buildParameters: buildParameters) // Sign the PIF objects before encoding it for Swift Build. try PIF.sign(workspace: topLevelObject.workspace) @@ -192,7 +196,6 @@ public final class PIFBuilder { private func availableBuildPluginTools( graph: ModulesGraph, buildParameters: BuildParameters, - hostBuildParameters: BuildParameters, pluginsPerModule: [ResolvedModule.ID: [ResolvedModule]], hostTriple: Basics.Triple ) async throws -> [ResolvedModule.ID: [String: PluginTool]] { @@ -207,7 +210,7 @@ public final class PIFBuilder { environment: buildParameters.buildEnvironment, for: hostTriple ) { name, path in - return hostBuildParameters.buildPath.appending(path) + return self.parameters.hostBuildProductsPath.appending(path) } accessibleToolsPerPlugin[plugin.id] = accessibleTools @@ -220,8 +223,7 @@ public final class PIFBuilder { /// Constructs all `PackagePIFBuilder` objects used by the `constructPIF` function. /// In particular, this is useful for unit testing the complex `PIFBuilder` class. func makePIFBuilders( - buildParameters: BuildParameters, - hostBuildParameters: BuildParameters + buildParameters: BuildParameters ) async throws -> [(ResolvedPackage, PackagePIFBuilder, any PackagePIFBuilder.BuildDelegate)] { let pluginScriptRunner = self.parameters.pluginScriptRunner let outputDir = self.parameters.pluginWorkingDirectory.appending("outputs") @@ -233,7 +235,6 @@ public final class PIFBuilder { let availablePluginTools = try await availableBuildPluginTools( graph: graph, buildParameters: buildParameters, - hostBuildParameters: hostBuildParameters, pluginsPerModule: pluginsPerModule, hostTriple: try pluginScriptRunner.hostTriple ) @@ -452,8 +453,7 @@ public final class PIFBuilder { /// Constructs a `PIF.TopLevelObject` representing the package graph. package func constructPIF( - buildParameters: BuildParameters, - hostBuildParameters: BuildParameters + buildParameters: BuildParameters ) async throws -> (PIF.TopLevelObject, [PackagePIFBuilder.ModuleOrProduct]) { return try await memoize(to: &self.cachedPIF) { let rootPackages = self.graph.rootPackages @@ -461,7 +461,7 @@ public final class PIFBuilder { throw PIFGenerationError.rootPackageNotFound } - let packagesAndPIFBuilders = try await makePIFBuilders(buildParameters: buildParameters, hostBuildParameters: hostBuildParameters) + let packagesAndPIFBuilders = try await makePIFBuilders(buildParameters: buildParameters) var modulesAndProducts: [PackagePIFBuilder.ModuleOrProduct] = [] let packagesAndPIFProjects = try packagesAndPIFBuilders.map { (package, pifBuilder, _) in @@ -554,7 +554,6 @@ public final class PIFBuilder { // Convenience method for generating PIF. public static func generatePIF( buildParameters: BuildParameters, - hostBuildParameters: BuildParameters, packageGraph: ModulesGraph, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -566,7 +565,8 @@ public final class PIFBuilder { additionalFileRules: [FileRuleDescription], addLocalRpaths: Bool, materializeStaticArchiveProductsForRootPackages: Bool, - createDynamicVariantsForLibraryProducts: Bool + createDynamicVariantsForLibraryProducts: Bool, + hostBuildProductsPath: AbsolutePath ) async throws -> PIFGenerationResult { let parameters = PIFBuilderParameters( buildParameters, @@ -577,8 +577,8 @@ public final class PIFBuilder { additionalFileRules: additionalFileRules, addLocalRpaths: addLocalRpaths, materializeStaticArchiveProductsForRootPackages: materializeStaticArchiveProductsForRootPackages, - createDynamicVariantsForLibraryProducts: createDynamicVariantsForLibraryProducts - + createDynamicVariantsForLibraryProducts: createDynamicVariantsForLibraryProducts, + hostBuildProductsPath: hostBuildProductsPath ) let builder = Self( graph: packageGraph, @@ -586,7 +586,7 @@ public final class PIFBuilder { fileSystem: fileSystem, observabilityScope: observabilityScope ) - return try await builder.generatePIF(preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters, hostBuildParameters: hostBuildParameters) + return try await builder.generatePIF(preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters) } } @@ -848,7 +848,8 @@ extension PIFBuilderParameters { additionalFileRules: [FileRuleDescription], addLocalRpaths: Bool, materializeStaticArchiveProductsForRootPackages: Bool, - createDynamicVariantsForLibraryProducts: Bool + createDynamicVariantsForLibraryProducts: Bool, + hostBuildProductsPath: AbsolutePath ) { self.init( isPackageAccessModifierSupported: buildParameters.driverParameters.isPackageAccessModifierSupported, @@ -864,6 +865,7 @@ extension PIFBuilderParameters { pluginWorkingDirectory: pluginWorkingDirectory, additionalFileRules: additionalFileRules, addLocalRPaths: addLocalRpaths, + hostBuildProductsPath: hostBuildProductsPath ) } } diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index f53ece010ce..10950012efa 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -274,8 +274,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { for package in graph.rootPackages { for product in package.products where product.type == .test { - let binaryPath = try buildParameters.binaryPath(for: product) - let coverageBinaryPath = try buildParameters.buildPath.appending( + let binaryPath = try self.binaryPath(for: product, parameters: buildParameters) + let coverageBinaryPath = try self.buildProductsPath(for: buildParameters).appending( buildParameters.testCoverageBinaryRelativePath(forTestProductName: product.name) ) builtProducts.append( @@ -307,6 +307,20 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { public var hasIntegratedAPIDigesterSupport: Bool { true } + public func buildProductsPath(for parameters: BuildParameters) -> Basics.AbsolutePath { + var configDir: String = parameters.configuration.dirname.capitalized + if parameters.triple.isMacOSX { + // no suffix + } else if parameters.triple.isAndroid() { + configDir += "-android" + } else if parameters.triple.isWasm { + configDir += "-webassembly" + } else { + configDir += "-" + (parameters.triple.darwinPlatform?.platformName ?? parameters.triple.osNameUnversioned) + } + return parameters.dataPath.appending(components: "Products", configDir) + } + public var enableTaskBacktraces: Bool { self.buildParameters.outputParameters.enableTaskBacktraces } @@ -414,10 +428,10 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { ) defer { - if self.fileSystem.exists(self.buildParameters.buildPath, followSymlink: true) { + if self.fileSystem.exists(self.buildProductsPath(for: self.buildParameters), followSymlink: true) { createBuildSymbolicLinks( self.scratchDirectory.appending(component: self.buildParameters.configuration.dirname), - pointingAt: self.buildParameters.buildPath, + pointingAt: self.buildProductsPath(for: self.buildParameters), fileSystem: self.fileSystem, observabilityScope: self.observabilityScope, ) @@ -982,7 +996,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { case .on: for setting in indexStoreSettingNames { settings[setting.enableVariableName] = "YES" - settings[setting.pathVariable] = self.buildParameters.indexStore.pathStringWithPosixSlashes + settings[setting.pathVariable] = self.indexStore(for: self.buildParameters).pathStringWithPosixSlashes } case .off: for setting in indexStoreSettingNames { @@ -1303,6 +1317,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { addLocalRpaths: !self.buildParameters.linkingParameters.shouldDisableLocalRpath, materializeStaticArchiveProductsForRootPackages: materializeStaticArchiveProductsForRootPackages, createDynamicVariantsForLibraryProducts: false, + hostBuildProductsPath: self.buildProductsPath(for: self.hostBuildParameters) ), fileSystem: self.fileSystem, observabilityScope: self.observabilityScope, @@ -1318,8 +1333,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { return try await pifBuilder.generatePIF( preservePIFModelStructure: preserveStructure, printPIFManifestGraphviz: buildParameters.printPIFManifestGraphviz, - buildParameters: buildParameters, - hostBuildParameters: hostBuildParameters + buildParameters: buildParameters ) } diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index cad9aec5c2e..ba13cd7e072 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -54,7 +54,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { for package in graph.rootPackages { for product in package.products where product.type == .test { - let binaryPath = try buildParameters.binaryPath(for: product) + let binaryPath = try self.binaryPath(for: product, parameters: buildParameters) builtProducts.append( BuiltTestProduct( productName: product.name, @@ -83,6 +83,20 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { public var hasIntegratedAPIDigesterSupport: Bool { false } + public func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath { + var configDir: String = parameters.configuration.dirname.capitalized + if parameters.triple.isMacOSX { + // no suffix + } else if parameters.triple.isAndroid() { + configDir += "-android" + } else if parameters.triple.isWasm { + configDir += "-webassembly" + } else { + configDir += "-" + (parameters.triple.darwinPlatform?.platformName ?? parameters.triple.osNameUnversioned) + } + return parameters.dataPath.appending(components: "Products", configDir) + } + public init( buildParameters: BuildParameters, packageGraphLoader: @escaping () async throws -> ModulesGraph, diff --git a/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift b/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift index 04ff1c2a30a..6b3e6255959 100644 --- a/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift +++ b/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift @@ -156,7 +156,7 @@ package func mockPluginTools( environment: buildParameters.buildEnvironment, for: hostTriple ) { name, path in - buildParameters.buildPath.appending(path) + BuildOperation.buildProductsPath(for: buildParameters).appending(path) } accessibleToolsPerPlugin[plugin.id] = accessibleTools diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 7ff0d79d099..41c2de13b88 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -219,7 +219,20 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { shouldDisableLocalRpath: self.shouldDisableLocalRpath, logLevel: self.logLevel ) - return print(parameters.buildPath.description) + let buildSystem = try builder.createBuildSystem( + packagePath: packagePath, + scratchDirectory: scratchDirectory, + buildSystem: self.buildSystem, + configuration: self.configuration, + architectures: self.architectures, + buildFlags: self.buildFlags, + manifestBuildFlags: self.manifestFlags, + useIntegratedSwiftDriver: self.useIntegratedSwiftDriver, + explicitTargetDependencyImportCheck: self.explicitTargetDependencyImportCheck, + shouldDisableLocalRpath: self.shouldDisableLocalRpath, + logLevel: self.logLevel + ) + return print(buildSystem.buildProductsPath(for: parameters).description) } try await builder.build( diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6bfe98cab38..15be5a4dbaa 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -3867,7 +3867,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { )) let lib = try result.moduleBuildDescription(for: "lib").clang() - let path = StringPattern.equal(result.plan.destinationBuildParameters.indexStore.pathString) + let path = StringPattern.equal(BuildOperation.indexStore(for: result.plan.destinationBuildParameters).pathString) XCTAssertMatch( try lib.basicArguments(isCXX: false), diff --git a/Tests/SwiftBuildSupportTests/CGenPIFTests.swift b/Tests/SwiftBuildSupportTests/CGenPIFTests.swift index 049428c0ed9..f06b400adaa 100644 --- a/Tests/SwiftBuildSupportTests/CGenPIFTests.swift +++ b/Tests/SwiftBuildSupportTests/CGenPIFTests.swift @@ -204,10 +204,6 @@ import SwiftBuild buildParameters: mockBuildParameters( destination: .host, buildSystemKind: .swiftbuild, - ), - hostBuildParameters: mockBuildParameters( - destination: .host, - buildSystemKind: .swiftbuild, ) ).0 } diff --git a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift index d27ac0ef3f7..eec6dd013f6 100644 --- a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift @@ -30,7 +30,8 @@ extension PIFBuilderParameters { temporaryDirectory: Basics.AbsolutePath, addLocalRpaths: Bool, shouldCreateDylibForDynamicProducts: Bool = false, - pluginScriptRunner: PluginScriptRunner? = nil + pluginScriptRunner: PluginScriptRunner? = nil, + hostBuildProductsPath: Basics.AbsolutePath? = nil ) throws -> Self { try self.init( isPackageAccessModifierSupported: true, @@ -49,7 +50,8 @@ extension PIFBuilderParameters { disableSandbox: false, pluginWorkingDirectory: temporaryDirectory.appending(component: "plugin-working-dir"), additionalFileRules: [], - addLocalRPaths: addLocalRpaths + addLocalRPaths: addLocalRpaths, + hostBuildProductsPath: hostBuildProductsPath ?? temporaryDirectory.appending(component: "host-build-products") ) } } @@ -59,7 +61,7 @@ fileprivate func withGeneratedPIF( addLocalRpaths: Bool = true, shouldCreateDylibForDynamicProducts: Bool = true, buildParameters: BuildParameters? = nil, - hostBuildParameters: BuildParameters? = nil, + hostBuildProductsPath: AbsolutePath? = nil, do doIt: (SwiftBuildSupport.PIF.TopLevelObject, TestingObservability) async throws -> () ) async throws { let buildParameters = if let buildParameters { @@ -67,11 +69,6 @@ fileprivate func withGeneratedPIF( } else { mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) } - let hostBuildParameters = if let hostBuildParameters { - hostBuildParameters - } else { - mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) - } try await fixture(name: fixtureName) { fixturePath in let observabilitySystem: TestingObservability = ObservabilitySystem.makeForTesting(verbose: false) let toolchain = try UserToolchain.default @@ -94,14 +91,14 @@ fileprivate func withGeneratedPIF( parameters: try PIFBuilderParameters.constructDefaultParametersForTesting( temporaryDirectory: fixturePath, addLocalRpaths: addLocalRpaths, - shouldCreateDylibForDynamicProducts: shouldCreateDylibForDynamicProducts + shouldCreateDylibForDynamicProducts: shouldCreateDylibForDynamicProducts, + hostBuildProductsPath: hostBuildProductsPath ), fileSystem: localFileSystem, observabilityScope: observabilitySystem.topScope ) let (pif, _) = try await builder.constructPIF( - buildParameters: buildParameters, - hostBuildParameters: hostBuildParameters + buildParameters: buildParameters ) try await doIt(pif, observabilitySystem) } @@ -392,8 +389,7 @@ struct PIFBuilderTests { // Act let (pif, _) = try await pifBuilder.constructPIF( - buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild), - hostBuildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) ) // Assert @@ -511,11 +507,6 @@ struct PIFBuilderTests { @Test func buildToolPluginCommandLineUsesHostBuildPath() async throws { let hostBuildPath = AbsolutePath("/path/to/host/build") let destBuildPath = AbsolutePath("/path/to/dest/build") - let hostBuildParams = mockBuildParameters( - destination: .host, - buildPath: hostBuildPath, - buildSystemKind: .swiftbuild - ) let destBuildParams = mockBuildParameters( destination: .host, buildPath: destBuildPath, @@ -525,7 +516,7 @@ struct PIFBuilderTests { try await withGeneratedPIF( fromFixture: "Miscellaneous/Plugins/MySourceGenPlugin", buildParameters: destBuildParams, - hostBuildParameters: hostBuildParams + hostBuildProductsPath: hostBuildPath ) { pif, observabilitySystem in let project = try pif.workspace.project(named: "MySourceGenPlugin") let target = try project.target(named: "MyLocalTool-product") @@ -827,8 +818,7 @@ struct PIFBuilderTests { observabilityScope: observability.topScope ) let (pif, _) = try await pifBuilder.constructPIF( - buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild), - hostBuildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) ) let remoteProject = try pif.workspace.project(named: "remote-pkg") @@ -966,8 +956,7 @@ struct PIFBuilderTests { ) let (pif, _) = try await pifBuilder.constructPIF( - buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild), - hostBuildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) ) let project = try pif.workspace.project(named: "Root") @@ -1060,8 +1049,7 @@ struct PIFBuilderTests { ) let (pif, _) = try await pifBuilder.constructPIF( - buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild), - hostBuildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) ) let project = try pif.workspace.project(named: "Pkg") diff --git a/Tests/SwiftBuildSupportTests/PrebuiltsPIFTests.swift b/Tests/SwiftBuildSupportTests/PrebuiltsPIFTests.swift index 68e10be6817..5b5765414ca 100644 --- a/Tests/SwiftBuildSupportTests/PrebuiltsPIFTests.swift +++ b/Tests/SwiftBuildSupportTests/PrebuiltsPIFTests.swift @@ -176,8 +176,7 @@ struct PrebuiltsPIFTests { observabilityScope: observability.topScope ) let (pif, _) = try await pifBuilder.constructPIF( - buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild), - hostBuildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) ) let hostTargets = Set([ @@ -433,8 +432,7 @@ struct PrebuiltsPIFTests { observabilityScope: observability.topScope ) let (pif, _) = try await pifBuilder.constructPIF( - buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild), - hostBuildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + buildParameters: mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) ) let targets = pif.workspace.projects.flatMap({ $0.underlying.targets }) diff --git a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift index 306a56a87c3..d5e8f2f1b32 100644 --- a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift +++ b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift @@ -317,7 +317,7 @@ struct SwiftBuildSystemTests { case .auto: nil } let expectedPathValue: AbsolutePath? = switch indexStoreSettingUT { - case .on: buildParameters.indexStore + case .on: swiftBuild.indexStore(for: buildParameters) case .off: nil case .auto: nil } From 02bd598fc1a583b5d76733905887f4d03c87d2a6 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 12 May 2026 17:21:57 -0700 Subject: [PATCH 05/11] Update SwiftBuildSystem to query the underlying build system for the built products dir suffix --- Sources/Build/BuildOperation.swift | 8 +- .../PackageCommands/DumpCommands.swift | 2 +- .../PackageCommands/GenerateSBOM.swift | 2 +- .../Commands/PackageCommands/Install.swift | 2 +- .../PackageCommands/PluginCommand.swift | 2 +- .../Commands/Snippets/Cards/SnippetCard.swift | 2 +- Sources/Commands/SwiftBuildCommand.swift | 4 +- Sources/Commands/SwiftRunCommand.swift | 6 +- Sources/Commands/SwiftTestCommand.swift | 48 ++++----- .../Commands/Utilities/PluginDelegate.swift | 13 +-- .../Commands/Utilities/TestingSupport.swift | 97 ++++++++++--------- .../BuildSystem/BuildSystem.swift | 22 ++--- .../SwiftBuildSupport/SwiftBuildSystem.swift | 41 ++++---- Sources/XCBuildSupport/XcodeBuildSystem.swift | 4 +- Sources/swift-bootstrap/main.swift | 2 +- .../SwiftBuildSystemTests.swift | 2 +- 16 files changed, 133 insertions(+), 124 deletions(-) diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 5cd76e3aec4..18b6d851390 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -193,7 +193,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS public var hasIntegratedAPIDigesterSupport: Bool { false } - public func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath { + public func buildProductsPath(for parameters: BuildParameters) async throws -> AbsolutePath { Self.buildProductsPath(for: parameters) } @@ -555,7 +555,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS do { try self.fileSystem.createSymbolicLink( oldBuildPath, - pointingAt: self.buildProductsPath(for: self.config.destinationBuildParameters), + pointingAt: Self.buildProductsPath(for: self.config.destinationBuildParameters), relative: true ) } catch { @@ -1015,7 +1015,7 @@ extension BuildOperation { // `buildPackageStructure` to recognize the split. config.databasePath = config.scratchDirectory.appending("plugin-tools.db") - config.buildDescriptionPath = self.buildProductsPath(for: self.config.toolsBuildParameters).appending( + config.buildDescriptionPath = Self.buildProductsPath(for: self.config.toolsBuildParameters).appending( component: "plugin-tools-description.json" ) @@ -1065,7 +1065,7 @@ extension BuildOperation { if let result = try await buildToolBuilder(name, path) { return result } else { - return self.buildProductsPath(for: config.toolsBuildParameters).appending(path) + return Self.buildProductsPath(for: config.toolsBuildParameters).appending(path) } } diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index b59ae552afb..78d4665fbe9 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -86,7 +86,7 @@ struct DumpSymbolGraph: AsyncSwiftCommand { if let symbolGraph = buildResult.symbolGraph { // The build system produced symbol graphs for us, one for each target. - let buildPath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + let buildPath = try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) // Copy the symbol graphs from the target-specific locations to the single output directory for rootPackage in try await buildSystem.getPackageGraph().rootPackages { diff --git a/Sources/Commands/PackageCommands/GenerateSBOM.swift b/Sources/Commands/PackageCommands/GenerateSBOM.swift index 6440bbee199..c57e364e9aa 100644 --- a/Sources/Commands/PackageCommands/GenerateSBOM.swift +++ b/Sources/Commands/PackageCommands/GenerateSBOM.swift @@ -58,7 +58,7 @@ extension SwiftPackageCommand { filter: try self.sbom.sbomFilter, product: self.product, specs: specs.isEmpty ? Spec.allCases : specs, - dir: await SBOMCreator.resolveSBOMDirectory(from: self.sbom.sbomDirectory, withDefault: try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters)), + dir: await SBOMCreator.resolveSBOMDirectory(from: self.sbom.sbomDirectory, withDefault: try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters)), observabilityScope: swiftCommandState.observabilityScope ) diff --git a/Sources/Commands/PackageCommands/Install.swift b/Sources/Commands/PackageCommands/Install.swift index ea6533edd00..e9f1f5c6d49 100644 --- a/Sources/Commands/PackageCommands/Install.swift +++ b/Sources/Commands/PackageCommands/Install.swift @@ -92,7 +92,7 @@ extension SwiftPackageCommand { let buildSystem = try await commandState.createBuildSystem(explicitProduct: productToInstall.name) try await buildSystem.build(subset: .product(productToInstall.name), buildOutputs: []) - let binPath = try buildSystem.buildProductsPath(for: commandState.productsBuildParameters) + let binPath = try await buildSystem.buildProductsPath(for: commandState.productsBuildParameters) .appending(component: productToInstall.name) let finalBinPath = swiftpmBinDir.appending(component: binPath.basename) try commandState.fileSystem.copy(from: binPath, to: finalBinPath) diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index 21421758687..9d75f055fb1 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -359,7 +359,7 @@ struct PluginCommand: AsyncSwiftCommand { return nil } } else { - return buildSystem.buildProductsPath(for: buildParameters).appending(path) + return try await buildSystem.buildProductsPath(for: buildParameters).appending(path) } } diff --git a/Sources/Commands/Snippets/Cards/SnippetCard.swift b/Sources/Commands/Snippets/Cards/SnippetCard.swift index 750c28f9eb3..aca2e2d916b 100644 --- a/Sources/Commands/Snippets/Cards/SnippetCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetCard.swift @@ -114,7 +114,7 @@ struct SnippetCard: Card { print("Building '\(snippet.path)'\n") let buildSystem = try await swiftCommandState.createBuildSystem(explicitProduct: snippet.name) try await buildSystem.build(subset: .product(snippet.name), buildOutputs: []) - let executablePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + let executablePath = try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) .appending(component: snippet.name) if let exampleTarget = try await buildSystem.getPackageGraph().module(for: snippet.name) { try ProcessEnv.chdir(exampleTarget.sources.paths[0].parentDirectory) diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 93fa1f0403a..42a47787da0 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -141,7 +141,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { public func run(_ swiftCommandState: SwiftCommandState) async throws { if options.shouldPrintBinPath { let buildSystem = try await swiftCommandState.createBuildSystem() - return try print(buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters).description) + return try await print(buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters).description) } if options.printManifestGraphviz { @@ -257,7 +257,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { filter: try self.options.sbom.sbomFilter, product: options.product, specs: try self.options.sbom.sbomSpecs, - dir: await SBOMCreator.resolveSBOMDirectory(from: self.options.sbom.sbomDirectory, withDefault: try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters)), + dir: await SBOMCreator.resolveSBOMDirectory(from: self.options.sbom.sbomDirectory, withDefault: try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters)), observabilityScope: swiftCommandState.observabilityScope ) diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index b0a76265d01..49f63ed57ce 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -169,7 +169,7 @@ public struct SwiftRunCommand: AsyncSwiftCommand { } let productRelativePath = try swiftCommandState.productsBuildParameters.executablePath(for: productName) - let productAbsolutePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + let productAbsolutePath = try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) .appending(productRelativePath) // Make sure we are running from the original working directory. @@ -225,11 +225,11 @@ public struct SwiftRunCommand: AsyncSwiftCommand { try await buildSystem.build(subset: .product(productName), buildOutputs: []) } - let executablePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + let executablePath = try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) .appending(component: productName) let productRelativePath = try swiftCommandState.productsBuildParameters.executablePath(for: productName) - let productAbsolutePath = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + let productAbsolutePath = try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) .appending(productRelativePath) let runnerPath: AbsolutePath diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index 40eb250ccce..2f03daca4d1 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -312,7 +312,8 @@ public struct SwiftTestCommand: AsyncSwiftCommand { private func run(_ swiftCommandState: SwiftCommandState, buildParameters: BuildParameters, testProducts: [BuiltTestProduct], buildSystem: any BuildSystem) async throws { // Remove test output from prior runs and validate priors. if self.options.enableExperimentalTestOutput && buildParameters.triple.supportsTestSummary { - _ = try? localFileSystem.removeFileTree(buildSystem.testOutputPath(for: buildParameters)) + let testOutputPath = try await buildSystem.testOutputPath(for: buildParameters) + _ = try? localFileSystem.removeFileTree(testOutputPath) } var results = [TestProductResult]() @@ -334,7 +335,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { } if !self.options.shouldRunInParallel { - let (xctestArgs, testCount, testPaths) = try xctestArgs(for: testProducts, swiftCommandState: swiftCommandState, buildSystem: buildSystem) + let (xctestArgs, testCount, testPaths) = try await xctestArgs(for: testProducts, swiftCommandState: swiftCommandState, buildSystem: buildSystem) // Tests have been filtered and/or skipped; assure that we only run test products // of the tests we must run. @@ -358,7 +359,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { results.append(contentsOf: productResults) } } else { - let testSuites = try TestingSupport.getTestSuites( + let testSuites = try await TestingSupport.getTestSuites( in: testProducts, swiftCommandState: swiftCommandState, enableCodeCoverage: options.enableCodeCoverage, @@ -398,7 +399,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { buildSystem: buildSystem ) - testResults = try runner.run(tests) + testResults = try await runner.run(tests) // Report a result for each product that had tests run. let failedTestProducts = Set(testResults.filter({ !$0.success }).map(\.unitTest.testProduct)) @@ -421,7 +422,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { lazy var testEntryPointPath = testProducts.lazy.compactMap(\.testEntryPointPath).first if options.testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) || testEntryPointPath == nil { // Filter test products to swift testing suites. - let testSuites = try TestingSupport.getSwiftTestingSuites( + let testSuites = try await TestingSupport.getSwiftTestingSuites( in: testProducts, swiftCommandState: swiftCommandState, shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, @@ -477,14 +478,14 @@ public struct SwiftTestCommand: AsyncSwiftCommand { case .failure: swiftCommandState.executionStatus = .failure if self.options.enableExperimentalTestOutput { - try Self.handleTestOutput(productsBuildParameters: buildParameters, packagePath: testProducts[0].packagePath, buildSystem: buildSystem) + try await Self.handleTestOutput(productsBuildParameters: buildParameters, packagePath: testProducts[0].packagePath, buildSystem: buildSystem) } case .noMatchingTests: swiftCommandState.observabilityScope.emit(.noMatchingTests) } } - private func xctestArgs(for testProducts: [BuiltTestProduct], swiftCommandState: SwiftCommandState, buildSystem: any BuildSystem) throws -> (arguments: [String], testCount: Int?, testPaths: Set?) { + private func xctestArgs(for testProducts: [BuiltTestProduct], swiftCommandState: SwiftCommandState, buildSystem: any BuildSystem) async throws -> (arguments: [String], testCount: Int?, testPaths: Set?) { switch options.testCaseSpecifier { case .none: if case .skip = options.skippedTests(fileSystem: swiftCommandState.fileSystem) { @@ -500,7 +501,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { } // Find the tests we need to run. - let testSuites = try TestingSupport.getTestSuites( + let testSuites = try await TestingSupport.getTestSuites( in: testProducts, swiftCommandState: swiftCommandState, enableCodeCoverage: options.enableCodeCoverage, @@ -560,7 +561,7 @@ public 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(buildSystem.codeCovPath(for: productsBuildParameters)) + try swiftCommandState.fileSystem.removeFileTree(try await buildSystem.codeCovPath(for: productsBuildParameters)) } try await run(swiftCommandState, buildParameters: productsBuildParameters, testProducts: testProducts, buildSystem: buildSystem) @@ -630,7 +631,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { let toolchain = try swiftCommandState.getTargetToolchain() let toolsVersion = try await swiftCommandState.getToolsVersion() - let testEnv = try TestingSupport.constructTestEnvironment( + let testEnv = try await TestingSupport.constructTestEnvironment( toolchain: toolchain, destinationBuildParameters: productsBuildParameters, sanitizers: globalOptions.build.sanitizers, @@ -658,13 +659,14 @@ public struct SwiftTestCommand: AsyncSwiftCommand { }) } - private static func handleTestOutput(productsBuildParameters: BuildParameters, packagePath: AbsolutePath, buildSystem: any BuildSystem) throws { - guard localFileSystem.exists(buildSystem.testOutputPath(for: productsBuildParameters)) else { + private static func handleTestOutput(productsBuildParameters: BuildParameters, packagePath: AbsolutePath, buildSystem: any BuildSystem) async throws { + let testOutputPath = try await buildSystem.testOutputPath(for: productsBuildParameters) + guard localFileSystem.exists(testOutputPath) else { print("No existing test output found.") return } - let lines = try String(contentsOfFile: buildSystem.testOutputPath(for: productsBuildParameters).pathString).components(separatedBy: "\n") + let lines = try String(contentsOfFile: testOutputPath.pathString).components(separatedBy: "\n") let events = try lines.map { try JSONDecoder().decode(TestEventRecord.self, from: $0) } let caseEvents = events.compactMap { $0.caseEvent } @@ -711,7 +713,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options) for product in testProducts { // Export the codecov data as JSON. - let jsonPath = buildSystem.codeCovPath(for: productsBuildParameters).appending(component: rootManifest.displayName + ".json") + let jsonPath = try await buildSystem.codeCovPath(for: productsBuildParameters).appending(component: rootManifest.displayName + ".json") try await exportCodeCovAsJSON( to: jsonPath, testBinary: product.coverageBinaryPath, @@ -728,7 +730,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { // Get the profraw files. let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options) - let codeCovPath = buildSystem.codeCovPath(for: productsBuildParameters) + let codeCovPath = try await buildSystem.codeCovPath(for: productsBuildParameters) let codeCovFiles = try swiftCommandState.fileSystem.getDirectoryContents(codeCovPath) // Construct arguments for invoking the llvm-prof tool. @@ -739,7 +741,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { args.append(filePath.pathString) } } - args += ["-o", buildSystem.codeCovDataFile(for: productsBuildParameters).pathString] + args += ["-o", try await buildSystem.codeCovDataFile(for: productsBuildParameters).pathString] try await AsyncProcess.checkNonZeroExit(arguments: args) } @@ -761,7 +763,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { let args = [ llvmCov.pathString, "export", - "-instr-profile=\(buildSystem.codeCovDataFile(for: productsBuildParameters))", + "-instr-profile=\(try await buildSystem.codeCovDataFile(for: productsBuildParameters))", ] + archArgs + [ testBinary.pathString, ] @@ -833,7 +835,7 @@ extension SwiftTestCommand { } let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(enableCodeCoverage: true) let buildSystem = try await swiftCommandState.createBuildSystem() - print(buildSystem.codeCovPath(for: productsBuildParameters).appending(component: rootManifest.displayName + ".json")) + print(try await buildSystem.codeCovPath(for: productsBuildParameters).appending(component: rootManifest.displayName + ".json")) } } @@ -859,7 +861,7 @@ extension SwiftTestCommand { func run(_ swiftCommandState: SwiftCommandState) async throws { let buildSystem = try await swiftCommandState.createBuildSystem() - try SwiftTestCommand.handleTestOutput( + try await SwiftTestCommand.handleTestOutput( productsBuildParameters: try swiftCommandState.productsBuildParameters, packagePath: localFileSystem.currentWorkingDirectory ?? .root, // by definition runs in the current working directory buildSystem: buildSystem @@ -920,7 +922,7 @@ extension SwiftTestCommand { let toolchain = try swiftCommandState.getTargetToolchain() let toolsVersion = try await swiftCommandState.getToolsVersion() - let testEnv = try TestingSupport.constructTestEnvironment( + let testEnv = try await TestingSupport.constructTestEnvironment( toolchain: toolchain, destinationBuildParameters: productsBuildParameters, sanitizers: globalOptions.build.sanitizers, @@ -931,7 +933,7 @@ extension SwiftTestCommand { ) if testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) { - let testSuites = try TestingSupport.getTestSuites( + let testSuites = try await TestingSupport.getTestSuites( in: testProducts, swiftCommandState: swiftCommandState, enableCodeCoverage: false, @@ -1326,10 +1328,10 @@ final class ParallelTestRunner { } /// Executes the tests spawning parallel workers. Blocks calling thread until all workers are finished. - func run(_ tests: [UnitTest]) throws -> [TestResult] { + func run(_ tests: [UnitTest]) async throws -> [TestResult] { assert(!tests.isEmpty, "There should be at least one test to execute.") - let testEnv = try TestingSupport.constructTestEnvironment( + let testEnv = try await TestingSupport.constructTestEnvironment( toolchain: self.toolchain, destinationBuildParameters: self.productsBuildParameters, sanitizers: self.buildOptions.sanitizers, diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index 63170893f59..312acbd4334 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -239,7 +239,7 @@ final class PluginDelegate: PluginInvocationDelegate { // Clean out the code coverage directory that may contain stale `profraw` files from a previous run of // the code coverage tool. if parameters.enableCodeCoverage { - try swiftCommandState.fileSystem.removeFileTree(buildSystem.codeCovPath(for: toolsBuildParameters)) + try swiftCommandState.fileSystem.removeFileTree(try await buildSystem.codeCovPath(for: toolsBuildParameters)) } // Construct the environment we'll pass down to the tests. @@ -259,7 +259,7 @@ final class PluginDelegate: PluginInvocationDelegate { var numFailedTests = 0 for testProduct in await buildSystem.builtTestProducts { // Get the test suites in the bundle. Each is just a container for test cases. - let testSuites = try TestingSupport.getTestSuites( + let testSuites = try await TestingSupport.getTestSuites( fromTestAt: testProduct.bundlePath, swiftCommandState: swiftCommandState, enableCodeCoverage: parameters.enableCodeCoverage, @@ -336,12 +336,13 @@ final class PluginDelegate: PluginInvocationDelegate { let codeCoverageDataFile: AbsolutePath? if parameters.enableCodeCoverage { // Use `llvm-prof` to merge all the `.profraw` files into a single `.profdata` file. - let mergedCovFile = buildSystem.codeCovDataFile(for: toolsBuildParameters) - let codeCovFileNames = try swiftCommandState.fileSystem.getDirectoryContents(buildSystem.codeCovPath(for: toolsBuildParameters)) + let mergedCovFile = try await buildSystem.codeCovDataFile(for: toolsBuildParameters) + let codeCovPath = try await buildSystem.codeCovPath(for: toolsBuildParameters) + let codeCovFileNames = try swiftCommandState.fileSystem.getDirectoryContents(codeCovPath) var llvmProfCommand = [try toolchain.getLLVMProf().pathString] llvmProfCommand += ["merge", "-sparse"] for fileName in codeCovFileNames where fileName.hasSuffix(".profraw") { - let filePath = buildSystem.codeCovPath(for: toolsBuildParameters).appending(component: fileName) + let filePath = codeCovPath.appending(component: fileName) llvmProfCommand.append(filePath.pathString) } llvmProfCommand += ["-o", mergedCovFile.pathString] @@ -408,7 +409,7 @@ final class PluginDelegate: PluginInvocationDelegate { let buildResult = try await buildSystem.build(subset: .target(targetName), buildOutputs: [.symbolGraph(options), .buildPlan]) if let symbolGraph = buildResult.symbolGraph { - let path = try buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) + let path = try await buildSystem.buildProductsPath(for: swiftCommandState.productsBuildParameters) return PluginInvocationSymbolGraphResult(directoryPath: "\(path)/\(symbolGraph.outputLocationForTarget(targetName, try swiftCommandState.productsBuildParameters).joined(separator:"/"))") } else if let buildPlan = buildResult.buildPlan { func lookupDescription( diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index 7e15f46742b..329a3a55d5f 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -80,21 +80,21 @@ enum TestingSupport { experimentalTestOutput: Bool, sanitizers: [Sanitizer], buildSystem: any BuildSystem - ) throws -> [BuiltTestProduct: [TestSuite]] { - let testSuitesByProduct = try testProducts - .map {( - $0, - try Self.getTestSuites( - fromTestAt: $0.bundlePath, - swiftCommandState: swiftCommandState, - enableCodeCoverage: enableCodeCoverage, - shouldSkipBuilding: shouldSkipBuilding, - experimentalTestOutput: experimentalTestOutput, - sanitizers: sanitizers, - buildSystem: buildSystem - ) - )} - return try Dictionary(throwingUniqueKeysWithValues: testSuitesByProduct) + ) async throws -> [BuiltTestProduct: [TestSuite]] { + var result: [(BuiltTestProduct, [TestSuite])] = [] + for product in testProducts { + let suites = try await Self.getTestSuites( + fromTestAt: product.bundlePath, + swiftCommandState: swiftCommandState, + enableCodeCoverage: enableCodeCoverage, + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, + sanitizers: sanitizers, + buildSystem: buildSystem + ) + result.append((product, suites)) + } + return try Dictionary(throwingUniqueKeysWithValues: result) } /// Runs the corresponding tool to get tests JSON and create TestSuite array. @@ -115,25 +115,26 @@ enum TestingSupport { experimentalTestOutput: Bool, sanitizers: [Sanitizer], buildSystem: any BuildSystem - ) throws -> [TestSuite] { + ) async throws -> [TestSuite] { // Run the correct tool. var args = [String]() #if os(macOS) + let env = try await Self.constructTestEnvironment( + toolchain: try swiftCommandState.getTargetToolchain(), + destinationBuildParameters: swiftCommandState.buildParametersForTest( + enableCodeCoverage: enableCodeCoverage, + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput + ).productsBuildParameters, + sanitizers: sanitizers, + library: .xctest, + testProductPaths: [path], + interopMode: nil, // Interop not required when listing tests + buildSystem: buildSystem + ) + let helperPath = try Self.xctestHelperPath(swiftCommandState: swiftCommandState).pathString let data: String = try withTemporaryFile { tempFile in - args = [try Self.xctestHelperPath(swiftCommandState: swiftCommandState).pathString, path.pathString, tempFile.path.pathString] - let env = try Self.constructTestEnvironment( - toolchain: try swiftCommandState.getTargetToolchain(), - destinationBuildParameters: swiftCommandState.buildParametersForTest( - enableCodeCoverage: enableCodeCoverage, - shouldSkipBuilding: shouldSkipBuilding, - experimentalTestOutput: experimentalTestOutput - ).productsBuildParameters, - sanitizers: sanitizers, - library: .xctest, - testProductPaths: [path], - interopMode: nil, // Interop not required when listing tests - buildSystem: buildSystem - ) + args = [helperPath, path.pathString, tempFile.path.pathString] try Self.runProcessWithExistenceCheck( path: path, fileSystem: swiftCommandState.fileSystem, @@ -145,7 +146,7 @@ enum TestingSupport { return try swiftCommandState.fileSystem.readFileContents(AbsolutePath(tempFile.path)) } #else - let env = try Self.constructTestEnvironment( + let env = try await Self.constructTestEnvironment( toolchain: try swiftCommandState.getTargetToolchain(), destinationBuildParameters: swiftCommandState.buildParametersForTest( enableCodeCoverage: enableCodeCoverage, @@ -175,19 +176,19 @@ enum TestingSupport { shouldSkipBuilding: Bool, sanitizers: [Sanitizer], buildSystem: any BuildSystem - ) throws -> [AbsolutePath: [String]] { - let suitesByProduct = try testProducts - .map {( - $0.binaryPath, - try Self.getSwiftTestingSuites( - testProduct: $0, - swiftCommandState: swiftCommandState, - shouldSkipBuilding: shouldSkipBuilding, - sanitizers: sanitizers, - buildSystem: buildSystem - ) - )} - return try Dictionary(throwingUniqueKeysWithValues: suitesByProduct) + ) async throws -> [AbsolutePath: [String]] { + var result: [(AbsolutePath, [String])] = [] + for product in testProducts { + let suites = try await Self.getSwiftTestingSuites( + testProduct: product, + swiftCommandState: swiftCommandState, + shouldSkipBuilding: shouldSkipBuilding, + sanitizers: sanitizers, + buildSystem: buildSystem + ) + result.append((product.binaryPath, suites)) + } + return try Dictionary(throwingUniqueKeysWithValues: result) } /// Runs the test binary to list Swift Testing tests and returns their identifiers. @@ -206,9 +207,9 @@ enum TestingSupport { shouldSkipBuilding: Bool, sanitizers: [Sanitizer], buildSystem: any BuildSystem - ) throws -> [String] { + ) async throws -> [String] { let toolchain = try swiftCommandState.getTargetToolchain() - let env = try Self.constructTestEnvironment( + let env = try await Self.constructTestEnvironment( toolchain: toolchain, destinationBuildParameters: swiftCommandState.buildParametersForTest( // Unlike the XCTest helper tool, the Swift Testing runtime initialises LLVM @@ -311,7 +312,7 @@ enum TestingSupport { testProductPaths: [AbsolutePath], interopMode: String?, buildSystem: any BuildSystem - ) throws -> Environment { + ) async throws -> Environment { var env = Environment.current // If the standard output or error stream is NOT a TTY, set the NO_COLOR @@ -351,7 +352,7 @@ enum TestingSupport { // // These are all merged using `llvm-profdata merge` once the outer test command has // completed. - let codecovProfile = buildSystem.buildProductsPath(for: buildParameters).appending(components: "codecov", "\(library)%m.%p.profraw") + let codecovProfile = try await buildSystem.buildProductsPath(for: buildParameters).appending(components: "codecov", "\(library)%m.%p.profraw") env["LLVM_PROFILE_FILE"] = codecovProfile.pathString } diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index 48b8ed8ad42..f9ac8e11d88 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -99,7 +99,7 @@ public protocol BuildSystem: Cancellable { func generatePIF(preserveStructure: Bool) async throws -> String /// The path to the build directory for the given build parameters. - func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath + func buildProductsPath(for parameters: BuildParameters) async throws -> AbsolutePath } extension BuildSystem { @@ -110,29 +110,29 @@ extension BuildSystem { } /// The path to the index store directory for the given build parameters. - public func indexStore(for parameters: BuildParameters) -> AbsolutePath { + public func indexStore(for parameters: BuildParameters) async throws -> AbsolutePath { assert(parameters.indexStoreMode != .off, "index store is disabled") - return buildProductsPath(for: parameters).appending(components: "index", "store") + return try await buildProductsPath(for: parameters).appending(components: "index", "store") } /// The path to the code coverage directory for the given build parameters. - public func codeCovPath(for parameters: BuildParameters) -> AbsolutePath { - buildProductsPath(for: parameters).appending("codecov") + public func codeCovPath(for parameters: BuildParameters) async throws -> AbsolutePath { + try await buildProductsPath(for: parameters).appending("codecov") } /// The path to the code coverage profdata file for the given build parameters. - public func codeCovDataFile(for parameters: BuildParameters) -> AbsolutePath { - codeCovPath(for: parameters).appending("default.profdata") + public func codeCovDataFile(for parameters: BuildParameters) async throws -> AbsolutePath { + try await codeCovPath(for: parameters).appending("default.profdata") } /// The path to the test output file for the given build parameters. - public func testOutputPath(for parameters: BuildParameters) -> AbsolutePath { - buildProductsPath(for: parameters).appending(component: "testOutput.txt") + public func testOutputPath(for parameters: BuildParameters) async throws -> AbsolutePath { + try await buildProductsPath(for: parameters).appending(component: "testOutput.txt") } /// Returns the path to the binary of a product for the given build parameters. - public func binaryPath(for product: ResolvedProduct, parameters: BuildParameters) throws -> AbsolutePath { - try buildProductsPath(for: parameters).appending(parameters.binaryRelativePath(for: product)) + public func binaryPath(for product: ResolvedProduct, parameters: BuildParameters) async throws -> AbsolutePath { + try await buildProductsPath(for: parameters).appending(parameters.binaryRelativePath(for: product)) } } diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 10950012efa..c0cb644c9b8 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -37,8 +37,7 @@ import Foundation import SWBBuildService import SwiftBuild import enum SWBCore.SwiftAPIDigesterMode -import struct SWBUtil.XcodeVersionInfo -import struct SWBUtil.Path +import SWBUtil struct SessionFailedError: Error { var error: Error @@ -250,6 +249,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { private let logLevel: Basics.Diagnostic.Severity private var packageGraph: AsyncThrowingValueMemoizer = .init() private var pifBuilder: AsyncThrowingValueMemoizer = .init() + private let buildProductsDirectorySuffixCache = AsyncCache() private let fileSystem: FileSystem private let observabilityScope: ObservabilityScope @@ -274,8 +274,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { for package in graph.rootPackages { for product in package.products where product.type == .test { - let binaryPath = try self.binaryPath(for: product, parameters: buildParameters) - let coverageBinaryPath = try self.buildProductsPath(for: buildParameters).appending( + let binaryPath = try await self.binaryPath(for: product, parameters: buildParameters) + let coverageBinaryPath = try await self.buildProductsPath(for: buildParameters).appending( buildParameters.testCoverageBinaryRelativePath(forTestProductName: product.name) ) builtProducts.append( @@ -307,17 +307,21 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { public var hasIntegratedAPIDigesterSupport: Bool { true } - public func buildProductsPath(for parameters: BuildParameters) -> Basics.AbsolutePath { - var configDir: String = parameters.configuration.dirname.capitalized - if parameters.triple.isMacOSX { - // no suffix - } else if parameters.triple.isAndroid() { - configDir += "-android" - } else if parameters.triple.isWasm { - configDir += "-webassembly" - } else { - configDir += "-" + (parameters.triple.darwinPlatform?.platformName ?? parameters.triple.osNameUnversioned) + public func buildProductsPath(for parameters: BuildParameters) async throws -> Basics.AbsolutePath { + let suffix = try await buildProductsDirectorySuffixCache.value(forKey: parameters.triple.tripleString) { + try await withService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint)) { service in + var suffix: String? + try await withSession(service: service, name: "swiftpm-build-products-path", toolchain: parameters.toolchain, packageManagerResourcesDirectory: self.packageManagerResourcesDirectory) { session, _ in + let info = try await session.buildTargetInfo(triple: parameters.triple.tripleString) + suffix = info.buildProductsDirectorySuffix + } + guard let suffix else { + throw StringError("Failed to query build system for build products path suffix") + } + return suffix + } } + let configDir = parameters.configuration.dirname.capitalized + suffix return parameters.dataPath.appending(components: "Products", configDir) } @@ -427,11 +431,12 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { replArguments: nil, ) + let productsPath = try await self.buildProductsPath(for: self.buildParameters) defer { - if self.fileSystem.exists(self.buildProductsPath(for: self.buildParameters), followSymlink: true) { + if self.fileSystem.exists(productsPath, followSymlink: true) { createBuildSymbolicLinks( self.scratchDirectory.appending(component: self.buildParameters.configuration.dirname), - pointingAt: self.buildProductsPath(for: self.buildParameters), + pointingAt: productsPath, fileSystem: self.fileSystem, observabilityScope: self.observabilityScope, ) @@ -996,7 +1001,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { case .on: for setting in indexStoreSettingNames { settings[setting.enableVariableName] = "YES" - settings[setting.pathVariable] = self.indexStore(for: self.buildParameters).pathStringWithPosixSlashes + settings[setting.pathVariable] = try await self.indexStore(for: self.buildParameters).pathStringWithPosixSlashes } case .off: for setting in indexStoreSettingNames { @@ -1317,7 +1322,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { addLocalRpaths: !self.buildParameters.linkingParameters.shouldDisableLocalRpath, materializeStaticArchiveProductsForRootPackages: materializeStaticArchiveProductsForRootPackages, createDynamicVariantsForLibraryProducts: false, - hostBuildProductsPath: self.buildProductsPath(for: self.hostBuildParameters) + hostBuildProductsPath: try await self.buildProductsPath(for: self.hostBuildParameters) ), fileSystem: self.fileSystem, observabilityScope: self.observabilityScope, diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index ba13cd7e072..208c09d357b 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -54,7 +54,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { for package in graph.rootPackages { for product in package.products where product.type == .test { - let binaryPath = try self.binaryPath(for: product, parameters: buildParameters) + let binaryPath = try await self.binaryPath(for: product, parameters: buildParameters) builtProducts.append( BuiltTestProduct( productName: product.name, @@ -83,7 +83,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { public var hasIntegratedAPIDigesterSupport: Bool { false } - public func buildProductsPath(for parameters: BuildParameters) -> AbsolutePath { + public func buildProductsPath(for parameters: BuildParameters) async throws -> AbsolutePath { var configDir: String = parameters.configuration.dirname.capitalized if parameters.triple.isMacOSX { // no suffix diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 41c2de13b88..0ba9c8f4f75 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -232,7 +232,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { shouldDisableLocalRpath: self.shouldDisableLocalRpath, logLevel: self.logLevel ) - return print(buildSystem.buildProductsPath(for: parameters).description) + return try await print(buildSystem.buildProductsPath(for: parameters).description) } try await builder.build( diff --git a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift index d5e8f2f1b32..99bc35ce454 100644 --- a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift +++ b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift @@ -317,7 +317,7 @@ struct SwiftBuildSystemTests { case .auto: nil } let expectedPathValue: AbsolutePath? = switch indexStoreSettingUT { - case .on: swiftBuild.indexStore(for: buildParameters) + case .on: try await swiftBuild.indexStore(for: buildParameters) case .off: nil case .auto: nil } From 3a7466b6411ae401f8f46a54b442745e4d8ed269 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Thu, 30 Apr 2026 18:22:05 -0700 Subject: [PATCH 06/11] Adopt inMemorySwiftSDK run destination API --- .../Consumer/Package.swift | 16 ++++ .../Consumer/Sources/GreeterUser/main.swift | 3 + .../ExternalDependency/Package.swift | 11 +++ .../Sources/Greeter/Greeter.swift | 3 + .../SwiftBuildSupport/SwiftBuildSystem.swift | 17 +++- .../WebAssemblyIntegrationTests.swift | 85 +++++++++++++++++++ 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Package.swift create mode 100644 Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Sources/GreeterUser/main.swift create mode 100644 Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Package.swift create mode 100644 Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Sources/Greeter/Greeter.swift diff --git a/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Package.swift b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Package.swift new file mode 100644 index 00000000000..ac81d5834c4 --- /dev/null +++ b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Package.swift @@ -0,0 +1,16 @@ +// swift-tools-version: 6.0 +import PackageDescription +let package = Package( + name: "GreeterUser", + targets: [ + .executableTarget( + name: "GreeterUser", + linkerSettings: [ + // `Greeter` is not declared as a target dependency; its module and static archive + // are resolved through the SDK's include/library search paths (populated via + // `swift sdk configure` in the enclosing test). + .linkedLibrary("Greeter"), + ] + ), + ] +) diff --git a/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Sources/GreeterUser/main.swift b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Sources/GreeterUser/main.swift new file mode 100644 index 00000000000..8e133ca0557 --- /dev/null +++ b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/Consumer/Sources/GreeterUser/main.swift @@ -0,0 +1,3 @@ +import Greeter + +print(greet()) diff --git a/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Package.swift b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Package.swift new file mode 100644 index 00000000000..ed4867c722e --- /dev/null +++ b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Package.swift @@ -0,0 +1,11 @@ +// swift-tools-version: 6.0 +import PackageDescription +let package = Package( + name: "Greeter", + products: [ + .library(name: "Greeter", type: .static, targets: ["Greeter"]), + ], + targets: [ + .target(name: "Greeter"), + ] +) diff --git a/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Sources/Greeter/Greeter.swift b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Sources/Greeter/Greeter.swift new file mode 100644 index 00000000000..3d9faf92cd4 --- /dev/null +++ b/Fixtures/WebAssembly/ConfiguredSDKSearchPaths/ExternalDependency/Sources/Greeter/Greeter.swift @@ -0,0 +1,3 @@ +public func greet() -> String { + return "Hello from Greeter!" +} diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index f53ece010ce..ff6ddcda9b8 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -779,8 +779,23 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { private func makeRunDestination(session: SWBBuildServiceSession) async throws -> SwiftBuild.SWBRunDestinationInfo { if let sdkManifestPath = self.buildParameters.toolchain.swiftSDK.swiftSDKManifest { + let swiftSDK = self.buildParameters.toolchain.swiftSDK + let triple = self.buildParameters.triple.tripleString + let paths = swiftSDK.pathsConfiguration + let tripleProperties = SwiftBuild.SWBSwiftSDK.TripleProperties( + sdkRootPath: paths.sdkRootPath?.pathString, + swiftResourcesPath: paths.swiftResourcesPath?.pathString, + swiftStaticResourcesPath: paths.swiftStaticResourcesPath?.pathString, + includeSearchPaths: paths.includeSearchPaths?.map(\.pathString), + librarySearchPaths: paths.librarySearchPaths?.map(\.pathString), + toolsetPaths: paths.toolsetPaths?.map(\.pathString) + ) + let inMemorySDK = SwiftBuild.SWBSwiftSDK( + manifestPath: sdkManifestPath.pathString, + targetTriples: [triple: tripleProperties] + ) return SwiftBuild.SWBRunDestinationInfo( - buildTarget: .swiftSDK(sdkManifestPath: sdkManifestPath.pathString, triple: self.buildParameters.triple.tripleString), + buildTarget: .swiftSDK(swiftSDK: inMemorySDK, triple: triple), targetArchitecture: buildParameters.triple.archName, supportedArchitectures: [], disableOnlyActiveArch: (buildParameters.architectures?.count ?? 1) > 1, diff --git a/Tests/SwiftPMWebAssemblyIntegrationTests/WebAssemblyIntegrationTests.swift b/Tests/SwiftPMWebAssemblyIntegrationTests/WebAssemblyIntegrationTests.swift index 5ac6aeec3da..beef88450ba 100644 --- a/Tests/SwiftPMWebAssemblyIntegrationTests/WebAssemblyIntegrationTests.swift +++ b/Tests/SwiftPMWebAssemblyIntegrationTests/WebAssemblyIntegrationTests.swift @@ -57,6 +57,7 @@ extension Trait where Self == Testing.ConditionTrait { } @Suite( + .serialized, .tags( Tag.Feature.Command.Build, ) @@ -152,4 +153,88 @@ private struct WebAssemblyIntegrationTests { #expect(lines.contains("Plugin tool flag: NONE")) } } + + @Test(.requiresWebAssemblySwiftSDK) + func configuredSwiftSDKSearchPaths() async throws { + try await fixture(name: "WebAssembly/ConfiguredSDKSearchPaths") { fixturePath in + let (compilerPath, sdkID) = try #require(try await findCompilerAndWebAssemblySDKIDForTesting()) + + var env = Environment() + env["SWIFT_EXEC"] = compilerPath.pathString + + let externalDependencyPath = fixturePath.appending(component: "ExternalDependency") + let consumerPath = fixturePath.appending(component: "Consumer") + + // Build a library we can use as mock "SDK content" + _ = try await executeSwiftBuild( + externalDependencyPath, + extraArgs: ["--swift-sdk", sdkID], + env: env, + buildSystem: .swiftbuild, + ) + let libBinPathOutput = try await executeSwiftBuild( + externalDependencyPath, + extraArgs: ["--swift-sdk", sdkID, "--show-bin-path"], + env: env, + buildSystem: .swiftbuild, + ) + let libBinPath = try AbsolutePath( + validating: libBinPathOutput.stdout.trimmingCharacters(in: .whitespacesAndNewlines) + ) + let staticArchive = libBinPath.appending(component: "libGreeter.a") + let swiftModule = libBinPath.appending(component: "Greeter.swiftmodule") + #expect(localFileSystem.exists(staticArchive), "Expected static archive at \(staticArchive)") + #expect(localFileSystem.exists(swiftModule), "Expected Swift module at \(swiftModule)") + let configuredSDKSearchPath = fixturePath.appending(component: "ConfiguredSDKSearchPath") + try localFileSystem.createDirectory(configuredSDKSearchPath) + try localFileSystem.copy(from: staticArchive, to: configuredSDKSearchPath.appending(component: "libGreeter.a")) + try localFileSystem.copy(from: swiftModule, to: configuredSDKSearchPath.appending(component: "Greeter.swiftmodule")) + + // Configure the Swift SDK we're using with additional search paths for the mock SDK content. + let triple = "wasm32-unknown-wasip1" + try await SwiftPM.sdk.execute([ + "configure", sdkID, triple, + "--include-search-path", configuredSDKSearchPath.pathString, + "--library-search-path", configuredSDKSearchPath.pathString, + ], env: env) + func reset() async { + _ = try? await SwiftPM.sdk.execute([ + "configure", sdkID, triple, "--reset", + ], env: env) + } + + do { + // Build a second package which depends on the mock SDK content + let buildOutput = try await executeSwiftBuild( + consumerPath, + extraArgs: ["--swift-sdk", sdkID], + env: env, + buildSystem: .swiftbuild, + ) + #expect(buildOutput.stdout.contains("Build complete")) + let consumerBinPathOutput = try await executeSwiftBuild( + consumerPath, + extraArgs: ["--swift-sdk", sdkID, "--show-bin-path"], + env: env, + buildSystem: .swiftbuild, + ) + let consumerBinPath = try AbsolutePath( + validating: consumerBinPathOutput.stdout.trimmingCharacters(in: .whitespacesAndNewlines) + ) + let wasmBinary = consumerBinPath.appending(component: "GreeterUser.wasm") + #expect(localFileSystem.exists(wasmBinary), "Expected .wasm binary at \(wasmBinary)") + + let wasmkitPath = try #require(try findWasmKit(sdkID: sdkID), "wasmkit not found in Swift SDK \(sdkID)") + let result = try await AsyncProcess.popen( + arguments: [wasmkitPath.pathString, "run", wasmBinary.pathString] + ) + let stdout = try result.utf8Output().trimmingCharacters(in: .whitespacesAndNewlines) + #expect(result.exitStatus == .terminated(code: 0), "wasmkit exited with non-zero status") + #expect(stdout == "Hello from Greeter!", "Unexpected output: \(stdout)") + await reset() + } catch { + await reset() + } + } + } } From 316f1dd2f3fc872c2d37ef12511b0222c7f5799c Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Wed, 13 May 2026 09:30:52 -0700 Subject: [PATCH 07/11] Preserve and deprecate BuildParameters API used by sourcekit-lsp --- .../BuildParameters/BuildParameters.swift | 29 +++++++++++++++++++ .../SwiftBuildSystemTests.swift | 5 ++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index f250eb3a089..57725d48267 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -255,6 +255,35 @@ public struct BuildParameters: Encodable { self.apiDigesterMode = apiDigesterMode } + /// The path to the build directory (inside the data directory). + @available(*, deprecated, message: "Use BuildSystem.buildProductsPath(for:) instead. This is preserved temporarily to support sourcekit-lsp") + public var buildPath: Basics.AbsolutePath { + // TODO: query the build system for this. + switch buildSystemKind { + case .xcode, .swiftbuild: + var configDir: String = configuration.dirname.capitalized + if self.triple.isMacOSX { + // no suffix + } else if self.triple.isAndroid() { + configDir += "-android" + } else if self.triple.isWasm { + configDir += "-webassembly" + } else { + configDir += "-" + (self.triple.darwinPlatform?.platformName ?? self.triple.osNameUnversioned) + } + return dataPath.appending(components: "Products", configDir) + case .native: + return dataPath.appending(component: configuration.dirname) + } + } + + /// The path to the index store directory. + @available(*, deprecated, message: "Use BuildSystem.indexStore(for:) instead. This is preserved temporarily to support sourcekit-lsp") + public var indexStore: Basics.AbsolutePath { + assert(indexStoreMode != .off, "index store is disabled") + return buildPath.appending(components: "index", "store") + } + public var llbuildManifest: Basics.AbsolutePath { // FIXME: this path isn't specific to `BuildParameters` due to its use of `..` // FIXME: it should be calculated in a different place diff --git a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift index 99bc35ce454..a5fc7ca9285 100644 --- a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift +++ b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift @@ -37,13 +37,13 @@ func withInstantiatedSwiftBuildSystem( try await fixture(name: fixtureName) { fixturePath in try await withTemporaryDirectory { tmpDir in + let toolchain = try UserToolchain.default let buildParameters = if let buildParameters { buildParameters } else { - mockBuildParameters(destination: .host, buildSystemKind: .swiftbuild) + mockBuildParameters(destination: .host, toolchain: toolchain, buildSystemKind: .swiftbuild) } let observabilitySystem: TestingObservability = ObservabilitySystem.makeForTesting() - let toolchain = try UserToolchain.default let workspace = try Workspace( fileSystem: fileSystem, forRootPackage: fixturePath, @@ -299,6 +299,7 @@ struct SwiftBuildSystemTests { fromFixture: "PIFBuilder/Simple", buildParameters: mockBuildParameters( destination: .host, + toolchain: try UserToolchain.default, buildSystemKind: .swiftbuild, indexStoreMode: indexStoreSettingUT, ), From 0fef7924aa27f2f662c026f7bf34f36cb4fda480 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Thu, 14 May 2026 04:02:00 -0700 Subject: [PATCH 08/11] [6.4] Demote a PIF debug message from info -> debug so it doesn't appear in verbose output (#10056) --- Sources/SwiftBuildSupport/PackagePIFBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 0090eb8e54b..f1ec1b9da2e 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -458,7 +458,7 @@ public final class PackagePIFBuilder { @discardableResult public func build() throws -> [ModuleOrProduct] { self.log( - .info, + .debug, "Building PIF project for package '\(self.package.identity)' " + "(\(package.products.count) products, \(package.modules.count) modules)" ) From 1d29fcdccfc4d3140d3e41896746bd603ccd87b7 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Thu, 14 May 2026 20:04:12 -0400 Subject: [PATCH 09/11] SwiftBuild: expose STRIP_INSTALLED_PRODUCT setting (#10038) Allow the STRIP_INSTALLED_PRODUCT build setting in the Swift Build settings to be configuration in SwiftPM. Add an "enable/disable" build options that will sets the settings value accordingly. When not set, the products are not stripped, matching the native build system. Fixes: #10037 Issue: rdar://176554583 Depends on: https://github.com/swiftlang/swift-build/pull/1395 --- Sources/Build/BuildOperation.swift | 9 ++ Sources/CoreCommands/CMakeLists.txt | 1 + Sources/CoreCommands/Options.swift | 7 ++ Sources/CoreCommands/SwiftCommandState.swift | 3 +- .../BuildParameters/BuildParameters.swift | 7 +- Sources/SPMBuildCore/CMakeLists.txt | 1 + .../SPMBuildCore/Diagnostics+Extensions.swift | 24 +++++ Sources/SwiftBuildSupport/PIFBuilder.swift | 2 +- .../SwiftBuildSupport/SwiftBuildSystem.swift | 16 ++++ Sources/XCBuildSupport/XcodeBuildSystem.swift | 10 ++ .../MockBuildTestHelper.swift | 2 + .../SwiftTesting+TraitConditional.swift | 7 +- Sources/swift-bootstrap/main.swift | 3 +- Tests/CommandsTests/BuildCommandTests.swift | 96 +++++++++++++++++++ .../SwiftBuildSystemTests.swift | 42 +++++++- 15 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 Sources/SPMBuildCore/Diagnostics+Extensions.swift diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 18b6d851390..cca8715b461 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -438,6 +438,15 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS return result } + if let stripProduct = self.config.destinationBuildParameters.stripProducts { + self.observabilityScope.emit( + Basics.Diagnostic.unsupportedStripProductsConfigurationFlag( + isEnabled: stripProduct, + with: .native, + ) + ) + } + let buildStartTime = DispatchTime.now() // Get the build description (either a cached one or newly created). diff --git a/Sources/CoreCommands/CMakeLists.txt b/Sources/CoreCommands/CMakeLists.txt index d396acd8f35..4620ecb0fed 100644 --- a/Sources/CoreCommands/CMakeLists.txt +++ b/Sources/CoreCommands/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(CoreCommands BuildSystemSupport.swift SwiftCommandState.swift + SwiftCommandState.swift SwiftCommandObservabilityHandler.swift Options.swift) target_link_libraries(CoreCommands PUBLIC diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index 1a39d809ac1..7fe2aafd793 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -650,6 +650,13 @@ public struct BuildOptions: ParsableArguments { /// See `BuildParameters.DebugInfoFormat.none` for details. case none } + + @Flag( + name: .customLong("experimental-strip-products"), + inversion: .prefixedEnableDisable, + help: "Whether or not to strip debug symbols from the final binary.", + ) + public var stripProducts: Bool? } public struct LinkerOptions: ParsableArguments { diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 6bf3525287d..dde229b9527 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -1063,7 +1063,8 @@ public final class SwiftCommandState { forceTestDiscovery: self.options.build.enableTestDiscovery, // backwards compatibility, remove with --enable-test-discovery testEntryPointPath: self.options.build.testEntryPointPath - ) + ), + stripProducts: self.options.build.stripProducts, ) } diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index 57725d48267..d714e975be3 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -94,6 +94,9 @@ public struct BuildParameters: Encodable { /// Whether to create dylibs for dynamic library products. public var shouldCreateDylibForDynamicProducts: Bool + /// Whether to strip debug symbols from the final binary + public var stripProducts: Bool? + /// The current build environment. public var buildEnvironment: BuildEnvironment { BuildEnvironment(platform: currentPlatform, configuration: configuration) @@ -187,7 +190,8 @@ public struct BuildParameters: Encodable { linkingParameters: Linking = .init(), outputParameters: Output = .init(), testingParameters: Testing = .init(), - apiDigesterMode: APIDigesterMode? = nil + apiDigesterMode: APIDigesterMode? = nil, + stripProducts: Bool? = nil, ) throws { // Default to the unversioned triple if none is provided so that we defer to the package's requested deployment target, for Darwin platforms. For other platforms, continue to include the version since those don't have the concept of a package-specified version, and the version is meaningful for some platforms including Android and FreeBSD. let triple = try triple ?? { @@ -253,6 +257,7 @@ public struct BuildParameters: Encodable { self.outputParameters = outputParameters self.testingParameters = testingParameters self.apiDigesterMode = apiDigesterMode + self.stripProducts = stripProducts } /// The path to the build directory (inside the data directory). diff --git a/Sources/SPMBuildCore/CMakeLists.txt b/Sources/SPMBuildCore/CMakeLists.txt index 23dd605543b..478a124b8e6 100644 --- a/Sources/SPMBuildCore/CMakeLists.txt +++ b/Sources/SPMBuildCore/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(SPMBuildCore BuildSystem/BuildSystemDelegate.swift BuildSystem/DiagnosticsCapturingBuildSystemDelegate.swift BuiltTestProduct.swift + Diagnostics+Extensions.swift Plugins/DefaultPluginScriptRunner.swift Plugins/PluginContextSerializer.swift Plugins/PluginInvocation.swift diff --git a/Sources/SPMBuildCore/Diagnostics+Extensions.swift b/Sources/SPMBuildCore/Diagnostics+Extensions.swift new file mode 100644 index 00000000000..54775a4b909 --- /dev/null +++ b/Sources/SPMBuildCore/Diagnostics+Extensions.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2026 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.Diagnostic +import struct SPMBuildCore.BuildSystemProvider +import enum PackageModel.BuildConfiguration + +extension Basics.Diagnostic { + + package static func unsupportedStripProductsConfigurationFlag( + isEnabled: Bool, + with selectedBuildSystem: BuildSystemProvider.Kind, + ) -> Self { + return .error("Command line option '--\(isEnabled ? "enable": "--disable")--experimental-strip-products' is unsupported with build system '\(selectedBuildSystem)'. Only use with '\(BuildSystemProvider.Kind.swiftbuild)' build system with configuration '\(BuildConfiguration.release)'") + } +} diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index e9d1d8f4ade..71337df3e53 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -442,7 +442,7 @@ public final class PIFBuilder { packageDisplayVersion: package.manifest.displayName, pkgConfigDirectories: self.parameters.pkgConfigDirectories, fileSystem: self.fileSystem, - observabilityScope: self.observabilityScope + observabilityScope: self.observabilityScope, ) packagesAndBuilders.append((package, packagePIFBuilder, packagePIFBuilderDelegate)) diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index c16a3d78974..5aaad31f9a9 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -12,6 +12,7 @@ @_spi(SwiftPMInternal) import Basics +import SPMBuildCore // for the Basics.Diagnostic extension import Dispatch import class Foundation.FileManager import class Foundation.JSONEncoder @@ -448,6 +449,15 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { return result } + if let stripProdduct = self.buildParameters.stripProducts, self.buildParameters.configuration != .release { + self.observabilityScope.emit( + Basics.Diagnostic.unsupportedStripProductsConfigurationFlag( + isEnabled: stripProdduct, + with: .swiftbuild, + ) + ) + } + guard try await self.compilePlugins(in: subset) else { result.serializedDiagnosticPathsByTargetName = .failure(StringError("Plugin compilation failed")) return result @@ -888,6 +898,12 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } } + settings["STRIP_INSTALLED_PRODUCT"] = if let stripInstalledProducts = self.buildParameters.stripProducts { + stripInstalledProducts ? "YES" : "NO" + } else { + "NO" + } + // FIXME: workaround for old Xcode installations such as what is in CI settings["LM_SKIP_METADATA_EXTRACTION"] = "YES" if let symbolGraphOptions { diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index 208c09d357b..0970c331275 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -13,6 +13,7 @@ @_spi(SwiftPMInternal) import Basics import Dispatch +import SPMBuildCore // for the Basics.Diagnostic extension import class Foundation.JSONEncoder import class Foundation.NSArray import class Foundation.NSDictionary @@ -186,6 +187,15 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { return buildResult } + if let stripProdduct = self.buildParameters.stripProducts { + self.observabilityScope.emit( + Basics.Diagnostic.unsupportedStripProductsConfigurationFlag( + isEnabled: stripProdduct, + with: .xcode, + ) + ) + } + let pifBuilder = try await getPIFBuilder() let pif = try pifBuilder.generatePIF() try self.fileSystem.writeIfChanged(path: buildParameters.pifManifest, string: pif) diff --git a/Sources/_InternalTestSupport/MockBuildTestHelper.swift b/Sources/_InternalTestSupport/MockBuildTestHelper.swift index 8a89997bcf6..9c0b69539eb 100644 --- a/Sources/_InternalTestSupport/MockBuildTestHelper.swift +++ b/Sources/_InternalTestSupport/MockBuildTestHelper.swift @@ -109,6 +109,7 @@ public func mockBuildParameters( prepareForIndexing: BuildParameters.PrepareForIndexingMode = .off, sanitizers: [Sanitizer] = [], numberOfWorkers: UInt32 = 3, + stripProducts: Bool? = nil, ) -> BuildParameters { try! BuildParameters( destination: destination, @@ -140,6 +141,7 @@ public func mockBuildParameters( shouldDisableLocalRpath: shouldDisableLocalRpath, shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib ), + stripProducts: stripProducts, ) } diff --git a/Sources/_InternalTestSupport/SwiftTesting+TraitConditional.swift b/Sources/_InternalTestSupport/SwiftTesting+TraitConditional.swift index 1debe6a989f..0010622f655 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+TraitConditional.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+TraitConditional.swift @@ -33,7 +33,7 @@ extension Trait where Self == Testing.ConditionTrait { } } - public static func requireCompiledWith6_3OrLater(_ comment: Comment? = nil) -> Self { + public static func requireCompiledWith6_3OrLater(because comment: Comment? = nil) -> Self { enabled(comment ?? "This needs to have been compiled with Swift 6.3 or later.") { #if compiler(>=6.3) true @@ -43,6 +43,11 @@ extension Trait where Self == Testing.ConditionTrait { } } + @available(*, deprecated, message: "use `requireCompiledWith6_3OrLater(because:) instead") + public static func requireCompiledWith6_3OrLater(_ comment: Comment? = nil) -> Self { + requireCompiledWith6_3OrLater(because: comment) + } + /// Enabled only if toolchain support swift concurrency public static var requiresSwiftConcurrencySupport: Self { enabled("skipping because test environment doesn't support concurrency") { diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 0ba9c8f4f75..f6d1235ce8e 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -345,7 +345,8 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { ), outputParameters: .init( isVerbose: logLevel <= .info - ) + ), + stripProducts: nil, ) } diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index c2f7c200284..495fe0e8238 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -141,6 +141,102 @@ struct BuildCommandTestCases { #expect(stdout.contains("USAGE: swift build")) } + @Test( + arguments: [ + BuildData(buildSystem: .swiftbuild, config: .release), + ], ["enable", "disable"], + ) + func testStripProductsGeneratedDoesNotEmitErrorsWhenSupportedBuildSystemAndConfiguration( + buildData: BuildData, + action: String, + ) async throws { + let buildSystem = buildData.buildSystem + let config = buildData.config + let argumentUT = "--\(action)-experimental-strip-products" + + try await fixture(name: "ValidLayouts/SingleModule/Library") { fixturePath in + let (stdout, stderr) = try await executeSwiftBuild( + fixturePath, + configuration: config, + extraArgs: [ + argumentUT, + "--verbose", + ], + buildSystem: buildSystem, + ) + + let diag = Basics.Diagnostic.unsupportedStripProductsConfigurationFlag( + isEnabled: action.lowercased() == "enable", + with: buildSystem, + ) + + #expect(stdout.contains("Build complete!")) + #expect(!stderr.contains("\(diag.severity): \(diag.message)")) + #expect(!stdout.contains("\(diag.severity): \(diag.message)")) + } + } + + @Test( + .requireHostOS(.macOS), + arguments: getBuildData(for: [.xcode]), ["enable", "disable"], + ) + func testStripProductsGeneratesErrorWhenUsedWithXcodeBuildSystemAndConfiguration( + buildData: BuildData, + action: String, + ) async throws { + try await __testImplementationStripProductsGeneratedErrorWhenUsedWithIncorrectBuildSystemAndConfiguration( + buildData: buildData, + action: action, + ) + } + + @Test( + arguments: getBuildData(for: [.native]) + [ + BuildData(buildSystem: .swiftbuild, config: .debug), + ], ["enable", "disable"], + ) + func testStripProductsGeneratesErrorWhenUsedWithUnsupportedBuildSystemAndConfiguration( + buildData: BuildData, + action: String + ) async throws { + try await __testImplementationStripProductsGeneratedErrorWhenUsedWithIncorrectBuildSystemAndConfiguration( + buildData: buildData, + action: action, + ) + } + + + private func __testImplementationStripProductsGeneratedErrorWhenUsedWithIncorrectBuildSystemAndConfiguration( + buildData: BuildData, + action: String, + ) async throws { + let buildSystem = buildData.buildSystem + let config = buildData.config + + let argumentUT = "--\(action)-experimental-strip-products" + + try await fixture(name: "ValidLayouts/SingleModule/Library") { fixturePath in + + await expectThrowsCommandExecutionError( + try await executeSwiftBuild( + fixturePath, + configuration: config, + extraArgs: [ + argumentUT, + ], + buildSystem: buildSystem, + ) + ) { error in + let diag = Basics.Diagnostic.unsupportedStripProductsConfigurationFlag( + isEnabled: action.lowercased() == "enable", + with: buildSystem, + ) + #expect(error.stderr.contains("\(diag.severity): \(diag.message)")) + } + } + } + + @Test( .tags( .Feature.CommandLineArguments.ShowBinPath, diff --git a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift index a5fc7ca9285..9950f21d925 100644 --- a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift +++ b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift @@ -137,7 +137,7 @@ extension PackageModel.Sanitizer { .tags( .TestSize.medium, ), - .requireCompiledWith6_3OrLater("https://github.com/swiftlang/swift-corelibs-foundation/pull/5269") + .requireCompiledWith6_3OrLater(because: "https://github.com/swiftlang/swift-corelibs-foundation/pull/5269") ) struct SwiftBuildSystemTests { @@ -343,6 +343,43 @@ struct SwiftBuildSystemTests { } } + + @Test( + arguments: [ + (stripProductsSettingUT: true, expectedValue: "YES"), + (stripProductsSettingUT: false, expectedValue: "NO"), + (stripProductsSettingUT: nil, expectedValue: "NO"), + ] + ) + func validatestripProductsetting( + stripProductsSettingUT: Bool?, + expectedValue: String + ) async throws { + try await withInstantiatedSwiftBuildSystem( + fromFixture: "PIFBuilder/Simple", + buildParameters: mockBuildParameters( + destination: .host, + buildSystemKind: .swiftbuild, + stripProducts: stripProductsSettingUT, + ), + ) { swiftBuild, service, session, observabilityScope, buildParameters in + + let buildSettings = try await swiftBuild.makeBuildParameters( + service: service, + session: session, + symbolGraphOptions: nil, + setToolchainSetting: false, // Set this to false as SwiftBuild checks the toolchain path + ) + + let synthesizedArgs = try #require(buildSettings.overrides.synthesized) + let actual = synthesizedArgs.table["STRIP_INSTALLED_PRODUCT"] + #expect( + actual == expectedValue, + "strip install products: \(String(describing: stripProductsSettingUT)) >>> Actual: '\(actual)' expected: '\(expectedValue)'", + ) + } + } + @Test( .serialized, arguments: [ @@ -379,6 +416,7 @@ struct SwiftBuildSystemTests { } } + @Test( .issue("https://github.com/swiftlang/swift-package-manager/issues/9321", relationship: .verifies), arguments: [ @@ -517,7 +555,6 @@ struct SwiftBuildSystemTests { } } - #if os(Windows) @Test func debugInfoFormatCodeViewOnWindows() async throws { // Test CodeView format separately as it's only supported on Windows @@ -541,7 +578,6 @@ struct SwiftBuildSystemTests { #expect(synthesizedArgs.table["DEBUG_INFORMATION_FORMAT"] == "codeview") } } - #endif @Test( arguments: [ From 6361b4b4fb7619e2fdcbe1314e072cdbad06bd5f Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Fri, 15 May 2026 23:34:16 -0500 Subject: [PATCH 10/11] [6.4] Remove percent from swift build progress logging (#10068) Cherry-pick of #10063 --- Sources/SwiftBuildSupport/SwiftBuildSystem.swift | 1 - .../SwiftBuildSystemMessageHandler.swift | 10 ++++------ .../SwiftBuildSystemMessageHandlerTests.swift | 8 ++++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 5aaad31f9a9..0a7df5923e9 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -706,7 +706,6 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { switch operation.state { case .succeeded: guard !self.logLevel.isQuiet else { return } - buildMessageHandler.progressAnimation.update(step: 100, total: 100, text: "") buildMessageHandler.progressAnimation.complete(success: true) let duration = ContinuousClock.Instant.now - buildStartTime let formattedDuration = duration.formatted(.units(allowed: [.seconds], fractionalPart: .show(length: 2, rounded: .up))) diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystemMessageHandler.swift b/Sources/SwiftBuildSupport/SwiftBuildSystemMessageHandler.swift index 5c9247f7696..ddcac03da4c 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystemMessageHandler.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystemMessageHandler.swift @@ -242,17 +242,15 @@ public final class SwiftBuildSystemMessageHandler { } } case .didUpdateProgress(let progressInfo): - let step = Int(progressInfo.percentComplete) let message = if let targetName = progressInfo.targetName { - "\(targetName) \(progressInfo.message)" + "[\(progressInfo.message)] \(targetName)" } else { - "\(progressInfo.message)" + "[\(progressInfo.message)]" } // Skip if message doesn't contain anything useful to display. - // TODO: To file an issue for SwiftBuild here. - if message.contains(where: \.isLetter) { - progressAnimation.update(step: step, total: 100, text: message) + if !message.isEmpty { + progressAnimation.update(step: -1, total: 100, text: message) } callback = { [weak self] buildSystem in diff --git a/Tests/SwiftBuildSupportTests/SwiftBuildSystemMessageHandlerTests.swift b/Tests/SwiftBuildSupportTests/SwiftBuildSystemMessageHandlerTests.swift index 7a32aac40a2..0cfdd461464 100644 --- a/Tests/SwiftBuildSupportTests/SwiftBuildSystemMessageHandlerTests.swift +++ b/Tests/SwiftBuildSupportTests/SwiftBuildSystemMessageHandlerTests.swift @@ -480,10 +480,10 @@ struct SwiftBuildSystemMessageHandlerTests { #expect(self.observability.diagnostics.count == 0) let output = self.outputStream.bytes.description - #expect(output.contains("Weird percent")) - #expect(!output.contains("12 / 32")) - #expect(output.contains("Something useful")) - #expect(output.contains("Complete")) + #expect(output.contains("[Weird percent]")) + #expect(output.contains("[12 / 32]")) + #expect(output.contains("[Something useful]")) + #expect(output.contains("[Complete]")) } } From 2c72f2620092191ec6202c19ed4bf150dc012a50 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Sat, 16 May 2026 01:23:07 -0400 Subject: [PATCH 11/11] PIF: emit error when testTarget depends on testTarget (#10064) Swift Build does not suport testTarget that depend on another testTarget, and the error produced was not clear. This change modifies the PIF Builder to emit an error diagnostics when it detects a testTarget depends on another testTarget. Fixes: #10062 Issue: rdar://174431298 --- .../Package.swift | 26 +++++++++++++++++++ .../Tests/leafTestTarget/leafTestTarget.swift | 9 +++++++ .../myOtherTestTarget/myOtherTestTarget.swift | 9 +++++++ .../Tests/myTestTarget/myTestTarget.swift | 9 +++++++ Sources/SwiftBuildSupport/PIFBuilder.swift | 9 +++++++ .../PackagePIFProjectBuilder+Products.swift | 6 +++++ .../PIFBuilderTests.swift | 16 ++++++++++++ 7 files changed, 84 insertions(+) create mode 100644 Fixtures/TestTargetDependOnTestTarget/Package.swift create mode 100644 Fixtures/TestTargetDependOnTestTarget/Tests/leafTestTarget/leafTestTarget.swift create mode 100644 Fixtures/TestTargetDependOnTestTarget/Tests/myOtherTestTarget/myOtherTestTarget.swift create mode 100644 Fixtures/TestTargetDependOnTestTarget/Tests/myTestTarget/myTestTarget.swift diff --git a/Fixtures/TestTargetDependOnTestTarget/Package.swift b/Fixtures/TestTargetDependOnTestTarget/Package.swift new file mode 100644 index 00000000000..d06bee9bea9 --- /dev/null +++ b/Fixtures/TestTargetDependOnTestTarget/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 6.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "TestTargetDependOnTestTarget", + targets: [ + .testTarget( + name: "leafTestTarget", + ), + .testTarget( + name: "myTestTarget", + dependencies: [ + "leafTestTarget", + ], + ), + .testTarget( + name: "myOtherTestTarget", + dependencies: [ + "leafTestTarget", + "myTestTarget", + ], + ), + ] +) diff --git a/Fixtures/TestTargetDependOnTestTarget/Tests/leafTestTarget/leafTestTarget.swift b/Fixtures/TestTargetDependOnTestTarget/Tests/leafTestTarget/leafTestTarget.swift new file mode 100644 index 00000000000..5cccd5e87c7 --- /dev/null +++ b/Fixtures/TestTargetDependOnTestTarget/Tests/leafTestTarget/leafTestTarget.swift @@ -0,0 +1,9 @@ +import Testing + +@Suite +struct LeafTestTargetTests { + @Test("LeafTestTarget tests") + func example() { + #expect(42 == 17 + 25) + } +} \ No newline at end of file diff --git a/Fixtures/TestTargetDependOnTestTarget/Tests/myOtherTestTarget/myOtherTestTarget.swift b/Fixtures/TestTargetDependOnTestTarget/Tests/myOtherTestTarget/myOtherTestTarget.swift new file mode 100644 index 00000000000..75dea8cdc10 --- /dev/null +++ b/Fixtures/TestTargetDependOnTestTarget/Tests/myOtherTestTarget/myOtherTestTarget.swift @@ -0,0 +1,9 @@ +import Testing + +@Suite +struct MyOTherTestTargetTests { + @Test("MyOTherTestTarget tests") + func example() { + #expect(42 == 17 + 25) + } +} diff --git a/Fixtures/TestTargetDependOnTestTarget/Tests/myTestTarget/myTestTarget.swift b/Fixtures/TestTargetDependOnTestTarget/Tests/myTestTarget/myTestTarget.swift new file mode 100644 index 00000000000..20daff5aefe --- /dev/null +++ b/Fixtures/TestTargetDependOnTestTarget/Tests/myTestTarget/myTestTarget.swift @@ -0,0 +1,9 @@ +import Testing + +@Suite +struct MyTestTargetTests { + @Test("MyTestTarget tests") + func example() { + #expect(42 == 17 + 25) + } +} diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 71337df3e53..99b72a599e8 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -187,6 +187,10 @@ public final class PIFBuilder { throw PIFGenerationError.printedPIFManifestGraphviz } + if self.observabilityScope.errorsReported { + throw PIFGenerationError.errorDiagnosticsReported + } + return PIFGenerationResult(pif: pifString, accompanyingMetadata: modulesAndProducts) } @@ -814,6 +818,8 @@ public enum PIFGenerationError: Error { /// Early build termination when using `--print-pif-manifest-graph`. case printedPIFManifestGraphviz + + case errorDiagnosticsReported } extension PIFGenerationError: CustomStringConvertible { @@ -832,6 +838,9 @@ extension PIFGenerationError: CustomStringConvertible { case .printedPIFManifestGraphviz: "Printed PIF manifest as graphviz" + + case .errorDiagnosticsReported: + "Errors reportes in PIF builder" } } } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index 7c1f628f507..c5130c96f48 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -488,6 +488,12 @@ extension PackagePIFProjectBuilder { } case .library, .systemModule, .test: + if moduleDependency.type == .test && product.type == .test { + log( + .error, + "Test target '\(product.name)' cannot depend on another test target '\(moduleDependency.name)'", + ) + } let shouldLinkProduct = moduleDependency.type != .systemModule let dependencyGUID = moduleDependency.pifTargetGUID self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( diff --git a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift index eec6dd013f6..31068090a1c 100644 --- a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift @@ -1015,6 +1015,22 @@ struct PIFBuilderTests { } } + @Test + func errorEmittedWhenTestTargetDependsOnAnotherTestTarget() async throws { + try await withGeneratedPIF(fromFixture: "TestTargetDependOnTestTarget") { pif, observabilitySystem in + let expectedErrors = [ + "PIF: Test target 'myOtherTestTarget' cannot depend on another test target 'leafTestTarget'", + "PIF: Test target 'myOtherTestTarget' cannot depend on another test target 'myTestTarget'", + "PIF: Test target 'myTestTarget' cannot depend on another test target 'leafTestTarget'", + ].sorted() + let actualDiags = observabilitySystem.diagnostics.filter { $0.severity == .error } + let diagMsgs = actualDiags.map { $0.message }.sorted() + + #expect(actualDiags.count == expectedErrors.count) + #expect(diagMsgs == expectedErrors) + } + } + @Test func mixedSourceTarget() async throws { let fs = InMemoryFileSystem( emptyFiles: