From 829efad9793eb325951fbe39da4fd6ca9b2904fd Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:32:35 -0400 Subject: [PATCH] Fix crash in macros caused by mixing prebuilts and source. (#9261) There is an issue when a macro that uses prebuilts uses a library that also depends on the swift-syntax library. This is commonly used to share utility code between macros. However, we currently can't tell whether the library will only be built for the host so it can not take advantage of the prebuilts. When a macro which is using prebuilts uses that library, we are seeing crashes in the macro caused when the library and the swift-syntax it uses is built for debug which is then linked with the macro which uses the swift-syntax prebuilts which are built for release. For now, this change turns off the prebuilts for macros that use such a library to avoid the crash. We'll investigate making the use of prebuilts conditional on the build settings in a future release. To be able to implement this change, we need to be able to detect the downstream swift-syntax dependency. We move the insertion of the settings for the prebuilts from Module creation time until after the ResolvedModuleBuilder is created so that we can make the changes there before the final ResolvedModules are created. --- .../PackageGraph/ModulesGraph+Loading.swift | 129 ++++++++++++++---- Sources/PackageLoading/PackageBuilder.swift | 83 ----------- Sources/PackageModel/CMakeLists.txt | 1 + Sources/PackageModel/Module/Module.swift | 38 +++++- .../PackageModel/Module/PrebuiltLibrary.swift | 51 +++++++ Sources/Workspace/Workspace.swift | 2 - Tests/BuildTests/BuildPlanTests.swift | 83 +++++++++-- .../PackageBuilderTests.swift | 2 - Tests/WorkspaceTests/PrebuiltsTests.swift | 89 +++++++++++- 9 files changed, 357 insertions(+), 121 deletions(-) create mode 100644 Sources/PackageModel/Module/PrebuiltLibrary.swift diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 2ea14951129..a269cd0e478 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -30,7 +30,7 @@ extension ModulesGraph { requiredDependencies: [PackageReference] = [], unsafeAllowedPackages: Set = [], binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]], - prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], // Product name to library mapping + prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], // Package -> Product name -> library shouldCreateMultipleTestProducts: Bool = false, createREPLProduct: Bool = false, customPlatformsRegistry: PlatformRegistry? = .none, @@ -167,7 +167,6 @@ extension ModulesGraph { path: packagePath, additionalFileRules: additionalFileRules, binaryArtifacts: binaryArtifacts[node.identity] ?? [:], - prebuilts: prebuilts, shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, testEntryPointPath: testEntryPointPath, createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false, @@ -214,7 +213,7 @@ extension ModulesGraph { let rootPackages = resolvedPackages.filter { root.manifests.values.contains($0.manifest) } checkAllDependenciesAreUsed( packages: resolvedPackages, - rootPackages, + rootPackages: rootPackages, prebuilts: prebuilts, observabilityScope: observabilityScope ) @@ -231,7 +230,7 @@ extension ModulesGraph { private func checkAllDependenciesAreUsed( packages: IdentifiableSet, - _ rootPackages: [ResolvedPackage], + rootPackages: [ResolvedPackage], prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], observabilityScope: ObservabilityScope ) { @@ -311,7 +310,7 @@ private func checkAllDependenciesAreUsed( // We check if any of the products of this dependency is guarded by a trait. let traitGuarded = traitGuardedProductDependencies.contains(product.name) // Consider prebuilts as used - let prebuilt = prebuilts[dependency.identity]?.keys.contains(product.name) ?? false + let prebuilt = prebuilts[dependency.identity]?[product.name] != nil return usedByPackage || traitGuarded || prebuilt } @@ -729,24 +728,6 @@ private func createResolvedPackages( // Establish product dependencies. for case .product(let productRef, let conditions) in moduleBuilder.module.dependencies { - if let package = productRef.package, prebuilts[.plain(package)]?[productRef.name] != nil { - // See if we're using a prebuilt instead - if moduleBuilder.module.type == .macro { - continue - } else if moduleBuilder.module.type == .test { - // use prebuilt if this is a test that depends a macro target - // these are guaranteed built for host - if moduleBuilder.module.dependencies.contains(where: { dep in - guard let module = dep.module else { - return false - } - return module.type == .macro - }) { - continue - } - } - } - // Find the product in this package's dependency products. // Look it up by ID if module aliasing is used, otherwise by name. let product = lookupByProductIDs ? productDependencyMap[productRef.identity] : @@ -800,6 +781,91 @@ private func createResolvedPackages( } } + // Prebuilts + for packageBuilder in packageBuilders { + for moduleBuilder in packageBuilder.modules { + // Currently we only support prebuilts for macros and their tests + switch moduleBuilder.module.type { + case .macro: + // Skip if the module has a dependency that depends on a prebuilt + // This is causing a mix of release and debug modes which is causing + // macros to crash. + // TODO: Find a way to build those dependencies with the prebuilts on host + if moduleBuilder.dependencies.contains(where: { + switch $0 { + case .module(let moduleBuilder, conditions: _): + return moduleBuilder.recursiveDependenciesContains(where: { + switch $0 { + case .product(let productBuilder, conditions: _): + return prebuilts[productBuilder.packageBuilder.package.identity]?[productBuilder.product.name] != nil + case .module: + return false + } + }) + case .product(let productBuilder, conditions: _): + return productBuilder.moduleBuilders.contains(where: { + $0.recursiveDependenciesContains(where: { + switch $0 { + case .product(let productBuilder, conditions: _): + return prebuilts[productBuilder.packageBuilder.package.identity]?[productBuilder.product.name] != nil + case .module: + return false + } + }) + }) + } + }) { + // Skip + continue + } + case .test: + // Check for macro tests + if !moduleBuilder.dependencies.contains(where: { + switch $0 { + case .module(let depModuleBuilder, conditions: _): + return depModuleBuilder.module.type == .macro + case .product: + return false + } + }) { + // Skip + continue + } + default: + // Skip + continue + } + + var prebuiltLibraries: [PrebuiltLibrary] = [] + for dep in moduleBuilder.dependencies { + switch dep { + case .product(let productBuilder, conditions: _): + if let prebuilt = prebuilts[productBuilder.packageBuilder.package.identity]?[productBuilder.product.name] { + // Use the prebuilt + if !prebuiltLibraries.contains(where: { $0.libraryName == prebuilt.libraryName }) { + prebuiltLibraries.append(prebuilt) + } + } + case .module: + break + } + } + + for prebuiltLibrary in prebuiltLibraries { + moduleBuilder.module.use(prebuiltLibrary: prebuiltLibrary) + + moduleBuilder.dependencies = moduleBuilder.dependencies.filter({ + switch $0 { + case .product(let productBuilder, conditions: _): + return prebuilts[productBuilder.packageBuilder.package.identity]?[productBuilder.product.name] == nil + case .module: + return true + } + }) + } + } + } + // If a module with similar name was encountered before, we emit a diagnostic. if foundDuplicateModule { var duplicateModules = [String: [Package]]() @@ -1337,6 +1403,23 @@ private final class ResolvedModuleBuilder: ResolvedBuilder { /// The module dependencies of this module. var dependencies: [Dependency] = [] + func recursiveDependenciesContains(where check: (Dependency) -> Bool) -> Bool { + dependencies.contains(where: { + if check($0) { + return true + } else { + switch $0 { + case .module(let moduleBuilder, conditions: _): + return moduleBuilder.recursiveDependenciesContains(where: check) + case .product(let productBuilder, conditions: _): + return productBuilder.moduleBuilders.contains(where: { + $0.recursiveDependenciesContains(where: check) + }) + } + } + }) + } + /// The defaultLocalization for this package var defaultLocalization: String? = nil diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 08535c82807..a1e055508b0 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -279,48 +279,6 @@ public struct BinaryArtifact { } } -/// A structure representing a prebuilt library to be used instead of a source dependency -public struct PrebuiltLibrary { - /// The package identity. - public let identity: PackageIdentity - - /// The name of the binary target the artifact corresponds to. - public let libraryName: String - - /// The path to the extracted prebuilt artifacts - public let path: AbsolutePath - - /// The path to the checked out source - public let checkoutPath: AbsolutePath? - - /// The products in the library - public let products: [String] - - /// The include path relative to the checkouts dir - public let includePath: [RelativePath]? - - /// The C modules that need their includes directory added to the include path - public let cModules: [String] - - public init( - identity: PackageIdentity, - libraryName: String, - path: AbsolutePath, - checkoutPath: AbsolutePath?, - products: [String], - includePath: [RelativePath]? = nil, - cModules: [String] = [] - ) { - self.identity = identity - self.libraryName = libraryName - self.path = path - self.checkoutPath = checkoutPath - self.products = products - self.includePath = includePath - self.cModules = cModules - } -} - /// Helper for constructing a package following the convention system. /// /// The 'builder' here refers to the builder pattern and not any build system @@ -348,9 +306,6 @@ public final class PackageBuilder { /// Information concerning the different downloaded or local (archived) binary target artifacts. private let binaryArtifacts: [String: BinaryArtifact] - /// Prebuilts that may referenced from this package's targets - private let prebuilts: [PackageIdentity: [Product.ID: PrebuiltLibrary]] - /// Create multiple test products. /// /// If set to true, one test product will be created for each test target. @@ -407,7 +362,6 @@ public final class PackageBuilder { path: AbsolutePath, additionalFileRules: [FileRuleDescription], binaryArtifacts: [String: BinaryArtifact], - prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], shouldCreateMultipleTestProducts: Bool = false, testEntryPointPath: AbsolutePath? = nil, warnAboutImplicitExecutableTargets: Bool = true, @@ -422,7 +376,6 @@ public final class PackageBuilder { self.packagePath = path self.additionalFileRules = additionalFileRules self.binaryArtifacts = binaryArtifacts - self.prebuilts = prebuilts self.shouldCreateMultipleTestProducts = shouldCreateMultipleTestProducts self.testEntryPointPath = testEntryPointPath self.createREPLProduct = createREPLProduct @@ -1407,42 +1360,6 @@ public final class PackageBuilder { table.add(assignment, for: .SWIFT_ACTIVE_COMPILATION_CONDITIONS) } - // Add in flags for prebuilts if the target is a macro or a macro test. - // Currently we only support prebuilts for macros. - if target.type == .macro || target.isMacroTest(in: manifest) { - let prebuiltLibraries: [String: PrebuiltLibrary] = target.dependencies.reduce(into: .init()) { - guard case let .product(name: name, package: package, moduleAliases: _, condition: _) = $1, - let package = package, - let prebuilt = prebuilts[.plain(package)]?[name] - else { - return - } - - $0[prebuilt.libraryName] = prebuilt - } - - for prebuilt in prebuiltLibraries.values { - let lib = prebuilt.path.appending(components: ["lib", "lib\(prebuilt.libraryName).a"]).pathString - var ldFlagsAssignment = BuildSettings.Assignment() - ldFlagsAssignment.values = [lib] - table.add(ldFlagsAssignment, for: .OTHER_LDFLAGS) - - var includeDirs: [AbsolutePath] = [prebuilt.path.appending(component: "Modules")] - if let checkoutPath = prebuilt.checkoutPath, let includePath = prebuilt.includePath { - for includeDir in includePath { - includeDirs.append(checkoutPath.appending(includeDir)) - } - } else { - for cModule in prebuilt.cModules { - includeDirs.append(prebuilt.path.appending(components: "include", cModule)) - } - } - var includeAssignment = BuildSettings.Assignment() - includeAssignment.values = includeDirs.map({ "-I\($0.pathString)" }) - table.add(includeAssignment, for: .OTHER_SWIFT_FLAGS) - } - } - return table } diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index aa8848b4155..5d2ffa9d7f2 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(PackageModel Module/BinaryModule.swift Module/ClangModule.swift Module/PluginModule.swift + Module/PrebuiltLibrary.swift Module/SwiftModule.swift Module/SystemLibraryModule.swift Module/Module.swift diff --git a/Sources/PackageModel/Module/Module.swift b/Sources/PackageModel/Module/Module.swift index 7001c244212..39f0dce69e7 100644 --- a/Sources/PackageModel/Module/Module.swift +++ b/Sources/PackageModel/Module/Module.swift @@ -194,8 +194,44 @@ public class Module { return false } + public func use(prebuiltLibrary: PrebuiltLibrary) { + dependencies = dependencies.filter({ + switch $0 { + case .product(let product, conditions: _): + if let packageName = product.package, + prebuiltLibrary.identity == .plain(packageName) && prebuiltLibrary.products.contains(product.name) { + return false + } else { + return true + } + case .module: + return true + } + }) + + // Add build settings to use the prebuilts + let lib = prebuiltLibrary.path.appending(components: ["lib", "lib\(prebuiltLibrary.libraryName).a"]).pathString + var ldFlagsAssignment = BuildSettings.Assignment() + ldFlagsAssignment.values = [lib] + buildSettings.add(ldFlagsAssignment, for: .OTHER_LDFLAGS) + + var includeDirs: [AbsolutePath] = [prebuiltLibrary.path.appending(component: "Modules")] + if let checkoutPath = prebuiltLibrary.checkoutPath, let includePath = prebuiltLibrary.includePath { + for includeDir in includePath { + includeDirs.append(checkoutPath.appending(includeDir)) + } + } else { + for cModule in prebuiltLibrary.cModules { + includeDirs.append(prebuiltLibrary.path.appending(components: "include", cModule)) + } + } + var includeAssignment = BuildSettings.Assignment() + includeAssignment.values = includeDirs.map({ "-I\($0.pathString)" }) + buildSettings.add(includeAssignment, for: .OTHER_SWIFT_FLAGS) + } + /// The dependencies of this module. - public let dependencies: [Dependency] + public private(set) var dependencies: [Dependency] /// The language-level module name. public private(set) var c99name: String diff --git a/Sources/PackageModel/Module/PrebuiltLibrary.swift b/Sources/PackageModel/Module/PrebuiltLibrary.swift new file mode 100644 index 00000000000..efc2d3a95a0 --- /dev/null +++ b/Sources/PackageModel/Module/PrebuiltLibrary.swift @@ -0,0 +1,51 @@ +// +// PrebuiltLibrary.swift +// SwiftPM +// +// Created by Doug Schaefer on 2025-10-16. +// + +import Basics + +/// A structure representing a prebuilt library to be used instead of a source dependency +public struct PrebuiltLibrary { + /// The package identity. + public let identity: PackageIdentity + + /// The name of the binary target the artifact corresponds to. + public let libraryName: String + + /// The path to the extracted prebuilt artifacts + public let path: AbsolutePath + + /// The path to the checked out source + public let checkoutPath: AbsolutePath? + + /// The products in the library + public let products: [String] + + /// The include path relative to the checkouts dir + public let includePath: [RelativePath]? + + /// The C modules that need their includes directory added to the include path + public let cModules: [String] + + public init( + identity: PackageIdentity, + libraryName: String, + path: AbsolutePath, + checkoutPath: AbsolutePath?, + products: [String], + includePath: [RelativePath]? = nil, + cModules: [String] = [] + ) { + self.identity = identity + self.libraryName = libraryName + self.path = path + self.checkoutPath = checkoutPath + self.products = products + self.includePath = includePath + self.cModules = cModules + } +} + diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index b5243e1a170..0a1595a3fde 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -1248,7 +1248,6 @@ extension Workspace { path: path, additionalFileRules: [], binaryArtifacts: binaryArtifacts, - prebuilts: [:], fileSystem: self.fileSystem, observabilityScope: observabilityScope, enabledTraits: try manifest.enabledTraits(using: .default) @@ -1313,7 +1312,6 @@ extension Workspace { path: previousPackage.path, additionalFileRules: self.configuration.additionalFileRules, binaryArtifacts: packageGraph.binaryArtifacts[identity] ?? [:], - prebuilts: [:], shouldCreateMultipleTestProducts: self.configuration.shouldCreateMultipleTestProducts, createREPLProduct: self.configuration.createREPLProduct, fileSystem: self.fileSystem, diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index c0f61204e73..a1c735bc423 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -4617,16 +4617,48 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { emptyFiles: [ "/MyPackage/Sources/MyMacroMacros/MyMacroMacros.swift", "/MyPackage/Sources/MyMacros/MyMacros.swift", - "/MyPackage/Sources/MyMacroTests/MyMacroTests.swift" + "/MyPackage/Sources/MyMacroTests/MyMacroTests.swift", + "/swift-syntax/Sources/SwiftSyntaxMacrosTestSupport/SwiftSyntaxMacrosTestSupport.swift", + "/swift-syntax/Sources/SwiftSyntaxMacros/SwiftSyntaxMacros.swift", + "/swift-syntax/Sources/SwiftCompilerPlugin/SwiftCompilerPlugin.swift", ] ) let graph = try loadModulesGraph( fileSystem: fs, manifests: [ + Manifest.createRemoteSourceControlManifest( + displayName: "swift-syntax", + url: "https://github.com/swiftlang/swift-syntax", + path: "/swift-syntax", + products: [ + .init( + name: "SwiftSyntaxMacrosTestSupport", + type: .library(.automatic), + targets: ["SwiftSyntaxMacrosTestSupport"] + ), + .init( + name: "SwiftSyntaxMacros", + type: .library(.automatic), + targets: ["SwiftSyntaxMacros"] + ), + .init( + name: "SwiftCompilerPlugin", + type: .library(.automatic), + targets: ["SwiftCompilerPlugin"]) + ], + targets: [ + .init(name: "SwiftSyntaxMacrosTestSupport"), + .init(name: "SwiftSyntaxMacros"), + .init(name: "SwiftCompilerPlugin"), + ] + ), Manifest.createRootManifest( displayName: "MyPackage", path: "/MyPackage", + dependencies: [ + .remoteSourceControl(url: "https://github.com/swiftlang/swift-syntax", requirement: .exact("600.0.1")), + ], targets: [ TargetDescription( name: "MyMacroMacros", @@ -4723,27 +4755,60 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { "/MyPackage/Sources/MyMacroLibrary/MyMacroLibrary.swift", "/MyPackage/Sources/MyMacroMacros/MyMacroMacros.swift", "/MyPackage/Sources/MyMacros/MyMacros.swift", - "/MyPackage/Sources/MyMacroTests/MyMacroTests.swift" + "/MyPackage/Sources/MyMacroTests/MyMacroTests.swift", + "/swift-syntax/Sources/SwiftSyntaxMacrosTestSupport/SwiftSyntaxMacrosTestSupport.swift", + "/swift-syntax/Sources/SwiftSyntaxMacros/SwiftSyntaxMacros.swift", + "/swift-syntax/Sources/SwiftCompilerPlugin/SwiftCompilerPlugin.swift", + "/swift-syntax/Sources/SwiftSyntax/SwiftSyntax.swift" ] ) let graph = try loadModulesGraph( fileSystem: fs, manifests: [ + Manifest.createRemoteSourceControlManifest( + displayName: "swift-syntax", + url: "https://github.com/swiftlang/swift-syntax", + path: "/swift-syntax", + products: [ + .init( + name: "SwiftSyntaxMacrosTestSupport", + type: .library(.automatic), + targets: ["SwiftSyntaxMacrosTestSupport"] + ), + .init( + name: "SwiftSyntaxMacros", + type: .library(.automatic), + targets: ["SwiftSyntaxMacros"] + ), + .init( + name: "SwiftCompilerPlugin", + type: .library(.automatic), + targets: ["SwiftCompilerPlugin"] + ), + .init( + name: "SwiftSyntax", + type: .library(.automatic), + targets: ["SwiftSyntax"] + ), + ], + targets: [ + .init(name: "SwiftSyntaxMacrosTestSupport"), + .init(name: "SwiftSyntaxMacros"), + .init(name: "SwiftCompilerPlugin"), + .init(name: "SwiftSyntax"), + ] + ), Manifest.createRootManifest( displayName: "MyPackage", path: "/MyPackage", + dependencies: [ + .remoteSourceControl(url: "https://github.com/swiftlang/swift-syntax", requirement: .exact("600.0.1")), + ], targets: [ - TargetDescription( - name: "MyMacroLibrary", - dependencies: [ - .product(name: "SwiftSyntax", package: "swift-syntax"), - ] - ), TargetDescription( name: "MyMacroMacros", dependencies: [ - "MyMacroLibrary", .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), ], diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index c7c685ecab9..8e866e063f3 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -3587,7 +3587,6 @@ final class PackageBuilderTester { _ manifest: Manifest, path: AbsolutePath = .root, binaryArtifacts: [String: BinaryArtifact] = [:], - prebuilts: [PackageIdentity: [String: PrebuiltLibrary]] = [:], shouldCreateMultipleTestProducts: Bool = false, createREPLProduct: Bool = false, supportXCBuildTypes: Bool = false, @@ -3606,7 +3605,6 @@ final class PackageBuilderTester { path: path, additionalFileRules: supportXCBuildTypes ? FileRuleDescription.xcbuildFileTypes : FileRuleDescription.swiftpmFileTypes, binaryArtifacts: binaryArtifacts, - prebuilts: prebuilts, shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, warnAboutImplicitExecutableTargets: true, createREPLProduct: createREPLProduct, diff --git a/Tests/WorkspaceTests/PrebuiltsTests.swift b/Tests/WorkspaceTests/PrebuiltsTests.swift index 759e94c3721..19c1c6f6a6f 100644 --- a/Tests/WorkspaceTests/PrebuiltsTests.swift +++ b/Tests/WorkspaceTests/PrebuiltsTests.swift @@ -32,6 +32,7 @@ final class PrebuiltsTests: XCTestCase { artifact: Data, swiftSyntaxVersion: String, swiftSyntaxURL: String? = nil, + additionalTargets: [MockTarget] = [], run: (Workspace.SignedPrebuiltsManifest, AbsolutePath, MockPackage, MockPackage) async throws -> () ) async throws { try await fixtureXCTest(name: "Signing") { fixturePath in @@ -121,7 +122,7 @@ final class PrebuiltsTests: XCTestCase { ], type: .test ), - ], + ] + additionalTargets, dependencies: [ .sourceControl( url: swiftSyntaxURL, @@ -963,6 +964,92 @@ final class PrebuiltsTests: XCTestCase { } } } + + func testPrebuiltsInDeps() async throws { + // This is the case where macros that depend on libraries that also depend on swift-syntax. + // Currently we disable the use of prebuilts in the macros since the libraries are + // not able to take advantage of the prebuilts at this time and macros crash when they mix. + let sandbox = AbsolutePath("/tmp/ws") + let fs = InMemoryFileSystem() + let artifact = Data() + + let extraTargets: [MockTarget] = [ + try MockTarget( + name: "ExtraLibrary", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + ], + type: .regular + ), + try MockTarget( + name: "ExtraMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + .target(name: "ExtraLibrary") + ], + type: .macro + ), + ] + + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1", additionalTargets: extraTargets) + { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: artifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } + } + + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTAssertEqual(archivePath, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64.zip")) + XCTAssertEqual(destination, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64")) + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + swiftVersion: swiftVersion, + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ), + ) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error || $0.severity == .warning }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + try checkSettings(rootPackage, "ExtraLibrary", usePrebuilt: false) + try checkSettings(rootPackage, "ExtraMacros", usePrebuilt: false) + } + } + + } } extension String {