From 03276023c04ebe3f80ebc9d5ae56c6fc44395166 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 25 Jul 2025 12:46:06 +0100 Subject: [PATCH 1/6] [test] NFC: Factor out `withHostToolsPackages` Factor out the creation of the common package dependencies between these two tests. --- .../HostBuildToolBuildOperationTests.swift | 488 ++++++------------ 1 file changed, 170 insertions(+), 318 deletions(-) diff --git a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift index 1e4748d3..56dd070a 100644 --- a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift @@ -173,183 +173,144 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { try await testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: .anyiOSDevice) } - func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: RunDestinationInfo) async throws { - try await withTemporaryDirectory { tmpDirPath async throws -> Void in - let depPackage = try await TestPackageProject( - "DepPackage", - groupTree: TestGroup("Foo", children: [ - TestFile("transitivedep.swift"), - TestFile("dep.swift"), + private func withHostToolsPackages( + clients: TestProject..., + body: (BuildOperationTester, TestWorkspace) async throws -> Void + ) async throws { + let depPackage = try await TestPackageProject( + "DepPackage", + groupTree: TestGroup("Foo", children: [ + TestFile("transitivedep.swift"), + TestFile("dep.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SWIFT_VERSION": swiftVersion, + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "CODE_SIGNING_ALLOWED": "NO", + ]), + ], + targets: [ + TestStandardTarget("TransitivePackageDep", type: .objectFile, buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", + ], + impartedBuildProperties: + TestImpartedBuildProperties( + buildSettings: [ + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "IMPARTED_SETTINGS" + ]) + ), + ], buildPhases: [ + TestSourcesBuildPhase(["transitivedep.swift"]) ]), - buildConfigurations: [ + TestStandardTarget("PackageDep", type: .staticLibrary, buildConfigurations: [ TestBuildConfiguration( "Debug", buildSettings: [ - "SWIFT_VERSION": swiftVersion, - "GENERATE_INFOPLIST_FILE": "YES", - "PRODUCT_NAME": "$(TARGET_NAME)", - "CODE_SIGNING_ALLOWED": "NO", + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", ]), - ], - targets: [ - TestStandardTarget("TransitivePackageDep", type: .objectFile, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ], - impartedBuildProperties: - TestImpartedBuildProperties( - buildSettings: [ - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "IMPARTED_SETTINGS" - ]) - ), - ], buildPhases: [ - TestSourcesBuildPhase(["transitivedep.swift"]) - ]), - TestStandardTarget("PackageDep", type: .staticLibrary, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ - TestSourcesBuildPhase(["dep.swift"]), - TestFrameworksBuildPhase([ - TestBuildFile(.target("TransitivePackageDep")) - ]) - ], dependencies: [ - "TransitivePackageDep" - ]), - TestPackageProductTarget("PackageDepProduct", frameworksBuildPhase: - TestFrameworksBuildPhase([ - TestBuildFile(.target("PackageDep")), - ] - ), buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], dependencies: [ - "PackageDep" - ]), - ]) - - let hostToolsPackage = try await TestPackageProject( - "HostToolsPackage", - groupTree: TestGroup("Foo", children: [ - TestFile("tool.swift"), - TestFile("lib.swift"), + ], buildPhases: [ + TestSourcesBuildPhase(["dep.swift"]), + TestFrameworksBuildPhase([ + TestBuildFile(.target("TransitivePackageDep")) + ]) + ], dependencies: [ + "TransitivePackageDep" ]), - buildConfigurations: [ + TestPackageProductTarget("PackageDepProduct", frameworksBuildPhase: + TestFrameworksBuildPhase([ + TestBuildFile(.target("PackageDep")), + ] + ), buildConfigurations: [ TestBuildConfiguration( "Debug", buildSettings: [ - "SWIFT_VERSION": swiftVersion, - "GENERATE_INFOPLIST_FILE": "YES", - "PRODUCT_NAME": "$(TARGET_NAME)", - "CODE_SIGNING_ALLOWED": "NO", + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", ]), - ], - targets: [ - TestStandardTarget("HostTool", type: .hostBuildTool, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - ]) - ], buildPhases: [ - TestSourcesBuildPhase(["tool.swift"]), - TestFrameworksBuildPhase([TestBuildFile(.target("PackageDepProduct"))]) - ], dependencies: [ - "PackageDepProduct" - ]), - TestStandardTarget("HostToolClientLib", type: .objectFile, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ - TestSourcesBuildPhase(["lib.swift"]), - ], dependencies: [ - "HostTool" - ]), - TestPackageProductTarget("HostToolClientLibProduct", frameworksBuildPhase: - TestFrameworksBuildPhase([TestBuildFile(.target("HostToolClientLib"))] - ), buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], dependencies: [ - "HostToolClientLib" + ], dependencies: [ + "PackageDep" + ]), + ]) + + let hostToolsPackage = try await TestPackageProject( + "HostToolsPackage", + groupTree: TestGroup("Foo", children: [ + TestFile("tool.swift"), + TestFile("lib.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SWIFT_VERSION": swiftVersion, + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "CODE_SIGNING_ALLOWED": "NO", ]), - ]) - - let testProject = try await TestProject( - "aProject", - groupTree: TestGroup("Foo", children: [ - TestFile("frame.swift"), - TestFile("app.swift") - ]), buildConfigurations: [ + ], + targets: [ + TestStandardTarget("HostTool", type: .hostBuildTool, buildConfigurations: [ TestBuildConfiguration( "Debug", buildSettings: [ - "SWIFT_VERSION": swiftVersion, - "GENERATE_INFOPLIST_FILE": "YES", - "PRODUCT_NAME": "$(TARGET_NAME)", - "CODE_SIGNING_ALLOWED": "NO", + "SDKROOT": "auto", + ]) + ], buildPhases: [ + TestSourcesBuildPhase(["tool.swift"]), + TestFrameworksBuildPhase([TestBuildFile(.target("PackageDepProduct"))]) + ], dependencies: [ + "PackageDepProduct" + ]), + TestStandardTarget("HostToolClientLib", type: .objectFile, buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", ]), - ], - targets: [ - TestStandardTarget("Framework", type: .framework, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ - TestSourcesBuildPhase(["frame.swift"]), - TestFrameworksBuildPhase([ - TestBuildFile(.target("HostToolClientLibProduct")) + ], buildPhases: [ + TestSourcesBuildPhase(["lib.swift"]), + ], dependencies: [ + "HostTool" + ]), + TestPackageProductTarget("HostToolClientLibProduct", frameworksBuildPhase: + TestFrameworksBuildPhase([TestBuildFile(.target("HostToolClientLib"))] + ), buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", ]), - ], dependencies: [ - "HostToolClientLibProduct" - ]), - TestStandardTarget("App", type: .application, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ - TestSourcesBuildPhase(["app.swift"]), - ], dependencies: [ - "Framework" - ]), - ] - ) - let testWorkspace = TestWorkspace("aWorkspace", sourceRoot: tmpDirPath.join("Test"), projects: [depPackage, hostToolsPackage, testProject]) + ], dependencies: [ + "HostToolClientLib" + ]), + ]) + + try await withTemporaryDirectory { tmpDirPath in + let testWorkspace = TestWorkspace("aWorkspace", sourceRoot: tmpDirPath.join("Test"), projects: [depPackage, hostToolsPackage] + clients) let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false, systemInfo: .init(operatingSystemVersion: Version(99, 98, 97), productBuildVersion: "99A98", nativeArchitecture: Architecture.host.stringValue ?? "undefined_arch")) - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("DepPackage/transitivedep.swift")) { stream in + let fs = tester.fs + let root = testWorkspace.sourceRoot + + try await fs.writeFileContents(root.join("DepPackage/transitivedep.swift")) { stream in stream <<< """ public let transitiveDependencyMessage = "Hello from host tool transitive dependency!" """ } - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("DepPackage/dep.swift")) { stream in + try await fs.writeFileContents(root.join("DepPackage/dep.swift")) { stream in stream <<< """ import TransitivePackageDep @@ -361,7 +322,7 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { """ } - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("HostToolsPackage/tool.swift")) { stream in + try await fs.writeFileContents(root.join("HostToolsPackage/tool.swift")) { stream in stream <<< """ import PackageDep @@ -374,13 +335,65 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { """ } - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("HostToolsPackage/lib.swift")) { stream in + try await fs.writeFileContents(root.join("HostToolsPackage/lib.swift")) { stream in stream <<< """ public class MyClass {} """ } + try await body(tester, testWorkspace) + } + } + + func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: RunDestinationInfo) async throws { + let testProject = try await TestProject( + "aProject", + groupTree: TestGroup("Foo", children: [ + TestFile("frame.swift"), + TestFile("app.swift") + ]), buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SWIFT_VERSION": swiftVersion, + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "CODE_SIGNING_ALLOWED": "NO", + ]), + ], + targets: [ + TestStandardTarget("Framework", type: .framework, buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", + ]), + ], buildPhases: [ + TestSourcesBuildPhase(["frame.swift"]), + TestFrameworksBuildPhase([ + TestBuildFile(.target("HostToolClientLibProduct")) + ]), + ], dependencies: [ + "HostToolClientLibProduct" + ]), + TestStandardTarget("App", type: .application, buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", + ]), + ], buildPhases: [ + TestSourcesBuildPhase(["app.swift"]), + ], dependencies: [ + "Framework" + ]), + ] + ) + + try await withHostToolsPackages(clients: testProject) { tester, testWorkspace in try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/frame.swift")) { stream in stream <<< """ @@ -441,169 +454,8 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { @Test(.requireSDKs(.macOS)) func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparationForPackage() async throws { - try await withTemporaryDirectory { tmpDirPath async throws -> Void in - let depPackage = try await TestPackageProject( - "DepPackage", - groupTree: TestGroup("Foo", children: [ - TestFile("transitivedep.swift"), - TestFile("dep.swift"), - ]), - buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SWIFT_VERSION": swiftVersion, - "GENERATE_INFOPLIST_FILE": "YES", - "PRODUCT_NAME": "$(TARGET_NAME)", - "CODE_SIGNING_ALLOWED": "NO", - ]), - ], - targets: [ - TestStandardTarget("TransitivePackageDep", type: .objectFile, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ], - impartedBuildProperties: - TestImpartedBuildProperties( - buildSettings: [ - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "IMPARTED_SETTINGS" - ]) - ), - ], buildPhases: [ - TestSourcesBuildPhase(["transitivedep.swift"]) - ]), - TestStandardTarget("PackageDep", type: .staticLibrary, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ - TestSourcesBuildPhase(["dep.swift"]), - TestFrameworksBuildPhase([ - TestBuildFile(.target("TransitivePackageDep")) - ]) - ], dependencies: [ - "TransitivePackageDep" - ]), - TestPackageProductTarget("PackageDepProduct", frameworksBuildPhase: - TestFrameworksBuildPhase([ - TestBuildFile(.target("PackageDep")), - ] - ), buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], dependencies: [ - "PackageDep" - ]), - ]) - - let hostToolsPackage = try await TestPackageProject( - "HostToolsPackage", - groupTree: TestGroup("Foo", children: [ - TestFile("tool.swift"), - TestFile("lib.swift"), - ]), - buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SWIFT_VERSION": swiftVersion, - "GENERATE_INFOPLIST_FILE": "YES", - "PRODUCT_NAME": "$(TARGET_NAME)", - "CODE_SIGNING_ALLOWED": "NO", - ]), - ], - targets: [ - TestStandardTarget("HostTool", type: .hostBuildTool, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - ]) - ], buildPhases: [ - TestSourcesBuildPhase(["tool.swift"]), - TestFrameworksBuildPhase([TestBuildFile(.target("PackageDepProduct"))]) - ], dependencies: [ - "PackageDepProduct" - ]), - TestStandardTarget("HostToolClientLib", type: .objectFile, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ - TestSourcesBuildPhase(["lib.swift"]), - ], dependencies: [ - "HostTool" - ]), - TestPackageProductTarget("HostToolClientLibProduct", frameworksBuildPhase: - TestFrameworksBuildPhase([TestBuildFile(.target("HostToolClientLib"))] - ), buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], dependencies: [ - "HostToolClientLib" - ]), - ]) - - let testWorkspace = TestWorkspace("aWorkspace", sourceRoot: tmpDirPath.join("Test"), projects: [depPackage, hostToolsPackage]) - let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false, systemInfo: .init(operatingSystemVersion: Version(99, 98, 97), productBuildVersion: "99A98", nativeArchitecture: Architecture.host.stringValue ?? "undefined_arch")) - - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("DepPackage/transitivedep.swift")) { stream in - stream <<< - """ - public let transitiveDependencyMessage = "Hello from host tool transitive dependency!" - """ - } - - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("DepPackage/dep.swift")) { stream in - stream <<< - """ - import TransitivePackageDep - - public let dependencyMessage = "Hello from host tool dependency! " + transitiveDependencyMessage - #if !IMPARTED_SETTINGS - #error("settings not imparted") - #endif - """ - } - - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("HostToolsPackage/tool.swift")) { stream in - stream <<< - """ - import PackageDep - - @main struct Foo { - static func main() { - print("Hello from host tool! " + dependencyMessage) - } - } - """ - } - - try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("HostToolsPackage/lib.swift")) { stream in - stream <<< - """ - public class MyClass {} - """ - } - - try await tester.checkIndexBuild(prepareTargets: hostToolsPackage.targets.map(\.guid), workspaceOperation: false, runDestination: .anyMac, persistent: true) { results in + try await withHostToolsPackages { tester, testWorkspace in + try await tester.checkIndexBuild(prepareTargets: testWorkspace.projects[1].targets.map(\.guid), workspaceOperation: false, runDestination: .anyMac, persistent: true) { results in results.checkNoDiagnostics() results.checkTaskExists(.matchTargetName("HostTool"), .matchRuleType("Ld")) From 209579fd2edfa2e1824c1f9c55fcd271be83d9bc Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 25 Jul 2025 12:46:06 +0100 Subject: [PATCH 2/6] [test] Parameterize two host tools index tests Use Swift Testing parameterization to test different run destinations. --- .../HostBuildToolBuildOperationTests.swift | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift index 56dd070a..e285f4a0 100644 --- a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift @@ -158,21 +158,6 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.macOS)) - func hostToolsAndDependenciesAreBuiltDuringIndexingPreparation_Mac() async throws { - try await testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: .anyMac) - } - - @Test(.requireSDKs(.macOS, .iOS)) - func hostToolsAndDependenciesAreBuiltDuringIndexingPreparation_MacCatalyst() async throws { - try await testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: .anyMacCatalyst) - } - - @Test(.requireSDKs(.macOS, .iOS)) - func hostToolsAndDependenciesAreBuiltDuringIndexingPreparation_iOS() async throws { - try await testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: .anyiOSDevice) - } - private func withHostToolsPackages( clients: TestProject..., body: (BuildOperationTester, TestWorkspace) async throws -> Void @@ -346,6 +331,7 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { } } + @Test(.requireSDKs(.macOS, .iOS), arguments: [RunDestinationInfo.anyMac, .anyMacCatalyst, .anyiOSDevice]) func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparation(destination: RunDestinationInfo) async throws { let testProject = try await TestProject( "aProject", @@ -452,10 +438,10 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.macOS)) - func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparationForPackage() async throws { + @Test(.requireSDKs(.macOS, .iOS), arguments: [RunDestinationInfo.anyMac, .anyMacCatalyst, .anyiOSDevice]) + func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparationForPackage(destination: RunDestinationInfo) async throws { try await withHostToolsPackages { tester, testWorkspace in - try await tester.checkIndexBuild(prepareTargets: testWorkspace.projects[1].targets.map(\.guid), workspaceOperation: false, runDestination: .anyMac, persistent: true) { results in + try await tester.checkIndexBuild(prepareTargets: testWorkspace.projects[1].targets.map(\.guid), workspaceOperation: false, runDestination: destination, persistent: true) { results in results.checkNoDiagnostics() results.checkTaskExists(.matchTargetName("HostTool"), .matchRuleType("Ld")) From d9750b002a555443f588826f3a502268852322ff Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 25 Jul 2025 12:46:06 +0100 Subject: [PATCH 3/6] [test] Set `SDKROOT: auto` at project level for `withHostToolsPackages` This better matches what we actually generate for packages. Also set all available platforms as supported. --- .../HostBuildToolBuildOperationTests.swift | 53 ++++--------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift index e285f4a0..4126dd07 100644 --- a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift @@ -176,16 +176,15 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { "GENERATE_INFOPLIST_FILE": "YES", "PRODUCT_NAME": "$(TARGET_NAME)", "CODE_SIGNING_ALLOWED": "NO", + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "$(AVAILABLE_PLATFORMS)", ]), ], targets: [ TestStandardTarget("TransitivePackageDep", type: .objectFile, buildConfigurations: [ TestBuildConfiguration( "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ], + buildSettings: [:], impartedBuildProperties: TestImpartedBuildProperties( buildSettings: [ @@ -195,14 +194,7 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["transitivedep.swift"]) ]), - TestStandardTarget("PackageDep", type: .staticLibrary, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ + TestStandardTarget("PackageDep", type: .staticLibrary, buildPhases: [ TestSourcesBuildPhase(["dep.swift"]), TestFrameworksBuildPhase([ TestBuildFile(.target("TransitivePackageDep")) @@ -214,14 +206,7 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { TestFrameworksBuildPhase([ TestBuildFile(.target("PackageDep")), ] - ), buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], dependencies: [ + ), dependencies: [ "PackageDep" ]), ]) @@ -240,43 +225,25 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { "GENERATE_INFOPLIST_FILE": "YES", "PRODUCT_NAME": "$(TARGET_NAME)", "CODE_SIGNING_ALLOWED": "NO", + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "$(AVAILABLE_PLATFORMS)", ]), ], targets: [ - TestStandardTarget("HostTool", type: .hostBuildTool, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - ]) - ], buildPhases: [ + TestStandardTarget("HostTool", type: .hostBuildTool, buildPhases: [ TestSourcesBuildPhase(["tool.swift"]), TestFrameworksBuildPhase([TestBuildFile(.target("PackageDepProduct"))]) ], dependencies: [ "PackageDepProduct" ]), - TestStandardTarget("HostToolClientLib", type: .objectFile, buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], buildPhases: [ + TestStandardTarget("HostToolClientLib", type: .objectFile, buildPhases: [ TestSourcesBuildPhase(["lib.swift"]), ], dependencies: [ "HostTool" ]), TestPackageProductTarget("HostToolClientLibProduct", frameworksBuildPhase: TestFrameworksBuildPhase([TestBuildFile(.target("HostToolClientLib"))] - ), buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "SDKROOT": "auto", - "SUPPORTED_PLATFORMS": "macosx iphoneos iphonesimulator", - ]), - ], dependencies: [ + ), dependencies: [ "HostToolClientLib" ]), ]) From 0ce0d2b3e6e5c0b64550aedbce58c2395961f8a6 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 25 Jul 2025 12:46:06 +0100 Subject: [PATCH 4/6] [test] Add same-package host tool dependency to `withHostToolsPackages` This exposes the issue where `isTargetSuitableForPlatformForIndex` returns `false` in the target and package build. Also change `testHostToolsAndDependenciesAreBuiltDuringIndexingPreparationForPackage` to use a dependency package and test both the target and package build descriptions. --- .../SWBTestSupport/BuildOperationTester.swift | 2 + .../HostBuildToolBuildOperationTests.swift | 72 +++++++++++++++++-- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/Sources/SWBTestSupport/BuildOperationTester.swift b/Sources/SWBTestSupport/BuildOperationTester.swift index 5294b027..17c93778 100644 --- a/Sources/SWBTestSupport/BuildOperationTester.swift +++ b/Sources/SWBTestSupport/BuildOperationTester.swift @@ -1553,6 +1553,7 @@ package final class BuildOperationTester { /// Construct 'prepare' index build operation, and test the result. package func checkIndexBuild( prepareTargets: [String], + buildTargets: [any TestTarget]? = nil, workspaceOperation: Bool = true, runDestination: RunDestinationInfo? = nil, persistent: Bool = false, @@ -1561,6 +1562,7 @@ package final class BuildOperationTester { ) async throws -> T { let buildRequest = try Self.buildRequestForIndexOperation( workspace: workspace, + buildTargets: buildTargets, prepareTargets: prepareTargets, workspaceOperation: workspaceOperation, runDestination: runDestination, diff --git a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift index 4126dd07..689bf872 100644 --- a/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/HostBuildToolBuildOperationTests.swift @@ -214,6 +214,7 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { let hostToolsPackage = try await TestPackageProject( "HostToolsPackage", groupTree: TestGroup("Foo", children: [ + TestFile("tooldep.swift"), TestFile("tool.swift"), TestFile("lib.swift"), ]), @@ -230,11 +231,18 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { ]), ], targets: [ + TestStandardTarget("HostToolDep", type: .objectFile, buildPhases: [ + TestSourcesBuildPhase(["tooldep.swift"]), + ]), TestStandardTarget("HostTool", type: .hostBuildTool, buildPhases: [ TestSourcesBuildPhase(["tool.swift"]), - TestFrameworksBuildPhase([TestBuildFile(.target("PackageDepProduct"))]) + TestFrameworksBuildPhase([ + TestBuildFile(.target("PackageDepProduct")), + TestBuildFile(.target("HostToolDep")), + ]), ], dependencies: [ - "PackageDepProduct" + "PackageDepProduct", + "HostToolDep", ]), TestStandardTarget("HostToolClientLib", type: .objectFile, buildPhases: [ TestSourcesBuildPhase(["lib.swift"]), @@ -274,14 +282,22 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { """ } + try await fs.writeFileContents(root.join("HostToolsPackage/tooldep.swift")) { stream in + stream <<< + """ + public let samePackageMsg = "Hello from host tool same-package dependency!" + """ + } + try await fs.writeFileContents(root.join("HostToolsPackage/tool.swift")) { stream in stream <<< """ import PackageDep + import HostToolDep @main struct Foo { static func main() { - print("Hello from host tool! " + dependencyMessage) + print("Hello from host tool! " + dependencyMessage + samePackageMsg) } } """ @@ -405,15 +421,57 @@ fileprivate struct HostBuildToolBuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.macOS, .iOS), arguments: [RunDestinationInfo.anyMac, .anyMacCatalyst, .anyiOSDevice]) - func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparationForPackage(destination: RunDestinationInfo) async throws { - try await withHostToolsPackages { tester, testWorkspace in - try await tester.checkIndexBuild(prepareTargets: testWorkspace.projects[1].targets.map(\.guid), workspaceOperation: false, runDestination: destination, persistent: true) { results in + @Test(.requireSDKs(.macOS, .iOS), arguments: [RunDestinationInfo.anyMac, .anyMacCatalyst, .anyiOSDevice], [true, false]) + func testHostToolsAndDependenciesAreBuiltDuringIndexingPreparationForPackage( + destination: RunDestinationInfo, targetBuild: Bool + ) async throws { + let clientPackage = try await TestPackageProject( + "ClientPackage", + groupTree: TestGroup("Client", children: [ + TestFile("main.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "SWIFT_VERSION": swiftVersion, + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "CODE_SIGNING_ALLOWED": "NO", + "SDKROOT": "auto", + "SUPPORTED_PLATFORMS": "$(AVAILABLE_PLATFORMS)", + ]), + ], + targets: [ + TestStandardTarget("HostToolClient", type: .objectFile, buildPhases: [ + TestSourcesBuildPhase(["main.swift"]), + TestFrameworksBuildPhase([TestBuildFile(.target("HostToolClientLibProduct"))]), + ], dependencies: [ + "HostToolClientLibProduct" + ]), + ]) + + try await withHostToolsPackages(clients: clientPackage) { tester, testWorkspace in + try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("ClientPackage/main.swift")) { stream in + stream <<< + """ + print("Hello, world!") + """ + } + + let clientTarget = try #require(clientPackage.targets.first) + try await tester.checkIndexBuild( + prepareTargets: [clientTarget.guid], + buildTargets: targetBuild ? [clientTarget] : nil, + workspaceOperation: false, runDestination: destination, + persistent: true + ) { results in results.checkNoDiagnostics() results.checkTaskExists(.matchTargetName("HostTool"), .matchRuleType("Ld")) try results.checkTask(.matchTargetName("HostTool"), .matchRuleType(ProductPlan.preparedForIndexPreCompilationRuleName)) { task in try results.checkTaskFollows(task, .matchTargetName("PackageDep"), .matchRuleType("Libtool")) + try results.checkTaskFollows(task, .matchTargetName("HostToolDep"), .matchRuleType("SwiftDriver Compilation")) } } } From d21cf3102375805b93e1664cc94486fc4dd16c3e Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 25 Jul 2025 12:46:06 +0100 Subject: [PATCH 5/6] Only check `isTargetSuitableForPlatformForIndex` for workspace description For the package and target build description we can end up incorrectly dropping build tool dependencies since not all clients pass in the correct dependency information. It's not clear that we actually gain much from doing this check for the target and package build descriptions though, its primary purpose is to avoid configuring unsupported targets in the workspace build description where we try to configure for all available platforms. As such, switch to only checking for the workspace build description. Note we don't encounter this issue in the workspace case since there we override the build parameters for host build tools to always build for the host platform. rdar://152012769 --- Sources/SWBCore/DependencyResolution.swift | 22 ++++++------------- .../IndexTargetDependencyResolverTests.swift | 8 +++++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Sources/SWBCore/DependencyResolution.swift b/Sources/SWBCore/DependencyResolution.swift index 3316db1b..1357d234 100644 --- a/Sources/SWBCore/DependencyResolution.swift +++ b/Sources/SWBCore/DependencyResolution.swift @@ -623,23 +623,15 @@ extension SpecializationParameters { /// Determines whether a target should be configured for the given platform in the index arena. /// - /// The arena is used for two purposes: - /// 1. To retrieve settings for a given target - /// 2. To produce products of source dependencies for compilation purposes (it does not produce binaries) + /// When building a workspace build description, we configure for all possible platforms. As such, + /// we want to avoid unnecessarily configuring targets for unsupported platforms. When building a + /// target or package description, we are only configuring for a single platform and can therefore + /// avoid this check since we assume any dependency will necessary. /// - /// Thus, in general if a target doesn't support a platform, we don't want to configure it for that platform. If a - /// dependency is not supported for the platform of the dependent, presumably the dependent will not be able - /// to use its products for compilation purposes, since the source products will be put in a different platform - /// directory and/or they will not be usable by the dependent (e.g. the module will not be importable from a - /// different platform). If the dependency was intended to be usable from that platform for compilation purposes, - /// it would be a supported platform. - /// - /// There's an exception for this for a dependent host tool, which are required for compilation and must therefore - /// be configured (and registered as a dependency) regardless. + /// Note there's an exception for this for host build tools, which are required for compilation + /// and must therefore be configured (and registered as a dependency) regardless nonisolated func isTargetSuitableForPlatformForIndex(_ target: Target, parameters: BuildParameters, imposedParameters: SpecializationParameters?, dependencies: OrderedSet? = nil) -> Bool { - if !buildRequest.enableIndexBuildArena { - return true - } + guard buildRequest.buildsIndexWorkspaceDescription else { return true } // Host tools case, always supported we'll override the parameters with that of the host regardless. if target.isHostBuildTool || dependencies?.contains(where: { $0.target.isHostBuildTool }) == true { diff --git a/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift b/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift index c787f3f7..72009cc8 100644 --- a/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift +++ b/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift @@ -949,8 +949,12 @@ import SWBUtil #expect(results.targets(packageProduct).map{ results.targetNameAndPlatform($0) } == ["PackageLibProduct-iphoneos"]) #expect(results.targets(unreferencedPackageLib).map{ results.targetNameAndPlatform($0) } == ["UnreferencedPackageLib-iphoneos"]) - try results.checkDependencies(of: .init(packageLib2, "iphoneos"), are: []) - + switch results.graphType { + case .dependency: + try results.checkDependencies(of: packageLib2, are: [.init(packageTool, "macos")]) + case .linkage: + try results.checkDependencies(of: packageLib2, are: []) + } results.delegate.checkNoDiagnostics() } } From fed85363632fd4bcd48a6c03a0063652556ab44c Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 25 Jul 2025 12:46:06 +0100 Subject: [PATCH 6/6] Remove dependency check from `isTargetSuitableForPlatformForIndex` This gets applied pretty inconsistently, only 1 client actually passes the correct dependency information. We ought to be able to rely on the host platform being imposed for the workspace build description, so it shouldn't be necessary. --- Sources/SWBCore/DependencyResolution.swift | 8 +++++--- Sources/SWBCore/LinkageDependencyResolver.swift | 13 ++++--------- Sources/SWBCore/TargetDependencyResolver.swift | 8 ++++---- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Sources/SWBCore/DependencyResolution.swift b/Sources/SWBCore/DependencyResolution.swift index 1357d234..ccad7d11 100644 --- a/Sources/SWBCore/DependencyResolution.swift +++ b/Sources/SWBCore/DependencyResolution.swift @@ -630,11 +630,13 @@ extension SpecializationParameters { /// /// Note there's an exception for this for host build tools, which are required for compilation /// and must therefore be configured (and registered as a dependency) regardless - nonisolated func isTargetSuitableForPlatformForIndex(_ target: Target, parameters: BuildParameters, imposedParameters: SpecializationParameters?, dependencies: OrderedSet? = nil) -> Bool { + nonisolated func isTargetSuitableForPlatformForIndex(_ target: Target, parameters: BuildParameters, imposedParameters: SpecializationParameters?) -> Bool { guard buildRequest.buildsIndexWorkspaceDescription else { return true } - // Host tools case, always supported we'll override the parameters with that of the host regardless. - if target.isHostBuildTool || dependencies?.contains(where: { $0.target.isHostBuildTool }) == true { + // Host tools case, always supported since we'll override the parameters with that of the + // host regardless. Any dependencies will have the host platform imposed on them through + // `imposedParameters`. + if target.isHostBuildTool { return true } diff --git a/Sources/SWBCore/LinkageDependencyResolver.swift b/Sources/SWBCore/LinkageDependencyResolver.swift index e5b9fcd0..9c648201 100644 --- a/Sources/SWBCore/LinkageDependencyResolver.swift +++ b/Sources/SWBCore/LinkageDependencyResolver.swift @@ -122,8 +122,7 @@ actor LinkageDependencyResolver { if Task.isCancelled { return } let configuredTarget = topLevelTargetsToDiscover[i] let imposedParameters = resolver.specializationParameters(configuredTarget, workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext) - let dependenciesOnPath = LinkageDependencies() - await linkageDependencies(for: configuredTarget, imposedParameters: imposedParameters, dependenciesOnPath: dependenciesOnPath) + await linkageDependencies(for: configuredTarget, imposedParameters: imposedParameters) } } @@ -141,7 +140,7 @@ actor LinkageDependencyResolver { private var dependenciesPerTarget = [ConfiguredTarget: [ResolvedTargetDependency]]() private var visitedDiscoveredTargets = Set() - private func linkageDependencies(for configuredTarget: ConfiguredTarget, imposedParameters: SpecializationParameters?, dependenciesOnPath: LinkageDependencies) async { + private func linkageDependencies(for configuredTarget: ConfiguredTarget, imposedParameters: SpecializationParameters?) async { // Track that we have visited this target. let visited = !visitedDiscoveredTargets.insert(configuredTarget).inserted @@ -167,7 +166,7 @@ actor LinkageDependencyResolver { return nil } let buildParameters = resolver.buildParametersByTarget[target] ?? configuredTarget.parameters - if await !resolver.isTargetSuitableForPlatformForIndex(target, parameters: buildParameters, imposedParameters: imposedParameters, dependencies: dependenciesOnPath.path) { + if await !resolver.isTargetSuitableForPlatformForIndex(target, parameters: buildParameters, imposedParameters: imposedParameters) { return nil } let effectiveImposedParameters = imposedParameters?.effectiveParameters(target: configuredTarget, dependency: ConfiguredTarget(parameters: buildParameters, target: target), dependencyResolver: resolver) @@ -195,7 +194,7 @@ actor LinkageDependencyResolver { } else { imposedParametersForDependency = resolver.specializationParameters(dependency.target, workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext) } - await self.linkageDependencies(for: dependency.target, imposedParameters: imposedParametersForDependency, dependenciesOnPath: dependenciesOnPath) + await self.linkageDependencies(for: dependency.target, imposedParameters: imposedParametersForDependency) } } @@ -657,7 +656,3 @@ private extension Path { return basenameWithoutSuffix.nilIfEmpty } } - -fileprivate actor LinkageDependencies { - var path: OrderedSet = [] -} diff --git a/Sources/SWBCore/TargetDependencyResolver.swift b/Sources/SWBCore/TargetDependencyResolver.swift index 018abdc2..e1f85acb 100644 --- a/Sources/SWBCore/TargetDependencyResolver.swift +++ b/Sources/SWBCore/TargetDependencyResolver.swift @@ -680,7 +680,7 @@ fileprivate extension TargetDependencyResolver { } // Add the discovered info. - let discoveredInfo = await computeDiscoveredTargetInfo(for: configuredTarget, imposedParameters: imposedParameters, dependencyPath: nil, resolver: resolver) + let discoveredInfo = await computeDiscoveredTargetInfo(for: configuredTarget, imposedParameters: imposedParameters, resolver: resolver) discoveredTargets[configuredTarget] = discoveredInfo // If we have no dependencies, we are done. @@ -742,7 +742,7 @@ fileprivate extension TargetDependencyResolver { discoveredInfo = info } else { if resolver.makeAggregateTargetsTransparentForSpecialization { - discoveredInfo = await computeDiscoveredTargetInfo(for: configuredTarget, imposedParameters: imposedParameters, dependencyPath: dependencyPath, resolver: resolver) + discoveredInfo = await computeDiscoveredTargetInfo(for: configuredTarget, imposedParameters: imposedParameters, resolver: resolver) } else { var immediateDependencies = [ResolvedTargetDependency]() var packageProductDependencies = [PackageProductTarget]() @@ -820,14 +820,14 @@ fileprivate extension TargetDependencyResolver { } /// Discover the info for a configured target with the given imposed parameters. - private func computeDiscoveredTargetInfo(for configuredTarget: ConfiguredTarget, imposedParameters: SpecializationParameters?, dependencyPath: OrderedSet?, resolver: isolated DependencyResolver) async -> DiscoveredTargetInfo { + private func computeDiscoveredTargetInfo(for configuredTarget: ConfiguredTarget, imposedParameters: SpecializationParameters?, resolver: isolated DependencyResolver) async -> DiscoveredTargetInfo { var immediateDependencies = [ResolvedTargetDependency]() var packageProductDependencies = [PackageProductTarget]() for dependency in resolver.explicitDependencies(for: configuredTarget) { if let asPackageProduct = dependency as? PackageProductTarget { packageProductDependencies.append(asPackageProduct) } else { - if !resolver.isTargetSuitableForPlatformForIndex(dependency, parameters: configuredTarget.parameters, imposedParameters: imposedParameters, dependencies: dependencyPath) { + if !resolver.isTargetSuitableForPlatformForIndex(dependency, parameters: configuredTarget.parameters, imposedParameters: imposedParameters) { continue }