diff --git a/Cakefile b/Cakefile index 3a1bebb2da..837f554128 100644 --- a/Cakefile +++ b/Cakefile @@ -11,6 +11,7 @@ helpers = require './lib/coffeescript/helpers' util = require 'util' process = require 'process' +{ BuildDeps } = require './build-support/build-deps' { CompileSources } = require './build-support/compile-sources' { JisonParser } = require './build-support/parser' { setupStyler } = require './build-support/colors' @@ -83,8 +84,10 @@ buildParser = -> # (1) cache parser build # (1.1) cache on grammar.coffee [DONE] # (1.2) cache on jison dep [DONE (kinda--uses package-lock.json)] - # (2) cache file compilation + # (2) cache file compilation [DONE] # (3) make source maps work for errors in the coffeescript compiler! + buildDepsTask = new BuildDeps + await buildDepsTask.cachedExecute console parserTask = new JisonParser await parserTask.cachedExecute console @@ -95,11 +98,9 @@ buildExceptParser = -> coffeeSource = path.join 'src', file jsOut = path.join 'lib/coffeescript', "#{name}.js" new CompileSources {coffeeSource, jsOut} - await Promise.all compileRequests.map (r) -> await r.cachedExecute console + Promise.all compileRequests.map (r) -> r.cachedExecute console -build = -> - await buildParser() - await buildExceptParser() +build = -> Promise.all [buildParser(), buildExceptParser()] transpile = (code, options = {}) -> options.minify = process.env.MINIFY isnt 'false' diff --git a/build-support/caching.coffee b/build-support/caching.coffee index f64e2f0d01..40e3537505 100644 --- a/build-support/caching.coffee +++ b/build-support/caching.coffee @@ -123,6 +123,12 @@ class Attestation @constructor.objectEquals cached, generated +exports.TaskError = class TaskError extends Error + printAndExit: (task, console) -> + console.error "failed: #{task.print()}" + process.exit 1 + + exports.BuildTask = class BuildTask identifier: -> throw new TypeError "unimplemented: #{@constructor.name}" inputSources: -> throw new TypeError "unimplemented: #{@constructor.name}" @@ -157,7 +163,12 @@ exports.BuildTask = class BuildTask console.info "task '#{@identifier()}' was not cached; executing" console.log @print() startTask = performance.now() - await @execute() + + await @execute(console).catch (e) => switch + when e instanceof TaskError + e.printAndExit @, console + else Promise.reject e + endTask = performance.now() console.info "task '#{@identifier()}' complete (#{endTask - startTask} ms)" console.debug "caching task '#{@identifier()}' at '#{@attestationPath()}'" diff --git a/build-support/compile-sources.coffee b/build-support/compile-sources.coffee index 5a04c664a9..90751f4b43 100644 --- a/build-support/compile-sources.coffee +++ b/build-support/compile-sources.coffee @@ -1,7 +1,8 @@ -{ BuildTask, ChecksumFiles } = require './caching' -{ spawnNodeProcess } = require './subprocess' -{ createHash } = require 'crypto' -path = require 'path' +{ BuildTask, ChecksumFiles } = require './caching' +{ invokeProcess, captureErr } = require './subprocess' +{ createHash } = require 'crypto' +path = require 'path' +process = require 'process' exports.CompileSources = class CompileSources extends BuildTask @@ -22,15 +23,17 @@ exports.CompileSources = class CompileSources extends BuildTask {srcName, outName, digest} = @extractNameKeys() "compile-sources-#{srcName}-#{outName}-#{digest}" - constructor: ({@coffeeSource, @jsOut}) -> + constructor: ({@coffeeSource, @jsOut, @coffeeBin = 'bin/coffee'}) -> super() unless @coffeeSource.match /\.(lit)?coffee$/ throw new TypeError "coffee source file must end in .coffee or .litcoffee (was: '#{@coffeeSource}')" unless @jsOut.match /\.js$/ throw new TypeError "js output file must end in .js (was: '#{@jsOut}')" - inputSources: -> new ChecksumFiles [@coffeeSource] + inputSources: -> new ChecksumFiles [@coffeeSource, @coffeeBin] outputSources: -> new ChecksumFiles [@jsOut] print: -> "coffee compile: #{@coffeeSource} -> #{@jsOut}" - execute: -> await spawnNodeProcess ['bin/coffee', '-c', '-o', @jsOut, @coffeeSource] + execute: (console) -> + proc = await invokeProcess process.execPath, [@coffeeBin, '-c', '-o', @jsOut, @coffeeSource] + await captureErr proc diff --git a/build-support/parser.coffee b/build-support/parser.coffee index e0f92603b6..a332871e2d 100644 --- a/build-support/parser.coffee +++ b/build-support/parser.coffee @@ -8,10 +8,12 @@ exports.JisonParser = class JisonParser extends BuildTask @grammarPath = 'lib/coffeescript/grammar.js', @parserPath = 'lib/coffeescript/parser.js', @jisonScript = 'build-support/jison-script.coffee', + @pkgLock = 'node_modules/.package-lock.json', + @coffeeBin = 'bin/coffee', } = {}) -> super() - inputSources: -> new ChecksumFiles [@grammarPath, @jisonScript] + inputSources: -> new ChecksumFiles [@grammarPath, @jisonScript, @pkgLock, @coffeeBin] outputSources: -> new ChecksumFiles [@parserPath] print: -> "jison generate: #{@grammarPath} -> #{@parserPath}" - execute: -> await spawnNodeProcess ['bin/coffee', @jisonScript, @grammarPath, @parserPath] + execute: -> await spawnNodeProcess [@coffeeBin, @jisonScript, @grammarPath, @parserPath] diff --git a/build-support/subprocess.coffee b/build-support/subprocess.coffee index 71ff1fcccb..0c85d40499 100644 --- a/build-support/subprocess.coffee +++ b/build-support/subprocess.coffee @@ -1,14 +1,19 @@ -{ spawn } = require 'child_process' -process = require 'process' +{ spawn } = require 'child_process' +process = require 'process' +{ TaskError } = require './caching' -exports.SubprocessError = class SubprocessError extends Error +exports.SubprocessError = class SubprocessError extends TaskError constructor: (@child, ...rest) -> super ...rest exe: -> @child.spawnfile args: -> @child.spawnargs + printAndExit: (task, console) -> + console.error "process '#{@exe()}' [#{@args().join ', '}] failed: #{@message}" + super task, console + exports.SpawnFailed = class SpawnFailed extends SubprocessError - constructor: (child, cause) -> super child, 'process spawn failed', {cause} + constructor: (child, cause) -> super child, 'process spawn failed: #{cause.message}', {cause} exports.ProcessCompletedError = class ProcessCompletedError extends SubprocessError exports.SignalReceived = class SignalReceived extends ProcessCompletedError @@ -22,6 +27,14 @@ exports.NonZeroExit = class NonZeroExit extends ProcessCompletedError exports.Aborted = class Aborted extends ProcessCompletedError constructor: (child, cause) -> super child, 'process aborted', {cause} +exports.OutputCapturedError = class OutputCapturedError extends ProcessCompletedError + constructor: (@capturedOutput, cause) -> + super cause.child, cause.message, {cause} + + printAndExit: (task, console) -> + process.stderr.write @capturedOutput + super task, console + # Async process spawning. exports.invokeProcess = invokeProcess = (...spawnArgs) -> new Promise (resolve, reject) -> @@ -66,3 +79,15 @@ exports.spawnNodeProcess = (args, {output = 'stderr'} = {}) -> capture proc await collectNone proc + + +exports.captureErr = captureErr = (proc) -> + getErrChunks = do (proc) -> + out = '' + for await chunk from proc.stderr.setEncoding 'utf8' + out += chunk + out + collectNone(proc).catch (e) -> switch + when e instanceof SubprocessError + getErrChunks.then (errChunks) -> Promise.reject new OutputCapturedError errChunks, e + else Promise.reject e