From 2378d6dfe1fa4d4c1a5e5d285cddbefe95c04aab Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 26 Apr 2024 17:15:16 -0400 Subject: [PATCH 1/2] Fix test targets that depend on macros and `swift-testing` (#7508) After #7353 landed, I noticed that the build products for test targets were not being emitted correctly. swift-testing and XCTest produce separate build products (with distinct names) but this wasn't happening as intended. It turns out that the changes to split `buildParameters` into `productsBuildParameters` and `toolsBuildParameters` weren't fully propagated to our testing infrastructure. I also noticed `SWIFT_PM_SUPPORTS_SWIFT_TESTING` wasn't being set correctly anymore (same root cause) although we've decided to ignore that flag over in swift-testing anyway (see https://github.com/apple/swift-testing/pull/376.) This regression caused build failures in swift-testing (e.g. [here](https://ci.swift.org/job/pr-swift-testing-macos/663/console)) with the telltale failure signature: > /Users/ec2-user/jenkins/workspace/pr-swift-testing-macos/branch-main/swift-testing/.build/x86_64-apple-macosx/debug/swift-testingPackageTests.xctest/Contents/MacOS/swift-testingPackageTests: /Users/ec2-user/jenkins/workspace/pr-swift-testing-macos/branch-main/swift-testing/.build/x86_64-apple-macosx/debug/swift-testingPackageTests.xctest/Contents/MacOS/swift-testingPackageTests: cannot execute binary file Which indicates that it thinks the filename for the swift-testing build product is the XCTest bundle's executable. This PR plumbs through the two build parameters arguments to everywhere in `swift test` and `swift build` that needs them and resolves the issue. --------- Co-authored-by: Max Desiatov --- Sources/Commands/SwiftBuildCommand.swift | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 6d74dd63c25..7310ba5bbfe 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -149,8 +149,7 @@ package struct SwiftBuildCommand: AsyncSwiftCommand { throw ExitCode.failure } if case .allIncludingTests = subset { - var buildParameters = try swiftCommandState.productsBuildParameters - for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) { + func updateTestingParameters(of buildParameters: inout BuildParameters, library: BuildParameters.Testing.Library) { buildParameters.testingParameters = .init( configuration: buildParameters.configuration, targetTriple: buildParameters.triple, @@ -161,18 +160,30 @@ package struct SwiftBuildCommand: AsyncSwiftCommand { testEntryPointPath: globalOptions.build.testEntryPointPath, library: library ) - try build(swiftCommandState, subset: subset, buildParameters: buildParameters) + } + var productsBuildParameters = try swiftCommandState.productsBuildParameters + var toolsBuildParameters = try swiftCommandState.toolsBuildParameters + for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) { + updateTestingParameters(of: &productsBuildParameters, library: library) + updateTestingParameters(of: &toolsBuildParameters, library: library) + try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) } } else { - try build(swiftCommandState, subset: subset) + try build(swiftCommandState, subset: subset, productsBuildParameters: nil, toolsBuildParameters: nil) } } - private func build(_ swiftCommandState: SwiftCommandState, subset: BuildSubset, buildParameters: BuildParameters? = nil) throws { + private func build( + _ swiftCommandState: SwiftCommandState, + subset: BuildSubset, + productsBuildParameters: BuildParameters?, + toolsBuildParameters: BuildParameters? + ) throws { let buildSystem = try swiftCommandState.createBuildSystem( explicitProduct: options.product, shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, - productsBuildParameters: buildParameters, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, // command result output goes on stdout // ie "swift build" should output to stdout outputStream: TSCBasic.stdoutStream From 5f569c252e1f27b445ad25a41adff86ac76f6b10 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 30 Apr 2024 19:48:58 -0400 Subject: [PATCH 2/2] Add `swift build --enable-code-coverage` (#7518) This PR adds the `--enable-code-coverage` flag (from `swift test`) to `swift build` so that code coverage can be used in a two-stage build process (build, then execute later.) Resolves rdar://127309781. --- Sources/Commands/SwiftBuildCommand.swift | 26 +++++++++++++++++---- Tests/CommandsTests/BuildCommandTests.swift | 17 ++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 7310ba5bbfe..b6813895e26 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -73,6 +73,12 @@ struct BuildCommandOptions: ParsableArguments { @Flag(help: "Build both source and test targets") var buildTests: Bool = false + /// Whether to enable code coverage. + @Flag(name: .customLong("code-coverage"), + inversion: .prefixedEnableDisable, + help: "Enable code coverage") + var enableCodeCoverage: Bool = false + /// If the binary output path should be printed. @Flag(name: .customLong("show-bin-path"), help: "Print the binary output path") var shouldPrintBinPath: Bool = false @@ -148,6 +154,18 @@ package struct SwiftBuildCommand: AsyncSwiftCommand { guard let subset = options.buildSubset(observabilityScope: swiftCommandState.observabilityScope) else { throw ExitCode.failure } + + var productsBuildParameters = try swiftCommandState.productsBuildParameters + var toolsBuildParameters = try swiftCommandState.toolsBuildParameters + + // 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(swiftCommandState.productsBuildParameters.codeCovPath) + productsBuildParameters.testingParameters.enableCodeCoverage = true + toolsBuildParameters.testingParameters.enableCodeCoverage = true + } + if case .allIncludingTests = subset { func updateTestingParameters(of buildParameters: inout BuildParameters, library: BuildParameters.Testing.Library) { buildParameters.testingParameters = .init( @@ -161,23 +179,21 @@ package struct SwiftBuildCommand: AsyncSwiftCommand { library: library ) } - var productsBuildParameters = try swiftCommandState.productsBuildParameters - var toolsBuildParameters = try swiftCommandState.toolsBuildParameters for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) { updateTestingParameters(of: &productsBuildParameters, library: library) updateTestingParameters(of: &toolsBuildParameters, library: library) try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) } } else { - try build(swiftCommandState, subset: subset, productsBuildParameters: nil, toolsBuildParameters: nil) + try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) } } private func build( _ swiftCommandState: SwiftCommandState, subset: BuildSubset, - productsBuildParameters: BuildParameters?, - toolsBuildParameters: BuildParameters? + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters ) throws { let buildSystem = try swiftCommandState.createBuildSystem( explicitProduct: options.product, diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index ea54f5a12da..b81616d9bcd 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -664,4 +664,21 @@ final class BuildCommandTests: CommandsTestCase { } } #endif + + func testCodeCoverage() throws { + // Test that no codecov directory is created if not specified when building. + try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in + let buildResult = try self.build(["--build-tests"], packagePath: path, cleanAfterward: false) + XCTAssertThrowsError(try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path)) + } + + // Test that enabling code coverage during building produces the expected folder. + try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in + let buildResult = try self.build(["--build-tests", "--enable-code-coverage"], packagePath: path, cleanAfterward: false) + try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path) + let codeCovPath = buildResult.binPath.appending("codecov") + let codeCovFiles = try localFileSystem.getDirectoryContents(codeCovPath) + XCTAssertGreaterThan(codeCovFiles.count, 0) + } + } }