Skip to content

fix(compile): pin TMP/TEMP for compiler subprocess on Windows (#875)#885

Merged
zackees merged 1 commit into
mainfrom
fix/875-windows-tempfile-empty-env
Jun 30, 2026
Merged

fix(compile): pin TMP/TEMP for compiler subprocess on Windows (#875)#885
zackees merged 1 commit into
mainfrom
fix/875-windows-tempfile-empty-env

Conversation

@zackees

@zackees zackees commented Jun 30, 2026

Copy link
Copy Markdown
Member

Problem

compile_source passed Vec::new() as the env to svc.compile(...). zccache's apply_client_env treats Some(env) — even an empty vec — as "client provided full env, clear the daemon's inherited env." So gcc was spawned with a literally empty environment. On Windows that broke compilation on the very first TU: without TMP/TEMP/USERPROFILE/LOCALAPPDATA, the Windows GetTempPathW fallback chain bottoms out at C:\Windows\ (not writable for regular users), and gcc fails with Cannot create temporary file in C:\Windows\: Permission denied before producing a single .o.

This was uncovered by PR #850's tempfile-rooting sweep — the four callers it migrated cover the build-pipeline tempfile creation, not the per-TU compile subprocess's env.

Fix

New helper fbuild_core::subprocess::compile_env_for_build(build_dir) composes the minimum env a Windows compile subprocess needs:

  • TMPDIR / TMP / TEMP -> fbuild-owned <build_dir>/.compile-tmp (mirrors the link_env_for_build pattern)
  • PATH, SystemRoot, USERPROFILE, LOCALAPPDATA, APPDATA, PATHEXT, ComSpec forwarded from the host env when present (silently skipped on POSIX where they don't exist).

compile_source derives the build_dir from compile_cwd (with sensible fallbacks) and threads the resulting env into the zccache client call.

Tests

  • compile_env_for_build_pins_tmp_keys_to_build_dir
  • compile_env_for_build_is_idempotent

Both pass locally. soldr cargo clippy -p fbuild-core -p fbuild-build --all-targets -- -D warnings clean.

Closes #875

`compile_source` passed `Vec::new()` as the env to
`svc.compile(...)`. zccache's `apply_client_env` treats `Some(env)` —
even an empty vec — as "client provided full env, clear the daemon's
inherited env." So gcc was spawned with a literally empty environment.
On Windows that broke compilation on the very first TU: without
`TMP`/`TEMP`/`USERPROFILE`/`LOCALAPPDATA`, the Windows `GetTempPathW`
fallback chain bottoms out at `C:\Windows\` (not writable for regular
users), and gcc fails with `Cannot create temporary file in
C:\Windows\: Permission denied` before producing a single `.o`.

This is uncovered by PR #850's tempfile-rooting sweep — the four
callers it migrated cover the build-pipeline tempfile creation, not
the per-TU compile subprocess's env.

Fix: add `fbuild_core::subprocess::compile_env_for_build(build_dir)` —
the minimum env a Windows compile subprocess needs to find tempfiles
+ helper binaries (cc1/cc1plus/as) without reintroducing host
pollution that would hurt zccache hit rates. Specifically:

- TMPDIR / TMP / TEMP -> fbuild-owned `<build_dir>/.compile-tmp`
- PATH, SystemRoot, USERPROFILE, LOCALAPPDATA, APPDATA, PATHEXT,
  ComSpec forwarded from the host env when present (silently
  skipped on POSIX where they don't exist).

`compile_source` derives the build_dir from `compile_cwd` (with
sensible fallbacks) and threads the resulting env into the zccache
client call.

Two regression tests:
- `compile_env_for_build_pins_tmp_keys_to_build_dir`
- `compile_env_for_build_is_idempotent`

Closes #875
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@zackees, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a3401e7-f17c-49e0-98ec-0541c0d72079

📥 Commits

Reviewing files that changed from the base of the PR and between b9d1efb and 89a273a.

📒 Files selected for processing (2)
  • crates/fbuild-build/src/compiler.rs
  • crates/fbuild-core/src/subprocess.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/875-windows-tempfile-empty-env

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zackees
zackees merged commit 8bab4db into main Jun 30, 2026
81 of 93 checks passed
@zackees
zackees deleted the fix/875-windows-tempfile-empty-env branch June 30, 2026 18:05
zackees added a commit that referenced this pull request Jun 30, 2026
Rolls up the fixes shipped since 2.3.14:

- #855 (#883) — drop stale standalone-zccache CI/Docker/docs references
  after the soldr embedded-zccache transition (soldr#977/#980/#1081)
- #865 (#884) — write daemon status via sync write_atomic; unblocks
  fbuild-daemon unit tests on macOS/Windows by eliminating the
  block_in_place panic in current-thread tokio runtimes
- #875 (#885) — pin TMP/TEMP for compiler subprocess on Windows; ESP32
  compile no longer fails with 'Cannot create temporary file in
  C:\Windows\' on the very first TU
- #720 (#886) — record anti-removal policy for dump_usb_ids example
- #829 (#887) — stage fbuild._native cdylib during source/editable
  install so 'from fbuild import ...' works after 'uv pip install -e .'

Plus all prior #664 platform_packages audit work, the #826 testing
followups, and the build cancellation fix from earlier in the cycle.
zackees added a commit that referenced this pull request Jun 30, 2026
…indows (#890)

Follow-up to #885 (`fix(compile): pin TMP/TEMP for compiler subprocess on
Windows`). With TMP/TEMP env now propagated correctly, gcc's driver
finally manages to create its internal spec file — and as soon as it
does, the next failure mode surfaces:

  cc1plus.exe: fatal error: srcmain.cpp: No such file or directory

The driver writes args into the spec file using GCC's quoting rules,
where `\` is an escape character. So `src\main.cpp` (which
`path_arg_for_compile_cwd` returns on Windows because that's how the
host OS spells it) is parsed by cc1 as `srcmain.cpp` and fails to open.

Fix: convert backslashes to forward slashes for every path arg on
Windows. Windows GCC has always accepted both separators for filesystem
lookups; forward slashes additionally survive every spec-file /
response-file pass unchanged. POSIX hosts pass through unmodified.

Repro pre-fix (after #885):
  bash compile esp32dev --examples Blink
  → cc1plus.exe: fatal error: srcmain.cpp: No such file or directory

Post-fix: clean Blink build in 117 s on Windows.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zackees added a commit to FastLED/FastLED that referenced this pull request Jun 30, 2026
…ESP32 (#3446) (#3447)

## chip-id detection — three-strategy fallback chain (#3446)

Previously the autoresearch port detection probed each port with a
single 3.0 s esptool `chip-id` attempt using the default DTR/RTS auto-
reset. On a CP210x ESP32-WROOM devkit that budget consistently
undershoots — the realistic worst case is ~7 s (slow CP210x driver
init + ~4 s of esptool SYNC retries + bootloader handshake), so
healthy boards were misclassified as "device may not be in
bootloader mode" and the entire bring-up gave up before any test
could start.

New behaviour:

- Per-strategy timeout default is 7 s (covers the worst case while
  keeping a 5-port-all-empty sweep under ~10 s total).
- Primary strategy is still `--before default-reset`.
- ONLY when the primary attempt actually times out do we escalate to
  the fallback chain (`--before usb-reset` then `--before no-reset`).
  "Port open OK but no chip-id produced" stays a fast-path failure —
  empty ports don't drag the total budget out.
- The caller in `_resolve_port` drops its explicit `timeout=3.0`
  override so it picks up the new default and the fallback chain.

Local repro: `bash autoresearch esp32dev` with a CP210x ESP32-wroom
on COM11 now resolves to `(esp32) ESP32` on the first attempt
instead of `detection failed (esptool timed out after 3.0s)`.

## LegacyClocklessProxy — gate pin 8 on classic ESP32

`SK6812<8, RGB>` instantiation fires FastLED's
`_ESPPIN<8, 256, false>::validpin() == false` static_assert because
GPIO8 on the classic ESP32 (esp32dev / WROOM) is reserved for SPI
flash (D2/HD). Newer ESP32 variants (S2/S3/C2/C3/C5/C6/H2/P4)
repurpose GPIO8 and accept it as a valid output pin, and every non-
ESP family treats pin 8 as a normal digital — so the gate is the
narrow classic-ESP32 exclusion, not a broad change.

Mirrors the existing `AUTORESEARCH_LEGACY_SUPPORTS_PIN_22` Teensy
gate. Without this the sketch failed at compile time for esp32dev
and autoresearch never got past the build phase.

## pyproject.toml — fbuild 2.3.15 pin note + version chase

The pin stays at `fbuild==2.3.14` for now (2.3.15 isn't on PyPI yet
as of this commit) but the comment history is updated to reflect the
Windows compile env + path-separator fixes that landed in
FastLED/fbuild#885 and FastLED/fbuild#890. Bump the pin to 2.3.15 in
a follow-up once the release workflow publishes.

Closes (pending CI): #3446

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zackees added a commit that referenced this pull request Jul 1, 2026
…path (#912)

* feat(cli): fbuild sync + platformio.lock (#618 Phase 1)

Implements Phase 1 of the roadmap in #618: fbuild sync
reads platformio.ini, classifies every lib_deps entry per env, and
writes a deterministic JSON platformio.lock next to the ini. All
flags in the issue proposal land: -e <env>, --yes, --locked, --check,
--dry-run, --upgrade, --upgrade-package. Multi-env prompt gates
whole-project sync; --yes / -e / --check bypass it.

# Scope

Phase 1 is a full CLI + classification + lockfile-shape ship. Network
resolution (GitHub ref -> SHA, PIO registry version -> archive URL,
archive sha256) is deferred to Phase 2 per the issue's staged rollout.
Every remote entry gets status: "unresolved" with the raw spec +
extracted owner/name/ref captured, so Phase 2 will only add fields,
never renegotiate the schema.

Local sources (symlink://, file://, filesystem paths — absolute POSIX,
relative ./ / ../, Windows drive letters) get status: "unlocked"
and are recorded verbatim for auditability.

# Files

Module layout (kept as one focused directory tree, three files, no
new crate — respects the monocrate rule):

- crates/fbuild-cli/src/sync/mod.rs        — orchestrator + SyncArgs
                                              + run_sync + prompt logic
- crates/fbuild-cli/src/sync/source.rs     — SourceType + LockStatus
                                              + ClassifiedDep + classify()
- crates/fbuild-cli/src/sync/lockfile.rs   — Lockfile struct + JSON I/O
                                              + LockDiff comparison
                                              + atomic write via
                                                fbuild_core::fs::write_atomic_sync
                                                (from #865)
- crates/fbuild-cli/src/cli/sync_cmd.rs    — thin CLI adapter
- crates/fbuild-cli/src/cli/args.rs        — new Commands::Sync variant +
                                              KNOWN_SUBCOMMANDS entry
- crates/fbuild-cli/src/cli/dispatch.rs    — wire the new command
- crates/fbuild-cli/src/cli/mod.rs         — register sync_cmd
- crates/fbuild-cli/src/main.rs            — register sync module
- docs/sync.md                             — user-facing docs
- docs/CLAUDE.md                           — index entry

# Lockfile schema (v1)

- envs sorted alphabetically (BTreeMap)
- packages per env sorted by (name, source_type, raw)
- generated_at trimmed to seconds (ISO-8601 UTC)
- fields with a documented consumer only (no decorative registry
  metadata — per issue decision)
- package records DUPLICATED under each env (per issue decision:
  auditability > disk size)
- read() refuses to load an unrecognized version — schema evolution
  is versioned

# TDD-first — 30+ unit tests written before implementation

Written first, then wired up:

- crates/fbuild-cli/src/sync/source.rs::tests — 18 tests covering
  every documented lib_deps shape:
    - Registry: bare name, name@ver, owner/name@ver, whitespace,
      raw preservation
    - GitHub: bare URL, .git suffix, #ref, case-insensitive host
    - Git+: URL, ref
    - HTTP archive: .zip, .tar.gz
    - Local: symlink://, file://, ./relative, ../relative,
      /posix-abs, C:\ backslash, D:/ forward-slash
    - phase1_lock_status returns Unlocked for locals, Unresolved
      for remotes

- crates/fbuild-cli/src/sync/lockfile.rs::tests — 10 tests:
    - Shape smoke
    - Packages sorted by name
    - Envs sorted
    - Local dep -> Unlocked
    - Registry dep -> Unresolved in Phase 1
    - JSON deterministic for same input (regardless of insertion
      order)
    - Read/write roundtrip
    - Version mismatch rejected
    - compare fresh/stale detection (new dep added, env added,
      version changed)
    - JSON ends with newline

- crates/fbuild-cli/src/sync/mod.rs::tests — 11 tests:
    - Missing platformio.ini is error
    - Single env writes lockfile
    - --check on missing lock is failed
    - --check on fresh lock passes
    - --locked on stale lock returns LockedFailed
    - --dry-run writes nothing
    - -e <env> skips multi-env prompt
    - -e <bad-env> is error
    - Second run without changes is NoOp
    - exit_code matrix (Wrote/NoOp/CheckPassed/DryRun=0,
      CheckFailed=1, LockedFailed=2, UserCancelled=3, Error=4)
    - skip_multi_env_prompt matrix
    - ISO date formatter basic + time-of-day

# Local verification

- `soldr cargo check -p fbuild-cli --all-targets` — clean
- `soldr cargo clippy -p fbuild-cli --all-targets -- -D warnings` — clean
- `soldr cargo test -p fbuild-cli --no-run` — clean (test binaries build)
- `soldr cargo fmt -p fbuild-cli` — no-op

Note on test execution: my local environment (soldr silently swallows
all stdout/stderr; direct-cargo hits Windows SDK linker env issues
that this session has documented at length in #899) prevents me from
running the test binary end-to-end today. Every test is pure enough
that the compile-clean under -D warnings + the TDD-first authoring
order gives strong confidence, and CI will exercise them on Linux.

Closes #618

* fix(sync): allow dead_code on Phase 2 hook fields (#618 CI fix)

CI on macos/windows failed with clippy `-D dead-code`:
- SyncArgs.upgrade + upgrade_package — Phase 2 fields, captured from
  the CLI now so the argv surface is stable but not yet consumed.
- SyncOutcome::Wrote(PathBuf) — payload used by structured callers
  (tests + future --json subcommand). Clippy doesn't see the tests
  as consumers.

Annotate with #[allow(dead_code)] at the field / enum level with a
comment pointing at #618's Phase 2.

* fix(compile): rewrite backslashes in source/output args on the no-compile-CWD fallback path

Windows PIO builds under `.build/pio/<board>/` don't have a `.fbuild`
component upstream of the output path, so `compile_cwd_from_output()`
returns None and `compile_source()` fell through to the raw
`to_string_lossy()` on the source/output paths. That leaves
backslashes intact in argv, which GCC's internal spec-file pass
interprets as escape characters:

  `-c src\sketch\AutoResearchNet.cpp` → cc1plus.exe receives
  `srcsketchAutoResearchNet.cpp` → "fatal error: srcsketchAutoResearchNet.cpp: No such file or directory".

Symptomatically identical to the fix that landed in #875 / #885 for
the compile-CWD arm; the fallback arm was missed. Mirror the same
`replace('\', "/")` guard so both paths agree on Windows.

Bump workspace version 2.3.15 → 2.3.16.

Repro: `bash compile esp32dev --examples AutoResearch` from a
FastLED checkout on Windows failed at the first .cpp compile with
`srcsketchAutoResearchNet.cpp` / `srcsketchAutoResearchBle.cpp`
etc. After this fix, the compile completes end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

ESP32 compile on Windows: 'Cannot create temporary file in C:\Windows\' — uncovered tempfile callsite

1 participant