Skip to content

dylint: lint crates' ui fixtures cannot load their .so/.dll on windows runners (CARGO_BUILD_TARGET vs dylint's probe path) #1373

Open

Description

@zackees

Symptom

On a windows-latest runner, the Dylint workflow's "Test Dylint libraries" step — which runs each lint crate's own compiletest ui fixtures — fails:

diff of stderr:

-error: use fbuild_core::path::NormalizedPath::display_slash() instead of hand-rolled ... rewrite
-  --> $DIR/disallowed.rs:5:16
...
+error: could not load library
+  `D:/a/fbuild/fbuild/target/dylint-tests/debug/ban_manual_slash_normalize@nightly-2026-04-16.dll`:
+  LoadLibraryExW failed

The lint never loads, so the fixture compiles clean and compiletest reports the expected diagnostic as missing.

What is going on

Every lint crate's #[test] fn ui already documents half of this:

// Dylint 6.0.1 looks for its test library directly under
// <target>/debug, while Soldr selects the host through
// CARGO_BUILD_TARGET and Cargo writes to <target>/<host>/debug.
unsafe { std::env::remove_var("CARGO_BUILD_TARGET"); }

Clearing the variable is not sufficient on the runner: it happens inside the test process, after cargo has already decided where to place the artifact. The error path the harness then probes (<target>/dylint-tests/debug/...dll) is not where the .dll was written (<target>/dylint-tests/x86_64-pc-windows-msvc/debug/...).

Two details suggest looking closer before assuming it is only a path問題:

  • LoadLibraryExW also fails when a dependent DLL cannot be resolved, not only when the file is absent — so a missing-file diagnosis should be confirmed rather than inferred from the message.
  • The PATH the harness builds for the driver invocation begins with a stray ; and contains a lone C; fragment, i.e. it looks mangled somewhere in the assembly. That may be incidental to Git Bash path conversion, or may be the actual cause.

Why it is filed rather than fixed

#1359 added a windows-latest Dylint leg so Windows-gated source is actually linted. The lints themselves are clean there — the full workspace sweep passes. Only the lint crates' own self-tests fail, and those assert platform-independent behavior (each lint fires on its fixture) already covered by the ubuntu leg. So that step is scoped to ubuntu, and this is tracked here instead of blocking the gate.

Worth fixing if someone wants the ui fixtures exercised on Windows too — mostly as a guard against a lint whose diagnostic rendering differs per-OS, which is a real if narrow risk given several of these lints are about path spelling.

Likely fixes

  1. Set CARGO_BUILD_TARGET= (empty) for the step itself rather than clearing it inside the test, so cargo places the artifact where the harness looks.
  2. Or point the harness at the real location by setting DYLINT_LIBRARY_PATH to <target>/<host>/debug on Windows.
  3. Confirm first whether the .dll exists at the probed path — that separates "wrong directory" from "dependent DLL missing", which need different fixes.

Activity

  1. zackees commented on Aug 23, 2026

    @zackees
    MemberAuthor

    Two attempts, two CI rounds, same failure. Recording what is now established so the next attempt starts from facts rather than my original guess — which was wrong.

    The issue's original hypothesis is wrong

    I blamed a CARGO_BUILD_TARGET path mismatch. That is real but not sufficient: with CARGO_BUILD_TARGET set, with an explicit --target build, and with the env var unset, the ui tests pass on a Windows host. All 27 lint crates pass locally through the exact CI step script. The bug is runner-specific.

    What is established

    Two target-dir layouts coexist. Reproduced locally — after one cargo test run, $CARGO_TARGET_DIR contains both:

    debug/
    x86_64-pc-windows-msvc/
    

    soldr builds the test binary under <target>/<triple>/debug, while each lint's fn ui clears CARGO_BUILD_TARGET so the library it rebuilds lands in <target>/debug — the only place Dylint 6.0.1 looks. Notably, unsetting CARGO_BUILD_TARGET for the whole step does not collapse them: soldr injects --target regardless, because the step invokes soldr cargo by name rather than through the proxy.

    The failure is a missing dependency, not a missing file. compiletest invokes the driver with

    PATH=...target/dylint-tests\x86_64-pc-windows-msvc\debug;...\debug\deps;...
    

    and the library it loads is at

    target/dylint-tests\debug\ban_manual_slash_normalize@nightly-2026-04-16.dll
    

    LoadLibraryExW failed names the library, which reads like the file is absent; the file is there. This is the distinction the issue text told me to confirm first and I then failed to apply.

    Ruled out

    hypothesis result
    PATH separator mangling — joining native CARGO_HOME to a POSIX $PATH with : Real bug, fixed in #1377, but not this failure — Windows leg failed identically after
    Untargeted debug + debug/deps missing from PATH No change whatsoever
    CARGO_BUILD_TARGET set/unset/explicit --target locally Passes in all three

    So the missing dependency is in neither target tree. Remaining candidates worth checking, in order:

    1. Which toolchain instance the library was linked against. The driver comes from DYLINT_DRIVER_PATH, the library from a cargo build under RUSTUP_TOOLCHAIN=nightly-2026-04-16, and on CI RUSTUP_HOME is setup-soldr's private root. If those resolve to two different rustc_driver-*.dll builds, the library's dependency is a specific hash of that dylib and the PATH points at the other one. This is my best remaining guess and it is checkable by listing rustc_driver* under both roots on the runner.
    2. Dump the actual dependency. One CI step running dumpbin /dependents (or objdump -p) on the built .dll, plus a directory listing of both debug trees, would end the guessing entirely. Cheaper than another hypothesis.
    3. Make the layouts genuinely single by having the step invoke the rustup cargo proxy directly instead of soldr cargo — there is precedent in the same workflow, where the sweep step bypasses soldr for a similar reason. I could not test this locally because the repo hook blocks bare cargo.

    Whoever picks this up: start with (2). It converts the remaining question from a guess into a reading.

  2. added a commit that references this issue on Aug 23, 2026
    6cb01d9
  3. zackees commented on Aug 23, 2026

    @zackees
    MemberAuthor

    Reopened — GitHub auto-closed this when #1377 merged, because my first commit on that branch carried a Closes FastLED/fbuild#1373 trailer written when I still thought I had the fix. I rescoped the PR after two failed attempts but the trailer stayed in the squashed commit message, so the close was mechanical, not a claim.

    This is not fixed. The evidence comment above stands: two target-dir layouts coexist, the failure is a missing dependency rather than a missing file, and both hypotheses I tried are ruled out. Start with dumping the actual dependency list on the runner — it converts the remaining question from a guess into a reading.

    What #1377 did land: the cygpath PATH-join hardening (a real latent bug on Windows, just not this one), and a de-flake of a test I had introduced in #1368.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions