UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

328 lines (324 loc) โ€ข 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.readCachedProjectGraph = readCachedProjectGraph; exports.readCachedProjectConfiguration = readCachedProjectConfiguration; exports.readProjectsConfigurationFromProjectGraph = readProjectsConfigurationFromProjectGraph; exports.buildProjectGraphAndSourceMapsWithoutDaemon = buildProjectGraphAndSourceMapsWithoutDaemon; exports.handleProjectGraphError = handleProjectGraphError; exports.createProjectGraphAsync = createProjectGraphAsync; exports.createProjectGraphAndSourceMapsAsync = createProjectGraphAndSourceMapsAsync; const perf_hooks_1 = require("perf_hooks"); const nx_json_1 = require("../config/nx-json"); const client_1 = require("../daemon/client/client"); const tmp_dir_1 = require("../daemon/tmp-dir"); const fileutils_1 = require("../utils/fileutils"); const output_1 = require("../utils/output"); const strip_indents_1 = require("../utils/strip-indents"); const workspace_root_1 = require("../utils/workspace-root"); const build_project_graph_1 = require("./build-project-graph"); const error_types_1 = require("./error-types"); const nx_deps_cache_1 = require("./nx-deps-cache"); const retrieve_workspace_files_1 = require("./utils/retrieve-workspace-files"); const get_plugins_1 = require("./plugins/get-plugins"); const logger_1 = require("../utils/logger"); const native_1 = require("../native"); const path_1 = require("path"); const cache_directory_1 = require("../utils/cache-directory"); const delayed_spinner_1 = require("../utils/delayed-spinner"); /** * Synchronously reads the latest cached copy of the workspace's ProjectGraph. * * @param {number} [minimumComputedAt] - The minimum timestamp that the cached ProjectGraph must have been computed at. * @throws {Error} if there is no cached ProjectGraph to read from */ function readCachedProjectGraph(minimumComputedAt) { const projectGraphCache = (0, nx_deps_cache_1.readProjectGraphCache)(minimumComputedAt); if (!projectGraphCache) { const angularSpecificError = (0, fileutils_1.fileExists)(`${workspace_root_1.workspaceRoot}/angular.json`) ? (0, strip_indents_1.stripIndents) ` Make sure invoke 'node ./decorate-angular-cli.js' in your postinstall script. The decorated CLI will compute the project graph. 'ng --help' should say 'Smart Monorepos ยท Fast CI'. ` : ''; throw new Error((0, strip_indents_1.stripIndents) ` [readCachedProjectGraph] ERROR: No cached ProjectGraph is available. If you are leveraging \`readCachedProjectGraph()\` directly then you will need to refactor your usage to first ensure that the ProjectGraph is created by calling \`await createProjectGraphAsync()\` somewhere before attempting to read the data. If you encounter this error as part of running standard \`nx\` commands then please open an issue on https://github.com/nrwl/nx ${angularSpecificError} `); } return projectGraphCache; } function readCachedProjectConfiguration(projectName) { const graph = readCachedProjectGraph(); const node = graph.nodes[projectName]; try { return node.data; } catch (e) { throw new Error(`Cannot find project: '${projectName}' in your workspace.`); } } /** * Get the {@link ProjectsConfigurations} from the {@link ProjectGraph} */ function readProjectsConfigurationFromProjectGraph(projectGraph) { return { projects: Object.fromEntries(Object.entries(projectGraph.nodes).map(([project, { data }]) => [ project, data, ])), version: 2, }; } async function buildProjectGraphAndSourceMapsWithoutDaemon() { global.NX_GRAPH_CREATION = true; const nxJson = (0, nx_json_1.readNxJson)(); perf_hooks_1.performance.mark('retrieve-project-configurations:start'); let configurationResult; let projectConfigurationsError; const plugins = await (0, get_plugins_1.getPlugins)(); try { configurationResult = await (0, retrieve_workspace_files_1.retrieveProjectConfigurations)(plugins, workspace_root_1.workspaceRoot, nxJson); } catch (e) { if (e instanceof error_types_1.ProjectConfigurationsError) { projectConfigurationsError = e; configurationResult = e.partialProjectConfigurationsResult; } else { throw e; } } const { projects, externalNodes, sourceMaps, projectRootMap } = configurationResult; perf_hooks_1.performance.mark('retrieve-project-configurations:end'); perf_hooks_1.performance.mark('retrieve-workspace-files:start'); const { allWorkspaceFiles, fileMap, rustReferences } = await (0, retrieve_workspace_files_1.retrieveWorkspaceFiles)(workspace_root_1.workspaceRoot, projectRootMap); perf_hooks_1.performance.mark('retrieve-workspace-files:end'); const cacheEnabled = process.env.NX_CACHE_PROJECT_GRAPH !== 'false'; perf_hooks_1.performance.mark('build-project-graph-using-project-file-map:start'); let projectGraphError; let projectGraphResult; try { projectGraphResult = await (0, build_project_graph_1.buildProjectGraphUsingProjectFileMap)(projects, externalNodes, fileMap, allWorkspaceFiles, rustReferences, cacheEnabled ? (0, nx_deps_cache_1.readFileMapCache)() : null, plugins, sourceMaps); } catch (e) { if ((0, error_types_1.isAggregateProjectGraphError)(e)) { projectGraphResult = { projectGraph: e.partialProjectGraph, projectFileMapCache: null, }; projectGraphError = e; } else { throw e; } } const { projectGraph, projectFileMapCache } = projectGraphResult; perf_hooks_1.performance.mark('build-project-graph-using-project-file-map:end'); delete global.NX_GRAPH_CREATION; const errors = [ ...(projectConfigurationsError?.errors ?? []), ...(projectGraphError?.errors ?? []), ]; if (cacheEnabled) { (0, nx_deps_cache_1.writeCache)(projectFileMapCache, projectGraph, sourceMaps, errors); } if (errors.length > 0) { throw new error_types_1.ProjectGraphError(errors, projectGraph, sourceMaps); } else { return { projectGraph, sourceMaps }; } } function handleProjectGraphError(opts, e) { if (opts.exitOnError) { const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true'; if (e instanceof error_types_1.ProjectGraphError) { let title = e.message; const bodyLines = isVerbose ? [e.stack] : ['Pass --verbose to see the stacktraces.']; output_1.output.error({ title, bodyLines: bodyLines, }); } else if (typeof e.message === 'string') { const lines = e.message.split('\n'); output_1.output.error({ title: lines[0], bodyLines: lines.slice(1), }); if (isVerbose) { console.error(e); } } else { console.error(e); } process.exit(1); } else { throw e; } } async function readCachedGraphAndHydrateFileMap(minimumComputedAt) { const graph = readCachedProjectGraph(minimumComputedAt); const projectRootMap = Object.fromEntries(Object.entries(graph.nodes).map(([project, { data }]) => [ data.root, project, ])); const { allWorkspaceFiles, fileMap, rustReferences } = await (0, retrieve_workspace_files_1.retrieveWorkspaceFiles)(workspace_root_1.workspaceRoot, projectRootMap); (0, build_project_graph_1.hydrateFileMap)(fileMap, allWorkspaceFiles, rustReferences); return graph; } /** * Computes and returns a ProjectGraph. * * Nx will compute the graph either in a daemon process or in the current process. * * Nx will compute it in the current process if: * * The process is running in CI (CI env variable is to true or other common variables used by CI providers are set). * * It is running in the docker container. * * The daemon process is disabled because of the previous error when starting the daemon. * * `NX_DAEMON` is set to `false`. * * `useDaemonProcess` is set to false in the options of the tasks runner inside `nx.json` * * `NX_DAEMON` env variable takes precedence: * * If it is set to true, the daemon will always be used. * * If it is set to false, the graph will always be computed in the current process. * * Tip: If you want to debug project graph creation, run your command with NX_DAEMON=false. * * Nx uses two layers of caching: the information about explicit dependencies stored on the disk and the information * stored in the daemon process. To reset both run: `nx reset`. */ async function createProjectGraphAsync(opts = { exitOnError: false, resetDaemonClient: false, }) { if (process.env.NX_FORCE_REUSE_CACHED_GRAPH === 'true') { try { // If no cached graph is found, we will fall through to the normal flow const graph = await readCachedGraphAndHydrateFileMap(); return graph; } catch (e) { if (e instanceof error_types_1.ProjectGraphError) { throw e; } logger_1.logger.verbose('Unable to use cached project graph', e); } } const projectGraphAndSourceMaps = await createProjectGraphAndSourceMapsAsync(opts); return projectGraphAndSourceMaps.projectGraph; } async function createProjectGraphAndSourceMapsAsync(opts = { exitOnError: false, resetDaemonClient: false, }) { perf_hooks_1.performance.mark('create-project-graph-async:start'); if (!client_1.daemonClient.enabled()) { const lock = !native_1.IS_WASM ? new native_1.FileLock((0, path_1.join)(cache_directory_1.workspaceDataDirectory, 'project-graph.lock')) : null; let locked = lock?.locked; while (locked) { logger_1.logger.verbose('Waiting for graph construction in another process to complete'); const spinner = new delayed_spinner_1.DelayedSpinner('Waiting for graph construction in another process to complete'); const start = Date.now(); await lock.wait(); spinner.cleanup(); // Note: This will currently throw if any of the caches are missing... // It would be nice if one of the processes that was waiting for the lock // could pick up the slack and build the graph if it's missing, but // we wouldn't want either of the below to happen: // - All of the waiting processes to build the graph // - Even one of the processes building the graph on a legitimate error try { // Ensuring that computedAt was after this process started // waiting for the graph to complete, means that the graph // was computed by the process was already working. const graph = await readCachedGraphAndHydrateFileMap(start); const sourceMaps = (0, nx_deps_cache_1.readSourceMapsCache)(); if (!sourceMaps) { throw new Error('The project graph was computed in another process, but the source maps are missing.'); } return { projectGraph: graph, sourceMaps, }; } catch (e) { // If the error is that the cached graph is stale after unlock, // the process that was working on the graph must have been canceled, // so we will fall through to the normal flow to ensure // its created by one of the processes that was waiting if (!(e instanceof error_types_1.StaleProjectGraphCacheError)) { throw e; } } locked = lock.check(); } lock?.lock(); try { const res = await buildProjectGraphAndSourceMapsWithoutDaemon(); perf_hooks_1.performance.measure('create-project-graph-async >> retrieve-project-configurations', 'retrieve-project-configurations:start', 'retrieve-project-configurations:end'); perf_hooks_1.performance.measure('create-project-graph-async >> retrieve-workspace-files', 'retrieve-workspace-files:start', 'retrieve-workspace-files:end'); perf_hooks_1.performance.measure('create-project-graph-async >> build-project-graph-using-project-file-map', 'build-project-graph-using-project-file-map:start', 'build-project-graph-using-project-file-map:end'); perf_hooks_1.performance.mark('create-project-graph-async:end'); perf_hooks_1.performance.measure('create-project-graph-async', 'create-project-graph-async:start', 'create-project-graph-async:end'); return res; } catch (e) { handleProjectGraphError(opts, e); } finally { lock?.unlock(); } } else { try { const projectGraphAndSourceMaps = await client_1.daemonClient.getProjectGraphAndSourceMaps(); perf_hooks_1.performance.mark('create-project-graph-async:end'); perf_hooks_1.performance.measure('create-project-graph-async', 'create-project-graph-async:start', 'create-project-graph-async:end'); return projectGraphAndSourceMaps; } catch (e) { if (e.message && e.message.indexOf('inotify_add_watch') > -1) { // common errors with the daemon due to OS settings (cannot watch all the files available) output_1.output.note({ title: `Unable to start Nx Daemon due to the limited amount of inotify watches, continuing without the daemon.`, bodyLines: [ 'For more information read: https://askubuntu.com/questions/1088272/inotify-add-watch-failed-no-space-left-on-device', 'Nx Daemon is going to be disabled until you run "nx reset".', ], }); (0, tmp_dir_1.markDaemonAsDisabled)(); return buildProjectGraphAndSourceMapsWithoutDaemon(); } if (e.internalDaemonError) { const errorLogFile = (0, tmp_dir_1.writeDaemonLogs)(e.message); output_1.output.warn({ title: `Nx Daemon was not able to compute the project graph.`, bodyLines: [ `Log file with the error: ${errorLogFile}`, `Please file an issue at https://github.com/nrwl/nx`, 'Nx Daemon is going to be disabled until you run "nx reset".', ], }); (0, tmp_dir_1.markDaemonAsDisabled)(); return buildProjectGraphAndSourceMapsWithoutDaemon(); } handleProjectGraphError(opts, e); } finally { if (opts.resetDaemonClient) { client_1.daemonClient.reset(); } } } }