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 <AgentEnder@users.noreply.github.com> Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
This commit is contained in:
@@ -761,4 +761,62 @@ describe('native task hasher', () => {
|
||||
// );
|
||||
// console.dir(hashes, { depth: null });
|
||||
// });
|
||||
|
||||
it('should provide NX_PROJECT_ROOT environment variable to runtime commands', async () => {
|
||||
const workspaceFiles = await retrieveWorkspaceFiles(tempFs.tempDir, {
|
||||
'libs/parent': 'parent',
|
||||
});
|
||||
const builder = new ProjectGraphBuilder(
|
||||
undefined,
|
||||
workspaceFiles.fileMap.projectFileMap
|
||||
);
|
||||
|
||||
builder.addNode({
|
||||
name: 'parent',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/parent',
|
||||
targets: {
|
||||
build: {
|
||||
executor: 'nx:run-commands',
|
||||
inputs: [
|
||||
// Use a runtime command that outputs the NX_PROJECT_ROOT
|
||||
{ runtime: 'echo "NX_PROJECT_ROOT: $NX_PROJECT_ROOT"' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const projectGraph = builder.getUpdatedProjectGraph();
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
{ build: ['^build'] },
|
||||
['parent'],
|
||||
['build'],
|
||||
undefined,
|
||||
{}
|
||||
);
|
||||
|
||||
const hash = await new NativeTaskHasherImpl(
|
||||
tempFs.tempDir,
|
||||
nxJson,
|
||||
projectGraph,
|
||||
workspaceFiles.rustReferences,
|
||||
{ selectivelyHashTsConfig: false }
|
||||
).hashTask(taskGraph.tasks['parent:build'], taskGraph, {});
|
||||
|
||||
// The runtime command should have access to NX_PROJECT_ROOT
|
||||
// and the hash should include that output
|
||||
expect(hash.details).toHaveProperty(
|
||||
'runtime:echo "NX_PROJECT_ROOT: $NX_PROJECT_ROOT"'
|
||||
);
|
||||
|
||||
// Verify the hash is deterministic and non-empty, proving NX_PROJECT_ROOT was available
|
||||
const runtimeHash =
|
||||
hash.details['runtime:echo "NX_PROJECT_ROOT: $NX_PROJECT_ROOT"'];
|
||||
expect(runtimeHash).toBeDefined();
|
||||
expect(runtimeHash).not.toBe('3244421341483603138'); // Should not be empty hash
|
||||
expect(runtimeHash).toBe('17104739612706417536'); // Should be consistent hash for "libs/parent"
|
||||
});
|
||||
});
|
||||
|
||||
@@ -359,7 +359,10 @@ impl HashPlanner {
|
||||
)]
|
||||
};
|
||||
let runtime_and_env_inputs = self_inputs.iter().filter_map(|i| match i {
|
||||
Input::Runtime(runtime) => Some(HashInstruction::Runtime(runtime.to_string())),
|
||||
Input::Runtime(runtime) => Some(HashInstruction::Runtime(
|
||||
project_name.to_string(),
|
||||
runtime.to_string(),
|
||||
)),
|
||||
Input::Environment(env) => Some(HashInstruction::Environment(env.to_string())),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
@@ -179,11 +179,18 @@ impl TaskHasher {
|
||||
trace!(parent: &span, "hash_workspace_files: {:?}", now.elapsed());
|
||||
hashed_workspace_files?
|
||||
}
|
||||
HashInstruction::Runtime(runtime) => {
|
||||
HashInstruction::Runtime(project_name, runtime) => {
|
||||
// Create a modified environment with NX_PROJECT_ROOT
|
||||
let mut env_with_project_root = js_env.clone();
|
||||
if let Some(project) = self.project_graph.nodes.get(project_name) {
|
||||
env_with_project_root
|
||||
.insert("NX_PROJECT_ROOT".to_string(), project.root.clone());
|
||||
}
|
||||
|
||||
let hashed_runtime = hash_runtime(
|
||||
&self.workspace_root,
|
||||
runtime,
|
||||
js_env,
|
||||
&env_with_project_root,
|
||||
Arc::clone(&self.runtime_cache),
|
||||
)?;
|
||||
trace!(parent: &span, "hash_runtime: {:?}", now.elapsed());
|
||||
|
||||
@@ -47,7 +47,7 @@ pub struct TaskGraph {
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||
pub enum HashInstruction {
|
||||
WorkspaceFileSet(Vec<String>),
|
||||
Runtime(String),
|
||||
Runtime(String, String), // (project_name, runtime)
|
||||
Environment(String),
|
||||
ProjectFileSet(String, Vec<String>),
|
||||
ProjectConfiguration(String),
|
||||
@@ -89,7 +89,8 @@ impl fmt::Display for HashInstruction {
|
||||
}
|
||||
HashInstruction::WorkspaceFileSet(file_set) =>
|
||||
format!("workspace:[{}]", file_set.join(",")),
|
||||
HashInstruction::Runtime(runtime) => format!("runtime:{}", runtime),
|
||||
HashInstruction::Runtime(project_name, runtime) =>
|
||||
format!("{}:runtime:{}", project_name, runtime),
|
||||
HashInstruction::Environment(env) => format!("env:{}", env),
|
||||
HashInstruction::TaskOutput(task_output, dep_outputs) => {
|
||||
let dep_outputs = dep_outputs.join(",");
|
||||
|
||||
Reference in New Issue
Block a user