@@ -10,14 +10,16 @@ import {
10
10
JsonObject ,
11
11
NewlineKind ,
12
12
InternalError ,
13
- Terminal
13
+ Terminal ,
14
+ ColorValue
14
15
} from '@rushstack/node-core-library' ;
15
16
import {
16
17
TerminalChunkKind ,
17
18
TextRewriterTransform ,
18
19
StderrLineTransform ,
19
20
SplitterTransform ,
20
- DiscardStdoutTransform
21
+ DiscardStdoutTransform ,
22
+ PrintUtilities
21
23
} from '@rushstack/terminal' ;
22
24
import { CollatedTerminal } from '@rushstack/stream-collator' ;
23
25
@@ -225,138 +227,155 @@ export class ProjectBuilder extends BaseBuilder {
225
227
files,
226
228
arguments : this . _commandToRun
227
229
} ;
228
- } else {
229
- terminal . writeLine (
230
- 'Unable to calculate incremental build state. Instead running full rebuild. Ensure Git is present.'
231
- ) ;
230
+ } else if ( this . isIncrementalBuildAllowed ) {
231
+ // To test this code path:
232
+ // Remove the `.git` folder then run "rush build --verbose"
233
+ terminal . writeLine ( {
234
+ text : PrintUtilities . wrapWords (
235
+ 'This workspace does not appear to be tracked by Git. ' +
236
+ 'Rush will proceed without incremental build, caching, and change detection.'
237
+ ) ,
238
+ foregroundColor : ColorValue . Cyan
239
+ } ) ;
232
240
}
233
241
} catch ( error ) {
234
- terminal . writeLine (
235
- 'Error calculating incremental build state. Instead running full rebuild. ' + error . toString ( )
236
- ) ;
242
+ // To test this code path:
243
+ // Delete a project's ".rush/temp/shrinkwrap-deps.json" then run "rush build --verbose"
244
+ terminal . writeLine ( 'Unable to calculate incremental build state: ' + error . toString ( ) ) ;
245
+ terminal . writeLine ( {
246
+ text : 'Rush will proceed without incremental build, caching, and change detection.' ,
247
+ foregroundColor : ColorValue . Cyan
248
+ } ) ;
237
249
}
238
250
239
- const isPackageUnchanged : boolean = ! ! (
240
- lastProjectBuildDeps &&
241
- projectBuildDeps &&
242
- projectBuildDeps . arguments === lastProjectBuildDeps . arguments &&
243
- _areShallowEqual ( projectBuildDeps . files , lastProjectBuildDeps . files )
244
- ) ;
251
+ // If the current command is allowed to do incremental builds, attempt to retrieve
252
+ // the project from the build cache or skip building, if appropriate.
253
+ if ( this . isIncrementalBuildAllowed ) {
254
+ const projectBuildCache : ProjectBuildCache | undefined = await this . _getProjectBuildCacheAsync (
255
+ terminal ,
256
+ trackedFiles ,
257
+ context . repoCommandLineConfiguration
258
+ ) ;
259
+ const restoreFromCacheSuccess : boolean | undefined =
260
+ await projectBuildCache ?. tryRestoreFromCacheAsync ( terminal ) ;
245
261
246
- const projectBuildCache : ProjectBuildCache | undefined = await this . _getProjectBuildCacheAsync (
247
- terminal ,
248
- trackedFiles ,
249
- context . repoCommandLineConfiguration
250
- ) ;
251
- const restoreFromCacheSuccess : boolean | undefined = await projectBuildCache ?. tryRestoreFromCacheAsync (
252
- terminal
253
- ) ;
262
+ if ( restoreFromCacheSuccess ) {
263
+ return TaskStatus . FromCache ;
264
+ }
254
265
255
- if ( restoreFromCacheSuccess ) {
256
- return TaskStatus . FromCache ;
257
- } else if ( isPackageUnchanged && this . isIncrementalBuildAllowed ) {
258
- return TaskStatus . Skipped ;
259
- } else {
260
- // If the deps file exists, remove it before starting a build.
261
- FileSystem . deleteFile ( currentDepsPath ) ;
262
-
263
- // TODO: Remove legacyDepsPath with the next major release of Rush
264
- const legacyDepsPath : string = path . join ( this . _rushProject . projectFolder , 'package-deps.json' ) ;
265
- // Delete the legacy package-deps.json
266
- FileSystem . deleteFile ( legacyDepsPath ) ;
267
-
268
- if ( ! this . _commandToRun ) {
269
- // Write deps on success.
270
- if ( projectBuildDeps ) {
271
- JsonFile . save ( projectBuildDeps , currentDepsPath , {
272
- ensureFolderExists : true
273
- } ) ;
274
- }
266
+ const isPackageUnchanged : boolean = ! ! (
267
+ lastProjectBuildDeps &&
268
+ projectBuildDeps &&
269
+ projectBuildDeps . arguments === lastProjectBuildDeps . arguments &&
270
+ _areShallowEqual ( projectBuildDeps . files , lastProjectBuildDeps . files )
271
+ ) ;
275
272
276
- return TaskStatus . Success ;
273
+ if ( isPackageUnchanged ) {
274
+ return TaskStatus . Skipped ;
277
275
}
276
+ }
278
277
279
- // Run the task
280
- terminal . writeLine ( 'Invoking: ' + this . _commandToRun ) ;
278
+ // If the deps file exists, remove it before starting a build.
279
+ FileSystem . deleteFile ( currentDepsPath ) ;
281
280
282
- const task : child_process . ChildProcess = Utilities . executeLifecycleCommandAsync ( this . _commandToRun , {
283
- rushConfiguration : this . _rushConfiguration ,
284
- workingDirectory : projectFolder ,
285
- initCwd : this . _rushConfiguration . commonTempFolder ,
286
- handleOutput : true ,
287
- environmentPathOptions : {
288
- includeProjectBin : true
289
- }
290
- } ) ;
281
+ // TODO: Remove legacyDepsPath with the next major release of Rush
282
+ const legacyDepsPath : string = path . join ( this . _rushProject . projectFolder , 'package-deps.json' ) ;
283
+ // Delete the legacy package-deps.json
284
+ FileSystem . deleteFile ( legacyDepsPath ) ;
291
285
292
- // Hook into events, in order to get live streaming of build log
293
- if ( task . stdout !== null ) {
294
- task . stdout . on ( 'data' , ( data : Buffer ) => {
295
- const text : string = data . toString ( ) ;
296
- collatedTerminal . writeChunk ( { text , kind : TerminalChunkKind . Stdout } ) ;
286
+ if ( ! this . _commandToRun ) {
287
+ // Write deps on success.
288
+ if ( projectBuildDeps ) {
289
+ JsonFile . save ( projectBuildDeps , currentDepsPath , {
290
+ ensureFolderExists : true
297
291
} ) ;
298
292
}
299
- if ( task . stderr !== null ) {
300
- task . stderr . on ( 'data' , ( data : Buffer ) => {
301
- const text : string = data . toString ( ) ;
302
- collatedTerminal . writeChunk ( { text, kind : TerminalChunkKind . Stderr } ) ;
303
- hasWarningOrError = true ;
304
- } ) ;
293
+
294
+ return TaskStatus . Success ;
295
+ }
296
+
297
+ // Run the task
298
+ terminal . writeLine ( 'Invoking: ' + this . _commandToRun ) ;
299
+
300
+ const task : child_process . ChildProcess = Utilities . executeLifecycleCommandAsync ( this . _commandToRun , {
301
+ rushConfiguration : this . _rushConfiguration ,
302
+ workingDirectory : projectFolder ,
303
+ initCwd : this . _rushConfiguration . commonTempFolder ,
304
+ handleOutput : true ,
305
+ environmentPathOptions : {
306
+ includeProjectBin : true
305
307
}
308
+ } ) ;
309
+
310
+ // Hook into events, in order to get live streaming of build log
311
+ if ( task . stdout !== null ) {
312
+ task . stdout . on ( 'data' , ( data : Buffer ) => {
313
+ const text : string = data . toString ( ) ;
314
+ collatedTerminal . writeChunk ( { text, kind : TerminalChunkKind . Stdout } ) ;
315
+ } ) ;
316
+ }
317
+ if ( task . stderr !== null ) {
318
+ task . stderr . on ( 'data' , ( data : Buffer ) => {
319
+ const text : string = data . toString ( ) ;
320
+ collatedTerminal . writeChunk ( { text, kind : TerminalChunkKind . Stderr } ) ;
321
+ hasWarningOrError = true ;
322
+ } ) ;
323
+ }
306
324
307
- let status : TaskStatus = await new Promise (
308
- ( resolve : ( status : TaskStatus ) => void , reject : ( error : TaskError ) => void ) => {
309
- task . on ( 'close' , ( code : number ) => {
310
- try {
311
- if ( code !== 0 ) {
312
- reject ( new TaskError ( 'error' , `Returned error code: ${ code } ` ) ) ;
313
- } else if ( hasWarningOrError ) {
314
- resolve ( TaskStatus . SuccessWithWarning ) ;
315
- } else {
316
- resolve ( TaskStatus . Success ) ;
317
- }
318
- } catch ( error ) {
319
- reject ( error ) ;
325
+ let status : TaskStatus = await new Promise (
326
+ ( resolve : ( status : TaskStatus ) => void , reject : ( error : TaskError ) => void ) => {
327
+ task . on ( 'close' , ( code : number ) => {
328
+ try {
329
+ if ( code !== 0 ) {
330
+ reject ( new TaskError ( 'error' , `Returned error code: ${ code } ` ) ) ;
331
+ } else if ( hasWarningOrError ) {
332
+ resolve ( TaskStatus . SuccessWithWarning ) ;
333
+ } else {
334
+ resolve ( TaskStatus . Success ) ;
320
335
}
321
- } ) ;
336
+ } catch ( error ) {
337
+ reject ( error ) ;
338
+ }
339
+ } ) ;
340
+ }
341
+ ) ;
342
+
343
+ if ( status === TaskStatus . Success && projectBuildDeps ) {
344
+ // Write deps on success.
345
+ const writeProjectStatePromise : Promise < boolean > = JsonFile . saveAsync (
346
+ projectBuildDeps ,
347
+ currentDepsPath ,
348
+ {
349
+ ensureFolderExists : true
322
350
}
323
351
) ;
324
352
325
- if ( status === TaskStatus . Success && projectBuildDeps ) {
326
- // Write deps on success.
327
- const writeProjectStatePromise : Promise < boolean > = JsonFile . saveAsync (
328
- projectBuildDeps ,
329
- currentDepsPath ,
330
- {
331
- ensureFolderExists : true
332
- }
333
- ) ;
334
-
335
- const setCacheEntryPromise : Promise < boolean | undefined > = this . tryWriteCacheEntryAsync (
336
- terminal ,
337
- trackedFiles ,
338
- context . repoCommandLineConfiguration
339
- ) ;
353
+ // If the command is successful and we can calculate project hash, we will write a
354
+ // new cache entry even if incremental builds are not allowed.
355
+ const setCacheEntryPromise : Promise < boolean | undefined > = this . tryWriteCacheEntryAsync (
356
+ terminal ,
357
+ trackedFiles ,
358
+ context . repoCommandLineConfiguration
359
+ ) ;
340
360
341
- const [ , cacheWriteSuccess ] = await Promise . all ( [ writeProjectStatePromise , setCacheEntryPromise ] ) ;
361
+ const [ , cacheWriteSuccess ] = await Promise . all ( [ writeProjectStatePromise , setCacheEntryPromise ] ) ;
342
362
343
- if ( terminalProvider . hasErrors ) {
344
- status = TaskStatus . Failure ;
345
- } else if ( cacheWriteSuccess === false ) {
346
- status = TaskStatus . SuccessWithWarning ;
347
- }
363
+ if ( terminalProvider . hasErrors ) {
364
+ status = TaskStatus . Failure ;
365
+ } else if ( cacheWriteSuccess === false ) {
366
+ status = TaskStatus . SuccessWithWarning ;
348
367
}
368
+ }
349
369
350
- normalizeNewlineTransform . close ( ) ;
370
+ normalizeNewlineTransform . close ( ) ;
351
371
352
- // If the pipeline is wired up correctly, then closing normalizeNewlineTransform should
353
- // have closed projectLogWritable.
354
- if ( projectLogWritable . isOpen ) {
355
- throw new InternalError ( 'The output file handle was not closed' ) ;
356
- }
357
-
358
- return status ;
372
+ // If the pipeline is wired up correctly, then closing normalizeNewlineTransform should
373
+ // have closed projectLogWritable.
374
+ if ( projectLogWritable . isOpen ) {
375
+ throw new InternalError ( 'The output file handle was not closed' ) ;
359
376
}
377
+
378
+ return status ;
360
379
} finally {
361
380
projectLogWritable . close ( ) ;
362
381
}
0 commit comments