RFC: centralize cargo-env handling in `tools/config.R`
Nobody has claimed this yet.
- Dominant language
- R
- Stars
- 263
- Forks
- 35
- Avg merge
- 8d 9h
- Merged PRs (30d)
- 2
Description
The fix in #515 / extendr#1088 unblocks the immediate "everything rebuilds twice" pain, but it papers over a deeper issue: the configure script should inherit whatever environment is set when R CMD INSTALL runs, because those env vars are what inst/templates/Makevars.in and inst/templates/Makevars.win.in end up reading at build time. So tools/config.R should do more than just substitute literal flags — it should be the single place where we decide which env vars get threaded into cargo, in what form, and so that both cargo build --lib and cargo run --bin document (and any future cargo invocations we add) see an identical environment. Right now those two invocations drift the moment anyone touches one line and forgets the other — which is exactly how #1087 happened.
AI-written details
Problem
There are two env-inheritance layers in play and they aren't well separated:
- Build-time substitution (
tools/config.R→gsubon the templates) — handles@PROFILE@,@CRAN_FLAGS@,@TARGET@,@PANIC_EXPORTS@,@LIBDIR@,@CLEAN_TARGET@. Each placeholder is computed once in R based on the environment atconfiguretime (Sys.getenv("DEBUG"),Sys.getenv("NOT_CRAN"), target-arch detection, etc.) and inlined. - Make-time expansion —
RUSTFLAGS,CARGO_HOME,PATH,LIBRARY_PATHare set inside the recipe and re-exported per recipe line.
Today, anyone adding a cargo invocation has to manually replicate both layers. #1087 is the canonical failure mode: the cargo run --bin document line was missing the RUSTFLAGS, @PANIC_EXPORTS@, and @PROFILE@ that cargo build --lib already had, so the second invocation had a different cargo fingerprint and rebuilt every dependency from scratch. The fix in #515 is just "copy the prefix to the second line" — which works, but the next cargo invocation we add will have the same trap waiting.
Design options
(A) @CARGO_ENV@ placeholder (recommended)
tools/config.R builds a single shell-syntax string of KEY=VAL pairs (RUSTFLAGS, panic exports, target dir overrides, anything env-shaped) and substitutes it into every cargo invocation line in the template:
$(STATLIB):
@CARGO_ENV@ cargo build @CRAN_FLAGS@ --lib @PROFILE@ ...
@CARGO_ENV@ cargo run @CRAN_FLAGS@ --bin document @PROFILE@ ...
Pros: one knob to turn in R, zero duplication in the Makevars, easy to extend (just append to the string in config.R). Adding a new cargo call is "paste the line, keep @CARGO_ENV@ in front." Make-level vars like $(RUSTFLAGS) can still be referenced inside the substituted string.
Cons: the substitution string is opaque in the rendered Makevars — a contributor reading the installed Makevars sees one big prefix per line, not the individual flags. Mitigated by a comment in config.R listing what goes in.
(B) tools/cargo-env.sh
config.R generates a sourceable shell file with export KEY=VAL lines; each recipe . ./tools/cargo-env.sh && before invoking cargo.
Pros: env is a real shell file you can cat to debug.
Cons: extra file in tools/, recipes get noisier (. ./tools/cargo-env.sh && export ... && cargo ...), Windows nmake-style portability needs checking. Doesn't really gain over (A).
(C) [env] table in generated .cargo/config.toml
config.R writes a [env] table into the already-generated .cargo/config.toml. Cargo natively reads it and applies to every invocation.
Pros: idiomatic cargo, single source of truth, automatically covers any cargo subcommand.
Cons: only reaches cargo-known env (RUSTFLAGS via [build] rustflags, but [env] for arbitrary keys). Doesn't help Make-level vars like LIBRARY_PATH that need to be set in the shell before cargo runs. Best combined with (A) rather than instead of it.
Recommendation
Option (A). Smallest delta from the current shape, zero new files, zero duplication risk for future cargo invocations. (C) is a complementary refinement worth doing later if we want cargo-native discoverability, but (A) is the load-bearing fix.
Scope
- Touch only
inst/templates/Makevars.in,inst/templates/Makevars.win.in,inst/templates/tools/config.R(or wherever the substitution lives). - Land after #515 so this isn't blocking the #1087 hotfix.
- Mirror the same template change in
extendr/extendr's in-tree generated copy (tests/extendrtests/src/Makevars*.in) — same trap as #1088.
Notes
- Discovered while diagnosing #1087: the rextendr template and the extendr in-tree copy had drifted, and the second cargo invocation was missing three different prefix elements. The right long-term fix is structural, not "remember to copy the prefix."
- This is RFC-shaped on purpose — happy to be talked out of (A) toward (C)-primary if there's a strong preference for cargo-native config over template substitution.
Drafted by Claude (claude-opus-4-7). Reviewed by the author.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with tools/config.R and compare the cargo invocations in inst/templates/Makevars.in and inst/templates/Makevars.win.in, then inspect the related changes in #515 and #1087. Confirm how the in-tree extendr templates under tests/extendrtests/src/ are kept aligned. Done means the chosen RFC approach is agreed, all listed cargo invocations receive the same environment, and the generated build configuration works on the supported platforms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- r, rust
- Domain
- build-system, tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100