nx/packages/devkit/src/executors/parse-target-string.ts
Craigory Coppola 5721ea3c21
feat(core): lock graph creation when running in another process (#29408)
## Current Behavior
Running Nx in multiple processes at the same time with the daemon
disabled can cripple a system due to excess memory usage when creating
the graph. This is due to plugin workers being started per-parent
process when there is no daemon. This change enables a file lock to
prevent the simultaneous processing, and read from the cache when the
first run completes.

Currently, running `nx show projects` 30 times in parallel looks
something like this:

30 processes exited within 37535ms

## Expected Behavior
30 processes exited within 6435ms

## Test Script
```js
//@ts-check

const { spawn } = require('child_process');

let alive = new Set();

let start = Date.now();
let iterations = 30;

for (let i = 0; i < iterations; i++) {
  const cp = spawn('npx nx show projects', [], {
    shell: true,
    env: {
      ...process.env,
      NX_DAEMON: 'false',
      NX_VERBOSE_LOGGING: 'true',
    },
  });
  alive.add(i);
  //   cp.stdout.on('data', (data) => {
  //     console.log(`stdout [${i}]: ${data}`);
  //   });
  cp.stderr.on('data', (data) => {
    console.error(`stderr [${i}]: ${data}`);
  });
  cp.on('exit', (code) => {
    console.log(`child process ${i} exited with code ${code}`);
    alive.delete(i);
  });
}

const i = setInterval(() => {
  if (alive.size > 0) {
  } else {
    clearInterval(i);
    console.log(
      `${iterations} processes exited within ${Date.now() - start}ms`
    );
  }
}, 1);

```
2025-01-28 09:46:52 -05:00

102 lines
2.9 KiB
TypeScript

import {
ExecutorContext,
ProjectGraph,
readCachedProjectGraph,
Target,
} from 'nx/src/devkit-exports';
import { splitByColons, splitTarget } from 'nx/src/devkit-internals';
/**
* Parses a target string into {project, target, configuration}
*
* Examples:
* ```typescript
* parseTargetString("proj:test", graph) // returns { project: "proj", target: "test" }
* parseTargetString("proj:test:production", graph) // returns { project: "proj", target: "test", configuration: "production" }
* ```
*
* @param targetString - target reference
*/
export function parseTargetString(
targetString: string,
projectGraph: ProjectGraph
): Target;
/**
* Parses a target string into {project, target, configuration}. Passing a full
* {@link ExecutorContext} enables the targetString to reference the current project.
*
* Examples:
* ```typescript
* parseTargetString("test", executorContext) // returns { project: "proj", target: "test" }
* parseTargetString("proj:test", executorContext) // returns { project: "proj", target: "test" }
* parseTargetString("proj:test:production", executorContext) // returns { project: "proj", target: "test", configuration: "production" }
* ```
*/
export function parseTargetString(
targetString: string,
ctx: ExecutorContext
): Target;
export function parseTargetString(
targetString: string,
projectGraphOrCtx?: ProjectGraph | ExecutorContext
): Target {
let projectGraph: ProjectGraph =
projectGraphOrCtx && 'projectGraph' in projectGraphOrCtx
? projectGraphOrCtx.projectGraph
: (projectGraphOrCtx as ProjectGraph);
if (!projectGraph) {
try {
projectGraph = readCachedProjectGraph();
} catch (e) {
projectGraph = { nodes: {} } as any;
}
}
const [maybeProject] = splitByColons(targetString);
if (
!projectGraph.nodes[maybeProject] &&
projectGraphOrCtx &&
'projectName' in projectGraphOrCtx &&
maybeProject !== projectGraphOrCtx.projectName
) {
targetString = `${projectGraphOrCtx.projectName}:${targetString}`;
}
const [project, target, configuration] = splitTarget(
targetString,
projectGraph
);
if (!project || !target) {
throw new Error(`Invalid Target String: ${targetString}`);
}
return {
project,
target,
configuration,
};
}
/**
* Returns a string in the format "project:target[:configuration]" for the target
*
* @param target - target object
*
* Examples:
*
* ```typescript
* targetToTargetString({ project: "proj", target: "test" }) // returns "proj:test"
* targetToTargetString({ project: "proj", target: "test", configuration: "production" }) // returns "proj:test:production"
* ```
*/
export function targetToTargetString({
project,
target,
configuration,
}: Target): string {
return `${project}:${target.indexOf(':') > -1 ? `"${target}"` : target}${
configuration !== undefined ? ':' + configuration : ''
}`;
}