Skip to content

Commit 5679b3e

Browse files
AgentEnderclaude[bot]nx-cloud[bot]
authored
feat(core): add NX_PROJECT_ROOT environment variable to runtime cache inputs (#31428)
Add NX_PROJECT_ROOT environment variable to runtime cache inputs ## Current Behavior Runtime cache input commands do not have access to project-specific context, making it impossible to create project-aware runtime inputs. ## Expected Behavior Runtime cache input commands can access `$NX_PROJECT_ROOT` environment variable containing the project's root directory path. ## Related Issue(s) Fixes #20949 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: AgentEnder <[email protected]> Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
1 parent 2c678a1 commit 5679b3e

File tree

4 files changed

+74
-5
lines changed

4 files changed

+74
-5
lines changed

packages/nx/src/hasher/native-task-hasher-impl.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,4 +761,62 @@ describe('native task hasher', () => {
761761
// );
762762
// console.dir(hashes, { depth: null });
763763
// });
764+
765+
it('should provide NX_PROJECT_ROOT environment variable to runtime commands', async () => {
766+
const workspaceFiles = await retrieveWorkspaceFiles(tempFs.tempDir, {
767+
'libs/parent': 'parent',
768+
});
769+
const builder = new ProjectGraphBuilder(
770+
undefined,
771+
workspaceFiles.fileMap.projectFileMap
772+
);
773+
774+
builder.addNode({
775+
name: 'parent',
776+
type: 'lib',
777+
data: {
778+
root: 'libs/parent',
779+
targets: {
780+
build: {
781+
executor: 'nx:run-commands',
782+
inputs: [
783+
// Use a runtime command that outputs the NX_PROJECT_ROOT
784+
{ runtime: 'echo "NX_PROJECT_ROOT: $NX_PROJECT_ROOT"' },
785+
],
786+
},
787+
},
788+
},
789+
});
790+
791+
const projectGraph = builder.getUpdatedProjectGraph();
792+
const taskGraph = createTaskGraph(
793+
projectGraph,
794+
{ build: ['^build'] },
795+
['parent'],
796+
['build'],
797+
undefined,
798+
{}
799+
);
800+
801+
const hash = await new NativeTaskHasherImpl(
802+
tempFs.tempDir,
803+
nxJson,
804+
projectGraph,
805+
workspaceFiles.rustReferences,
806+
{ selectivelyHashTsConfig: false }
807+
).hashTask(taskGraph.tasks['parent:build'], taskGraph, {});
808+
809+
// The runtime command should have access to NX_PROJECT_ROOT
810+
// and the hash should include that output
811+
expect(hash.details).toHaveProperty(
812+
'runtime:echo "NX_PROJECT_ROOT: $NX_PROJECT_ROOT"'
813+
);
814+
815+
// Verify the hash is deterministic and non-empty, proving NX_PROJECT_ROOT was available
816+
const runtimeHash =
817+
hash.details['runtime:echo "NX_PROJECT_ROOT: $NX_PROJECT_ROOT"'];
818+
expect(runtimeHash).toBeDefined();
819+
expect(runtimeHash).not.toBe('3244421341483603138'); // Should not be empty hash
820+
expect(runtimeHash).toBe('17104739612706417536'); // Should be consistent hash for "libs/parent"
821+
});
764822
});

packages/nx/src/native/tasks/hash_planner.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,10 @@ impl HashPlanner {
359359
)]
360360
};
361361
let runtime_and_env_inputs = self_inputs.iter().filter_map(|i| match i {
362-
Input::Runtime(runtime) => Some(HashInstruction::Runtime(runtime.to_string())),
362+
Input::Runtime(runtime) => Some(HashInstruction::Runtime(
363+
project_name.to_string(),
364+
runtime.to_string(),
365+
)),
363366
Input::Environment(env) => Some(HashInstruction::Environment(env.to_string())),
364367
_ => None,
365368
});

packages/nx/src/native/tasks/task_hasher.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,18 @@ impl TaskHasher {
179179
trace!(parent: &span, "hash_workspace_files: {:?}", now.elapsed());
180180
hashed_workspace_files?
181181
}
182-
HashInstruction::Runtime(runtime) => {
182+
HashInstruction::Runtime(project_name, runtime) => {
183+
// Create a modified environment with NX_PROJECT_ROOT
184+
let mut env_with_project_root = js_env.clone();
185+
if let Some(project) = self.project_graph.nodes.get(project_name) {
186+
env_with_project_root
187+
.insert("NX_PROJECT_ROOT".to_string(), project.root.clone());
188+
}
189+
183190
let hashed_runtime = hash_runtime(
184191
&self.workspace_root,
185192
runtime,
186-
js_env,
193+
&env_with_project_root,
187194
Arc::clone(&self.runtime_cache),
188195
)?;
189196
trace!(parent: &span, "hash_runtime: {:?}", now.elapsed());

packages/nx/src/native/tasks/types.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub struct TaskGraph {
4747
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
4848
pub enum HashInstruction {
4949
WorkspaceFileSet(Vec<String>),
50-
Runtime(String),
50+
Runtime(String, String), // (project_name, runtime)
5151
Environment(String),
5252
ProjectFileSet(String, Vec<String>),
5353
ProjectConfiguration(String),
@@ -89,7 +89,8 @@ impl fmt::Display for HashInstruction {
8989
}
9090
HashInstruction::WorkspaceFileSet(file_set) =>
9191
format!("workspace:[{}]", file_set.join(",")),
92-
HashInstruction::Runtime(runtime) => format!("runtime:{}", runtime),
92+
HashInstruction::Runtime(project_name, runtime) =>
93+
format!("{}:runtime:{}", project_name, runtime),
9394
HashInstruction::Environment(env) => format!("env:{}", env),
9495
HashInstruction::TaskOutput(task_output, dep_outputs) => {
9596
let dep_outputs = dep_outputs.join(",");

0 commit comments

Comments
 (0)