diff --git a/packages/cli/lib/run-cli.ts b/packages/cli/lib/run-cli.ts index 0e16cdcda4..ec258a14f6 100644 --- a/packages/cli/lib/run-cli.ts +++ b/packages/cli/lib/run-cli.ts @@ -4,7 +4,9 @@ import { findWorkspaceRoot } from './find-workspace-root'; const workspace = findWorkspaceRoot(process.cwd()); -setUpOutputWatching(); +if (process.env.NX_TERMINAL_OUTPUT_PATH) { + setUpOutputWatching(); +} requireCli(); function requireCli() { diff --git a/packages/workspace/src/tasks-runner/cache.ts b/packages/workspace/src/tasks-runner/cache.ts index d41bd21de3..edc58fe760 100644 --- a/packages/workspace/src/tasks-runner/cache.ts +++ b/packages/workspace/src/tasks-runner/cache.ts @@ -7,6 +7,7 @@ import { join } from 'path'; import { Hasher } from './hasher'; import * as fsExtra from 'fs-extra'; import { DefaultTasksRunnerOptions } from './tasks-runner-v2'; +import { fork, spawn } from 'child_process'; export type CachedResult = { terminalOutput: string; outputsPath: string }; export type TaskWithCachedResult = { task: Task; cachedResult: CachedResult }; @@ -40,6 +41,35 @@ export class Cache { private readonly options: DefaultTasksRunnerOptions ) {} + removeOldCacheRecords() { + /** + * Even though spawning a process is fast, we don't want to do it every time + * the user runs a command. Instead, we want to do it once in a while. + */ + const shouldSpawnProcess = Math.floor(Math.random() * 50) === 1; + if (shouldSpawnProcess) { + const scriptPath = join( + this.root, + 'node_modules', + '@nrwl', + 'workspace', + 'src', + 'tasks-runner', + 'remove-old-cache-records.js' + ); + try { + const p = spawn('node', [`"${scriptPath}"`, `"${this.cachePath}"`], { + stdio: 'ignore', + detached: true + }); + p.unref(); + } catch (e) { + console.log(`Unable to start remove-old-cache-records script:`); + console.log(e.message); + } + } + } + async get(task: Task): Promise { if (!this.cacheConfig.isCacheableTask(task)) return null; @@ -60,7 +90,6 @@ export class Cache { } async put(task: Task, terminalOutputPath: string, folders: string[]) { - if (!this.cacheConfig.isCacheableTask(task)) return; const terminalOutput = readFileSync(terminalOutputPath).toString(); const hash = await this.hasher.hash(task); const td = join(this.cachePath, hash); @@ -115,7 +144,11 @@ export class Cache { } async temporaryOutputPath(task: Task) { - return join(this.terminalOutputsDir, await this.hasher.hash(task)); + if (this.cacheConfig.isCacheableTask(task)) { + return join(this.terminalOutputsDir, await this.hasher.hash(task)); + } else { + return null; + } } private async getFromLocalDir(task: Task) { diff --git a/packages/workspace/src/tasks-runner/remove-old-cache-records.ts b/packages/workspace/src/tasks-runner/remove-old-cache-records.ts new file mode 100644 index 0000000000..266d8ecb0d --- /dev/null +++ b/packages/workspace/src/tasks-runner/remove-old-cache-records.ts @@ -0,0 +1,61 @@ +import * as fs from 'fs'; +import * as fsExtra from 'fs-extra'; +import * as path from 'path'; + +const WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7; + +const folder = process.argv[2]; + +removeOld(terminalOutputs()); +removeOld(cachedFiles()); + +function terminalOutputs() { + try { + return fs.readdirSync(path.join(folder, 'terminalOutputs')); + } catch (e) { + return []; + } +} + +function cachedFiles() { + try { + return fs.readdirSync(folder).filter(f => !f.endsWith('terminalOutputs')); + } catch (e) { + return []; + } +} + +function removeOld(records: string[]) { + try { + const time = mostRecentMTime(records); + + records.forEach(r => { + const child = path.join(folder, r); + try { + const s = fs.statSync(child); + if (time - s.mtimeMs > WEEK_IN_MS) { + if (s.isDirectory()) { + try { + fsExtra.removeSync(`${child}.commit`); + } catch (e) {} + } + fsExtra.removeSync(child); + } + } catch (e) {} + }); + } catch (e) {} +} + +function mostRecentMTime(records: string[]) { + let mostRecentTime = 0; + records.forEach(r => { + const child = path.join(folder, r); + try { + const s = fs.statSync(child); + if (s.mtimeMs > mostRecentTime) { + mostRecentTime = s.mtimeMs; + } + } catch (e) {} + }); + return mostRecentTime; +} diff --git a/packages/workspace/src/tasks-runner/task-orchestrator.ts b/packages/workspace/src/tasks-runner/task-orchestrator.ts index 0d200242ff..c9c463609d 100644 --- a/packages/workspace/src/tasks-runner/task-orchestrator.ts +++ b/packages/workspace/src/tasks-runner/task-orchestrator.ts @@ -28,6 +28,7 @@ export class TaskOrchestrator { const r1 = await this.applyCachedResults(cached); const r2 = await this.runRest(rest); + this.cache.removeOldCacheRecords(); return [...r1, ...r2]; } @@ -109,12 +110,16 @@ export class TaskOrchestrator { return this.cache.temporaryOutputPath(task).then(outputPath => { return new Promise((res, rej) => { try { + const env = { ...process.env }; + if (outputPath) { + env.NX_TERMINAL_OUTPUT_PATH = outputPath; + } const p = fork(this.getCommand(), this.getCommandArgs(task), { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env: { ...process.env, NX_TERMINAL_OUTPUT_PATH: outputPath } + env }); p.on('close', code => { - if (code === 0) { + if (outputPath && code === 0) { this.cache.put(task, outputPath, taskOutputs).then(() => { res(code); });