From 6763ab50f7ce8031a10733370f6bd30f7a584293 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Mon, 8 Jun 2026 22:25:43 -0700 Subject: [PATCH 1/2] [Driver] Recognize embedded stdlib layout when picking plugin paths For embedded `noneOS` targets the stdlib lives at `/embedded//Swift.swiftmodule`, but the existing `hasToolchainStdlib` probe only checked `//`, which is `/Swift.swiftmodule` (no platform subdir) for `noneOS` because `Triple.platformName()` returns nil. The probe always returned false, so the conditional swap in `addCommonFrontendOptions` placed the SDK's `-external-plugin-path` ahead of the toolchain's `-plugin-path`. The first-emplace race in `PluginLoader` then registered the bootstrap Xcode SDK's (potentially stale) `libSwiftMacros.dylib` as `SwiftMacros`, shadowing the freshly-built one and producing "type 'SwiftMacros.SwiftifyImportMacro' could not be found" errors when the embedded headers used macros that post-date the bootstrap platform (and some CI machines run a pretty old host OS, so they lack `_SwiftifyImport`). This should mostly be an issue when bootstrapping. Branch the probe on `triple.os == .noneOS` so the embedded layout is checked directly, with a single `fileSystem.exists` call. Covers Apple `*-apple-none-macho` (the visible failure), plus Wasm `*-unknown-none-wasm`, ARM/RISC-V/x86 `*-none-*-eabi|elf`, and AVR. Add 8 regression tests in `ToolchainTests` (4 Apple macho + 4 non-Apple `noneOS`), each gated with `requireFrontendSupportsTarget` so they self-skip on toolchains lacking the relevant LLVM backend. rdar://178814859 --- .../SwiftDriver/Jobs/FrontendJobHelpers.swift | 18 ++++- Tests/SwiftDriverTests/ToolchainTests.swift | 75 +++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index 58a5ebe02..40bd5afca 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -591,8 +591,22 @@ extension Driver { commandLine.appendFlag($0) } - let toolchainStdlibPath = VirtualPath.lookup(frontendTargetInfo.runtimeResourcePath.path) - .appending(components: frontendTargetInfo.target.triple.platformName() ?? "", "Swift.swiftmodule") + let resourceDirVPath = VirtualPath.lookup(frontendTargetInfo.runtimeResourcePath.path) + let toolchainStdlibPath: VirtualPath + if frontendTargetInfo.target.triple.os == .noneOS { + // Embedded `noneOS` targets lay their stdlib out under + // /embedded//Swift.swiftmodule rather than under + // a platform-named subdir since `Triple.platformName()` is nil for + // `noneOS` + toolchainStdlibPath = resourceDirVPath + .appending(components: "embedded", + frontendTargetInfo.target.triple.triple, + "Swift.swiftmodule") + } else { + toolchainStdlibPath = resourceDirVPath + .appending(components: frontendTargetInfo.target.triple.platformName() ?? "", + "Swift.swiftmodule") + } let hasToolchainStdlib = try fileSystem.exists(toolchainStdlibPath) let skipMacroSearchPath = isPlanJobForExplicitModule && isFrontendArgSupported(.loadResolvedPlugin) diff --git a/Tests/SwiftDriverTests/ToolchainTests.swift b/Tests/SwiftDriverTests/ToolchainTests.swift index 397f1c46d..b2a5d35a1 100644 --- a/Tests/SwiftDriverTests/ToolchainTests.swift +++ b/Tests/SwiftDriverTests/ToolchainTests.swift @@ -698,6 +698,81 @@ import CRT #endif } + func assertEmbeddedPluginPathOrder( + triple: String + ) async throws { + let sdkRoot = try testInputsPath.appending( + components: ["Platform Checks", "iPhoneOS.platform", "Developer", "SDKs", "iPhoneOS13.0.sdk"]) + + try await withTemporaryDirectory { resourceDir in + try localFileSystem.createDirectory( + resourceDir.appending(components: "embedded", triple, "Swift.swiftmodule"), + recursive: true) + + var driver = try TestDriver(args: [ + "swiftc", "-typecheck", "foo.swift", + "-target", triple, + "-enable-experimental-feature", "Embedded", + "-sdk", VirtualPath.absolute(sdkRoot).name, + "-resource-dir", resourceDir.pathString, + ]) + guard driver.isFrontendArgSupported(.pluginPath) else { return } + + let jobs = try await driver.planBuild().removingAutolinkExtractJobs() + let job = try #require(jobs.first) + + let toolchainPluginIdx = try #require( + job.commandLine.firstIndex(of: .flag("-plugin-path")), + "Expected toolchain -plugin-path to be emitted for \(triple)") + + // For toolchains that emit `-external-plugin-path` (Darwin), `-plugin-path` + // must come first so resource-dir plugins win the first-emplace race in + // PluginLoader. For toolchains that don't (GenericUnix etc.), we only + // assert that the toolchain plugin path is present — there is no + // `-external-plugin-path` to compare against. + guard driver.isFrontendArgSupported(.externalPluginPath) else { return } + guard let externalPluginIdx = + job.commandLine.firstIndex(of: .flag("-external-plugin-path")) else { return } + #expect(toolchainPluginIdx < externalPluginIdx, + "-plugin-path must precede -external-plugin-path so the toolchain libSwiftMacros wins (\(triple))") + } + } + + // Check that we pass the toolchain plugin path before the external plugin + // path for embedded targets. + @Test(.requireFrontendSupportsTarget("armv6-apple-none-macho")) + func embeddedPluginPath_armv6_apple_none_macho() async throws { + try await assertEmbeddedPluginPathOrder(triple: "armv6-apple-none-macho") + } + @Test(.requireFrontendSupportsTarget("armv7-apple-none-macho")) + func embeddedPluginPath_armv7_apple_none_macho() async throws { + try await assertEmbeddedPluginPathOrder(triple: "armv7-apple-none-macho") + } + @Test(.requireFrontendSupportsTarget("armv7em-apple-none-macho")) + func embeddedPluginPath_armv7em_apple_none_macho() async throws { + try await assertEmbeddedPluginPathOrder(triple: "armv7em-apple-none-macho") + } + @Test(.requireFrontendSupportsTarget("arm64-apple-none-macho")) + func embeddedPluginPath_arm64_apple_none_macho() async throws { + try await assertEmbeddedPluginPathOrder(triple: "arm64-apple-none-macho") + } + @Test(.requireFrontendSupportsTarget("wasm32-unknown-none-wasm")) + func embeddedPluginPath_wasm32_unknown_none_wasm() async throws { + try await assertEmbeddedPluginPathOrder(triple: "wasm32-unknown-none-wasm") + } + @Test(.requireFrontendSupportsTarget("armv6-none-none-eabi")) + func embeddedPluginPath_armv6_none_none_eabi() async throws { + try await assertEmbeddedPluginPathOrder(triple: "armv6-none-none-eabi") + } + @Test(.requireFrontendSupportsTarget("riscv32-none-none-eabi")) + func embeddedPluginPath_riscv32_none_none_eabi() async throws { + try await assertEmbeddedPluginPathOrder(triple: "riscv32-none-none-eabi") + } + @Test(.requireFrontendSupportsTarget("x86_64-unknown-none-elf")) + func embeddedPluginPath_x86_64_unknown_none_elf() async throws { + try await assertEmbeddedPluginPathOrder(triple: "x86_64-unknown-none-elf") + } + @Test func workingDirectoryForImplicitOutputs() async throws { let workingDirectory = localFileSystem.currentWorkingDirectory!.appending(components: "Foo", "Bar") From 3b7d1d30d57bdbdb5ab7b570b66457cab3a3a110 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Tue, 9 Jun 2026 11:45:25 -0700 Subject: [PATCH 2/2] parameterize test --- Tests/SwiftDriverTests/ToolchainTests.swift | 53 +++++++-------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/Tests/SwiftDriverTests/ToolchainTests.swift b/Tests/SwiftDriverTests/ToolchainTests.swift index b2a5d35a1..770509efa 100644 --- a/Tests/SwiftDriverTests/ToolchainTests.swift +++ b/Tests/SwiftDriverTests/ToolchainTests.swift @@ -698,7 +698,23 @@ import CRT #endif } - func assertEmbeddedPluginPathOrder( + static let supportedEmbeddedTriples: [String] = [ + "armv6-apple-none-macho", + "armv7-apple-none-macho", + "armv7em-apple-none-macho", + "arm64-apple-none-macho", + "wasm32-unknown-none-wasm", + "armv6-none-none-eabi", + "riscv32-none-none-eabi", + "x86_64-unknown-none-elf", + ].filter(probeFrontendForTarget) + + // Check that we pass the toolchain plugin path before the external plugin + // path for embedded targets. + @Test(.disabled(if: ToolchainTests.supportedEmbeddedTriples.isEmpty, + "Frontend does not support any of the embedded target triples"), + arguments: ToolchainTests.supportedEmbeddedTriples) + func embeddedPluginPathOrder( triple: String ) async throws { let sdkRoot = try testInputsPath.appending( @@ -738,41 +754,6 @@ import CRT } } - // Check that we pass the toolchain plugin path before the external plugin - // path for embedded targets. - @Test(.requireFrontendSupportsTarget("armv6-apple-none-macho")) - func embeddedPluginPath_armv6_apple_none_macho() async throws { - try await assertEmbeddedPluginPathOrder(triple: "armv6-apple-none-macho") - } - @Test(.requireFrontendSupportsTarget("armv7-apple-none-macho")) - func embeddedPluginPath_armv7_apple_none_macho() async throws { - try await assertEmbeddedPluginPathOrder(triple: "armv7-apple-none-macho") - } - @Test(.requireFrontendSupportsTarget("armv7em-apple-none-macho")) - func embeddedPluginPath_armv7em_apple_none_macho() async throws { - try await assertEmbeddedPluginPathOrder(triple: "armv7em-apple-none-macho") - } - @Test(.requireFrontendSupportsTarget("arm64-apple-none-macho")) - func embeddedPluginPath_arm64_apple_none_macho() async throws { - try await assertEmbeddedPluginPathOrder(triple: "arm64-apple-none-macho") - } - @Test(.requireFrontendSupportsTarget("wasm32-unknown-none-wasm")) - func embeddedPluginPath_wasm32_unknown_none_wasm() async throws { - try await assertEmbeddedPluginPathOrder(triple: "wasm32-unknown-none-wasm") - } - @Test(.requireFrontendSupportsTarget("armv6-none-none-eabi")) - func embeddedPluginPath_armv6_none_none_eabi() async throws { - try await assertEmbeddedPluginPathOrder(triple: "armv6-none-none-eabi") - } - @Test(.requireFrontendSupportsTarget("riscv32-none-none-eabi")) - func embeddedPluginPath_riscv32_none_none_eabi() async throws { - try await assertEmbeddedPluginPathOrder(triple: "riscv32-none-none-eabi") - } - @Test(.requireFrontendSupportsTarget("x86_64-unknown-none-elf")) - func embeddedPluginPath_x86_64_unknown_none_elf() async throws { - try await assertEmbeddedPluginPathOrder(triple: "x86_64-unknown-none-elf") - } - @Test func workingDirectoryForImplicitOutputs() async throws { let workingDirectory = localFileSystem.currentWorkingDirectory!.appending(components: "Foo", "Bar")