Probe for the actual Danger.swiftmodule instead of trusting the compiler that built danger-swift - #663
Conversation
|
The macOS CI failures here are unrelated to this diff. Every macOS job fails identically before Danger ever gets to run the compiled Swift: This happens inside the Root cause looks environmental: the macOS jobs install This is reproducible on #662 as well (identical stack trace, identical point of failure), so it predates this PR and isn't something this diff can fix — flagging in case it's useful context, but not blocking review of the actual change. |
|
Correction to my earlier note here about the macOS CI failure: I'd said "a recent Opened a fix at #664: install |
…g Linux legs The brew-installed danger-js on macOS is a pkg-built standalone binary with its own embedded Node 18 (pkg --targets node18-macos-*; pkg 5.8.1 -> pkg-fetch 3.4.2, whose newest Node 18 base is v18.15.0). danger-js 13.0.10 pins undici 6.21.1, which requires node >=18.17 per its engines field. Below that floor, undici's fetchFinale hands stream.finished() a web ReadableStream it doesn't support, so every fetch() in danger-swift ci throws ERR_INVALID_ARG_TYPE and the step fails before Danger can post anything -- unrelated to the runner's own Node (22/24), which is fine. danger-js switched from node-fetch to undici in 13.0.10 (2026-06-25), which is why every macOS PR run since has failed identically, including on PRs unrelated to this change (#662, #663). The Linux jobs already avoid this by installing danger from npm via actions/setup-node + yarn instead of the brew binary. Do the same on macOS.
There was a problem hiding this comment.
Pull request overview
This PR fixes Dangerfile compilation failures caused by SwiftPM’s differing module output layouts by replacing compile-time Swift-version heuristics with a runtime probe for the actual Danger.swiftmodule location, ensuring the -I import path matches the build artifacts produced by the target toolchain/build system.
Changes:
- Updated
SPMDanger.moduleFolderto probe forDanger.swiftmodulein both the flat andModules/layouts and fall back to the prior compiled default when ambiguous. - Updated
Script.artifactsPathto use the same probing behavior, correctly resolved relative to the script’sfolder. - Added/updated tests to cover flat-only, nested-only, both-present, neither-present probe states, plus a regression test ensuring probing occurs under the script folder (not process CWD); updated the changelog entry.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/RunnerLib/SPMDanger.swift | Replaces compile-time module path selection with a runtime probe for Danger.swiftmodule to choose the correct SwiftPM module layout. |
| Sources/DangerDependenciesResolver/Script.swift | Applies the same runtime probe to Marathon/inline-dependency artifact discovery, scoped to the script’s folder. |
| Tests/RunnerLibTests/SPMDangerTests.swift | Expands test coverage for the new moduleFolder probing behavior and updates the file manager stub. |
| Tests/DangerDependenciesResolverTests/ScriptTests.swift | Adds comprehensive probe-state tests for artifactsPath, including a regression test for probing under folder. |
| CHANGELOG.md | Documents the fix and adds the contributor reference. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
f142449 to
ce800f6
Compare
|
CI here is failing on every job at the "Install danger-js" step: This isn't caused by this PR — Opened #665 to bump |
|
@DylanBettermannDD are you able to make this ready for review now that the CI fix was merged? |
…ler that built danger-swift moduleFolder (and the equivalent Script.artifactsPath) picked between .build/debug and .build/debug/Modules using a compile-time #if compiler(<6.0) check on whichever Swift built the danger-swift binary itself. That's a proxy for a runtime property of the toolchain/build system building the target package, and it's wrong whenever those two differ — which is the normal case for a distributed binary (Homebrew, Docker, the new prebuilt universal binary), and is why the swiftbuild build system (Xcode 16.3+'s new default) breaks it. Replace the compile-time check with a runtime probe for the exact Danger.swiftmodule artifact at both candidate locations, falling back to today's compiled-in default whenever the probe is ambiguous (both or neither present) so no currently-working configuration changes behavior.
The Marathon inline-dependency path's Script.artifactsPath had the same compile-time #if compiler(<6.0) defect as SPMDanger.moduleFolder, but an initial fix probed FileManager.default against paths relative to the process's current directory. Runner.swift's only caller resolves the returned paths relative to the script's own folder (a separate directory where the build actually ran), not the process cwd, so the probe could pick the wrong candidate based on unrelated filesystem state. Resolve the probe against the script's own folder instead, with an injectable FileManager so this is actually testable, and add coverage mirroring the SPMDangerTests cases plus a regression test pinning that the probe ignores the process cwd.
ce800f6 to
8dad78b
Compare
|
@f-meloni @renfrenkel rebased and ready for review. It was previously approved, but I don't have permissions to merge PRs. |
Xcode 27's SwiftPM emits modules flat in .build/debug instead of .build/debug/Modules, so danger-swift 3.22.x compiled the Dangerfile with a stale -I and failed the "Run Danger 2" step with "no such module 'Danger'". Pin to danger/swift#663, which probes for the module rather than trusting a compile-time layout check. The pinned revision has isDevelop = true, so danger/swift exports its own DangerDeps product; rename ours to DangerDepsFirefox to avoid the duplicate-product error. danger-swift discovers it by the DangerDeps[A-Za-z]* pattern, so no other reference needs updating. Verified end-to-end on Xcode 27.0 (Swift 6.4) and building clean under Xcode 26.6 (Swift 6.3.3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Xcode 27's SwiftPM emits modules flat in .build/debug instead of .build/debug/Modules, so danger-swift 3.22.x compiled the Dangerfile with a stale -I and failed the "Run Danger 2" step with "no such module 'Danger'". Pin to danger/swift#663, which probes for the module rather than trusting a compile-time layout check. The pinned revision has isDevelop = true, so danger/swift exports its own DangerDeps product; rename ours to DangerDepsFirefox to avoid the duplicate-product error. danger-swift discovers it by the DangerDeps[A-Za-z]* pattern, so no other reference needs updating. Verified end-to-end on Xcode 27.0 (Swift 6.4) and building clean under Xcode 26.6 (Swift 6.3.3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Xcode 27's SwiftPM emits modules flat in .build/debug instead of .build/debug/Modules, so danger-swift 3.22.x compiled the Dangerfile with a stale -I and failed the "Run Danger 2" step with "no such module 'Danger'". Pin to danger/swift#663, which probes for the module rather than trusting a compile-time layout check. The pinned revision has isDevelop = true, so danger/swift exports its own DangerDeps product; rename ours to DangerDepsFirefox to avoid the duplicate-product error. danger-swift discovers it by the DangerDeps[A-Za-z]* pattern, so no other reference needs updating. Verified end-to-end on Xcode 27.0 (Swift 6.4) and building clean under Xcode 26.6 (Swift 6.3.3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem
SPMDanger.moduleFolder(used to build the-Iflag when compiling a Dangerfile against aDangerDepsSwiftPM product) picks between.build/debugand.build/debug/Modulesusing a compile-time check on the Swift version that built thedanger-swiftbinary itself:That's a proxy for a runtime property — the toolchain and build system used to build the target package — and it's wrong whenever those two differ, which is the normal case for a distributed
danger-swiftbinary (Homebrew, Docker, or the prebuilt universal binary from #660): the binary's own compile-time Swift version has nothing to do with which build system/toolchain later builds the Dangerfile's dependencies.Concretely, this breaks on a toolchain that defaults
swift buildto the newerswiftbuildbuild system (observed on Xcode 27 Beta 4 / Swift 6.4; not claiming a specific version threshold here, just that this is a real, shipping build system that behaves differently fromnative). Underswiftbuild, SwiftPM produces flat*.swiftmodulefiles with noModules/subdirectory at all — the inverse of whatnative+Swift 6 produces. Adanger-swiftbinary built with Swift ≥6.0 (true of every distributed binary today) then looks inModules/, finds nothing, and fails:Measured layouts (building only
--product DangerDeps<X>, exactly whatbuildDependenciesruns):.build/debugarm64-apple-macosx/debugout/Products/Debug*.swiftmodulein bin path<bin>/Modules/The two layouts are disjoint.
.build/debugitself stays a valid symlink under both — SwiftPM repoints it on every build — so only the module-search path (-I) is wrong, never-L.Sources/DangerDependenciesResolver/Script.swift'sartifactsPathhas the identical#if compiler(<6.0)pattern for the Marathon-based inline-dependency path (import ... package:Dangerfiles with noDangerDepslibrary).Fix
Replace the compile-time check with a runtime probe for the actual
Danger.swiftmoduleartifact at both candidate locations, falling back to today's compiled-in default whenever the probe is ambiguous (both or neither candidate present) so no currently-working configuration changes behavior:Probing for the exact artifact (not mere
Modules/directory existence) avoids a false positive from an empty/partialModules/left over from a prior build under a different toolchain.Backwards compatibility is testable, not just asserted: both unambiguous branches are byte-identical to today's two
#if compilerbranches, and the only behavior change is in states that are broken today (native <6.0 binary vs. ≥6.0 package or vice versa, andswiftbuild). The ambiguous fallback keeps every currently-working binary — including the official Docker image, which defaults to Swift 5.9 — on its existing compiled-in behavior.The same fix is applied to
Script.artifactsPath, resolved against the script's ownfolder(not the process's working directory, since the build for that path runs in a separate scratch folder).Testing
swift test --filter "SPMDangerTests|ScriptTests"— 20/20 passing, including new coverage for all four probe states (flat-only, nested-only, both-present, neither-present) on both call sites, plus a regression test pinning thatScript.artifactsPathprobes under its ownfolder, not the process cwd.swiftcflags:swiftbuildbuild system (Xcode 27 Beta 4, Swift 6.4): the unpatched behavior (-I <bin>/Modules) fails withno such module 'Danger'; the patched behavior (-I <bin>) compiles and runs.native(Xcode 26.6, Swift 6.3.3): unchanged — the probe unambiguously selects the nestedModules/path, byte-identical to today, and the flat path correctly fails (proving the two layouts are genuinely disjoint, not just "either works").swift build -c release --product danger-swiftsucceeds under both toolchains.Related
buildFolderto a hardcoded.build/out/Products/Debugwhen present): after a toolchain downgrade without a clean, the stale directory survives and gets selected, producingerror: module compiled with Swift 6.4 cannot be imported by the Swift 6.3.3 compiler. This PR instead keepsbuildFolderuntouched and only changes the module-search path, following the.build/debugsymlink SwiftPM already maintains rather than hardcoding an absolute layout path.