diff --git a/src/graph/builder.cpp b/src/graph/builder.cpp index aae31b8..0676fbf 100644 --- a/src/graph/builder.cpp +++ b/src/graph/builder.cpp @@ -1192,7 +1192,18 @@ auto process_import( if (auto const* env_val = std::getenv(name_buf.c_str())) { value_id = intern(env_val); } - // 2. Fall back to cached value from previous build (passed via options) + // 2. If the author wrote `?= default`, that default is the explicit + // revert-target for the env-unset case and wins over the cache. + else if (imp.default_value) { + auto expanded = parser::expand(*ctx.eval, *imp.default_value); + if (!expanded) { + return pup::unexpected(expanded.error()); + } + value_id = *expanded; + } + // 3. No env, no default — fall back to the cached value from the previous + // build so plain `import VAR` stays stable across shell sessions that + // forget to re-export the variable. else if (auto it = std::lower_bound( state.options.cached_env_vars.begin(), state.options.cached_env_vars.end(), @@ -1202,14 +1213,6 @@ auto process_import( it != state.options.cached_env_vars.end() && str(it->first) == var_name_sv) { value_id = it->second; } - // 3. Fall back to default value - else if (imp.default_value) { - auto expanded = parser::expand(*ctx.eval, *imp.default_value); - if (!expanded) { - return pup::unexpected(expanded.error()); - } - value_id = *expanded; - } auto value_sv = str(value_id); diff --git a/test/unit/test_e2e.cpp b/test/unit/test_e2e.cpp index 740648f..dd78ce6 100644 --- a/test/unit/test_e2e.cpp +++ b/test/unit/test_e2e.cpp @@ -4343,6 +4343,39 @@ SCENARIO("import with ?= operator", "[e2e][import]") } } +SCENARIO("import ?= default reverts when env is unset on warm index", "[e2e][import]") +{ + GIVEN("a Tupfile with import VAR ?= default, after a build that captured the env value") + { + auto f = E2EFixture { "import_soft_set" }; + + { + auto env = EnvGuard { "MY_VAR", "from_env" }; + REQUIRE(f.init().success()); + auto build1 = f.build(); + REQUIRE(build1.success()); + REQUIRE(f.read_file("out.txt") == "from_env\n"); + } + + WHEN("the env var is unset and the project is rebuilt") + { + auto build2 = f.build(); + + THEN("build succeeds") + { + INFO("stdout: " << build2.stdout_output); + INFO("stderr: " << build2.stderr_output); + REQUIRE(build2.success()); + } + + THEN("the default value is restored, not the cached env value") + { + REQUIRE(f.read_file("out.txt") == "default_value\n"); + } + } + } +} + // ============================================================================= // Error Handling Tests // =============================================================================