/** * Instructions for updating this file. * - this file must be updated everytime a new cross-folder dependency is added * into any of the [refs.ts] file. For instance, if suddenly you add a * reference to [../storage/whatever.ts] in [build/libwab.ts], then you must * update the dependencies of the [build/libwab.d.ts] task. **/ var assert = require('assert'); var child_process = require("child_process"); var fs = require("fs"); var path = require("path"); var source_map = require("source-map"); var events = require("events"); events.EventEmitter.defaultMaxListeners = 32; var head; function getGitHead() { if (!head) head = child_process.execSync("git rev-parse HEAD"); return head; } jake.addListener("start", function () { if (!fs.existsSync("build")) fs.mkdirSync("build"); }); var branchingFactor = 32; // The list of files generated by the build. var generated = [ 'shell/npm/package.json', 'shell/npm/bin/touchdevelop.js', 'results.html', 'results.json', ]; // A list of targets we compile with the --noImplicitAny flag. var noImplicitAny = { "build/browser.d.ts": null, "build/blockly-main.js": null }; // On Windows, merely changing a file in the directory does *not* change that // directory's mtime, meaning that we can't depend on a directory, but rather // need to depend on all the files in a directory. [expand] takes a list of // dependencies, and for filenames that seem to be directories, performs a // manual expansion. function expand(dependencies) { var isFolder = function (f) { return f.indexOf(".") < 0 && f.indexOf("/") < 0; }; var acc = []; dependencies.forEach(function (f) { if (isFolder(f)) { var l = new jake.FileList(); l.include(f+"/*"); acc = acc.concat(l.toArray()); } else { acc.push(f); } }); return acc; } // for use with child_process.exec/execFile function execCallback(task) { return function (error, stdout, stderr) { if (stdout) console.log(stdout.toString()); if (stderr) console.error(stderr.toString()); if (error) task.fail(error); else task.complete(); } } // This function tries to be "smart" about the target. // - if the target is of the form "foobar/refs.ts", the output is bundled with // [--out] into "build/foobar.js", and "build/foobar.d.ts" is also generated; // - otherwise, the file is just compiled as a single file into the "build/" // directory function mkSimpleTask(production, dependencies, target) { var sourceRoot = (process.env.TRAVIS ? "https://github.com/Microsoft/TouchDevelop/raw/"+getGitHead()+"/" : "/editor/local/"); var tscCall = [ "node node_modules/typescript/bin/tsc", "--noEmitOnError", "--removeComments", "--target ES5", "--module commonjs", "--declaration", // not always strictly needed, but better be safe than sorry ]; if (process.env.TD_SOURCE_MAPS) { tscCall.push("--sourceMap"); tscCall.push("--sourceRoot "+sourceRoot); } if (production in noImplicitAny) tscCall.push("--noImplicitAny"); var match = target.match(/(\w+)\/refs.ts/); if (match) { tscCall.push("--out "+production); } else { tscCall.push("--outDir build/"); } tscCall.push(target); // We always generate definition files file(production.replace(".js", ".d.ts"), [ production ]); return file(production, expand(dependencies), { async: true, parallelLimit: branchingFactor }, function () { var task = this; console.log("[B] "+production); jake.exec(tscCall.join(" "), { printStdout: true }, function () { task.complete(); }); }); } // A series of compile-and-run rules that generate various files for the build // system. function runAndComplete(cmds, task) { jake.exec(cmds, {}, function() { task.complete(); }); } mkSimpleTask('build/genmeta.js', [ 'tools', 'generated/help.cache' ], "tools/genmeta.ts"); file('build/api.js', expand([ "build/genmeta.js", "lib" ]), { async: true }, function () { console.log("[P] generating build/api.js, localization.json and topiclist.json"); runAndComplete([ "node build/genmeta.js", ], this); }); mkSimpleTask('build/addCssPrefixes.js', [ 'tools' ], "tools/addCssPrefixes.ts"); task('css-prefixes', [ "build/addCssPrefixes.js" ], { async: true }, function () { console.log("[P] modifying in-place all css files"); runAndComplete([ "node build/addCssPrefixes.js" ], this); }); mkSimpleTask('build/shell.js', [ 'shell/shell.ts' ], "shell/shell.ts"); mkSimpleTask('build/package.js', [ 'shell/package.ts', 'build/shell.js' ], "shell/package.ts"); file('build/pkgshell.js', [ 'build/package.js' ], { async: true }, function () { console.log("[P] generating build/pkgshell.js and packaging"); runAndComplete([ "node build/package.js" ], this); }); // These dependencies have been hand-crafted by reading the various [refs.ts] // files. The dependencies inside the same folder are coarse-grained: for // instance, anytime something changes in [editor/], [build/editor.d.ts] gets // rebuilt. This amounts to assuming that for all [foo/bar.ts], [bar.ts] is a // dependency of [build/foo.js]. mkSimpleTask('build/browser.js', [ 'browser/browser.ts' ], "browser/browser.ts"); mkSimpleTask('build/rt.js', [ 'build/browser.d.ts', 'rt', 'lib' ], "rt/refs.ts"); mkSimpleTask('build/storage.js', [ 'build/rt.d.ts', 'rt/typings.d.ts', 'build/browser.d.ts', 'storage' ], "storage/refs.ts"); mkSimpleTask('build/embedded.js', [ 'build/ast.d.ts', 'embedded' ], "embedded/refs.ts"); mkSimpleTask('build/ast.js', [ 'build/rt.d.ts', 'ast' ], "ast/refs.ts"); mkSimpleTask('build/libwab.js', [ 'build/rt.d.ts', 'rt/typings.d.ts', 'build/browser.d.ts', 'libwab' ], "libwab/refs.ts"); mkSimpleTask('build/libnode.js', [ 'build/rt.d.ts', 'rt/typings.d.ts', 'libnode' ], "libnode/refs.ts"); mkSimpleTask('build/libcordova.js', [ 'build/rt.d.ts', 'rt/typings.d.ts', 'build/browser.d.ts', 'libcordova' ], "libcordova/refs.ts"); mkSimpleTask('build/editor.js', [ 'rt/typings.d.ts', 'build/browser.d.ts', 'build/rt.d.ts', 'build/ast.d.ts', 'build/storage.d.ts', 'build/libwab.d.ts', 'build/libcordova.d.ts', 'build/embedded.d.ts', 'intellitrain', 'editor' ], "editor/refs.ts"); mkSimpleTask('build/officemix.js', [ 'officemix' ], "officemix/officemix.ts"); mkSimpleTask('build/jsonapi.js', [], "noderunner/jsonapi.ts"); mkSimpleTask('build/client.js', [ 'rt/typings.d.ts', 'build/jsonapi.d.ts', 'tools/client.ts' ], "tools/client.ts"); // XXX coarse-grained dependencies here over the whole 'noderunner' directory mkSimpleTask('build/nrunner.js', [ 'build/browser.d.ts', 'rt/typings.d.ts', 'build/rt.d.ts', 'build/ast.d.ts', 'build/libnode.d.ts', 'noderunner' ], "noderunner/nrunner.ts"); // XXX same here mkSimpleTask('build/runner.js', [ 'build/browser.d.ts', 'rt/typings.d.ts', 'build/rt.d.ts', 'build/storage.d.ts', 'build/libwab.d.ts', 'build/libnode.d.ts', 'build/libcordova.d.ts', 'runner' ], "runner/refs.ts"); mkSimpleTask('build/ace-main.js', [ "www/ace/ace-main.ts", "editor/messages.ts" ], "www/ace/refs.ts"); mkSimpleTask('build/blockly-main.js', [ "www/blockly/blockly-main.ts", "www/blockly/compiler.ts", "editor/messages.ts" ], "www/blockly/refs.ts"); // Now come the rules for files that are obtained by concatenating multiple // _js_ files into another one. The sequence exactly reproduces what happened // previously, as there are ordering issues with initialization of global variables // (unsurprisingly). Here's the semantics of these entries: // - files ending with ".js" end up as dependencies (either as rule names, or as // statically-checked-in files in the repo, such as [langs.js]), and are // concatenated in the final file // - files without an extension generate a dependency on the ".d.ts" rule and // the ".js" compiled file ends up in the concatenation var concatMap = { "build/noderunner.js": [ "tools/node_prelude.js", "build/browser", "build/rt", "build/ast", "build/api.js", "generated/langs.js", "build/libnode", "build/pkgshell.js", "build/nrunner.js", ], "build/noderuntime.js": [ "tools/node_prelude.js", "build/browser", "build/rt", "build/storage", "build/libnode", "build/runner", ], "build/runtime.js": [ "build/rt", "build/storage", "build/libwab", "build/libnode", "build/libcordova", "build/runner", ], "build/main.js": [ "build/rt", "build/ast", "build/api.js", "generated/langs.js", "build/storage", "build/libwab", "build/libcordova", "build/pkgshell.js", "build/embedded", "build/editor" , ], }; // Just a dumb concatenation function justCat(files, dest) { console.log("[C]", dest); var bufs = []; files.forEach(function (f) { bufs.push(fs.readFileSync(f)); }); fs.writeFileSync(dest, Buffer.concat(bufs)); } // A concatenation that recomputes proper maps. Generates the source map in the // same directory as the original map, and expects it to remain that way. function mapCat(files, dest) { console.log("[C]", dest, "with maps"); // An array of buffers for all the files we want to concatenate. var bufs = []; // Current line offest. var lineOffset = 0; // The source map we're generating. var map = new source_map.SourceMapGenerator({ file: dest + ".map" }); files.forEach(function (f) { var buf = fs.readFileSync(f); bufs.push(buf); if (fs.existsSync(f + ".map")) { var originalMap = new source_map.SourceMapConsumer(fs.readFileSync(f + ".map", { encoding: "utf-8" })); originalMap.eachMapping(function (m) { map.addMapping({ generated: { line: m.generatedLine + lineOffset, column: m.generatedColumn, }, original: { line: m.originalLine, column: m.originalColumn, }, source: m.source, name: m.name }); }); // An extra line was added for the sourcemap comment already. lineOffset--; } lineOffset += buf.toString().split("\n").length; if (buf[buf.length - 1] == "\n".charCodeAt(0)) lineOffset--; }); bufs.push(new Buffer("\n//# sourceMappingURL="+path.basename(dest)+".map")); fs.writeFileSync(dest, Buffer.concat(bufs)); fs.writeFileSync(dest+".map", map.toString()); } Object.keys(concatMap).forEach(function (f) { var isJs = function (s) { return s.substr(s.length - 3, 3) == ".js"; }; var buildDeps = concatMap[f].map(function (x) { if (isJs(x)) return x; else return x + ".js"; }); var toConcat = buildDeps; file(f, buildDeps, { parallelLimit: branchingFactor }, function () { if (f == "build/main.js" && process.env.TD_SOURCE_MAPS) mapCat(toConcat, f); else justCat(toConcat, f); }); }); task('log', [], { async: false }, function () { if (process.env.TRAVIS_COMMIT) { console.log("[I] dumping commit info to build/buildinfo.json"); fs.writeFileSync('build/buildinfo.json', JSON.stringify({ commit: process.env.TRAVIS_COMMIT, commitRange: process.env.TRAVIS_COMMIT_RANGE })); } }); // Our targets are the concatenated files, which are the final result of the // compilation. We also re-run the CSS prefixes thingy everytime. desc('build the whole TouchDevelop project') task('default', [ 'css-prefixes', 'build/client.js', 'build/officemix.d.ts', 'build/ace-main.js', 'build/blockly-main.js', 'log' ].concat(Object.keys(concatMap)), { parallelLimit: branchingFactor, },function () { console.log("[I] build completed."); }); desc('clean up the build folders and files') task('clean', [], function () { // XXX do this in a single call? check out https://github.com/mde/utilities/blob/master/lib/file.js generated.forEach(function (f) { jake.rmRf(f); }); jake.rmRf('build/'); }); desc('display info about installed tools') task('info', [], { async: true }, function () { var task = this; if (process.env.TRAVIS) { assert(process.env.TD_UPLOAD_KEY, "missing touchdevelop upload key TD_UPLOAD_KEY"); } jake.exec([ 'tsc --version' ], { printStdout: true, printStderr: true }, function() { task.complete(); }); }); desc('run local test suite') task('test', [ 'info', 'build/client.js', 'default' ], { async: true }, function () { var task = this; console.log("[I] running tests") jake.exec([ 'node build/client.js buildtest' ], { printStdout: true, printStderr: true }, function() { task.complete(); }); }); // this task runs as a "after_success" step in the travis-ci automation desc('upload current build to the cloud') task('upload', [ "build/client.js" ], { async : true }, function() { var task = this; var upload = function (buildVersion) { console.log("[I] uploading v" + buildVersion) var procs = []; if (process.env.TD_UPLOAD_LITE_KEY) { console.log("[I] uploading to lite") procs.push('node build/client.js tdupload ' + process.env.TD_UPLOAD_LITE_KEY + ' ' + buildVersion + ' latest'); } var uploadKey = process.env.TD_UPLOAD_KEY || "direct"; procs.push('node build/client.js tdupload ' + uploadKey + ' ' + buildVersion); jake.exec(procs, { printStdout: true, printStderr: true }, function() { task.complete(); }); }; if (!process.env.TRAVIS) { upload(process.env.TD_UPLOAD_USER || process.env.USERNAME); } else { assert(process.env.TRAVIS_BUILD_NUMBER, "missing travis build number"); assert(process.env.TD_UPLOAD_KEY, "missing touchdevelop upload key TD_UPLOAD_KEY"); var buildVersion = 80100 + parseInt(process.env.TRAVIS_BUILD_NUMBER || - 80000); if (process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST != "false") { // don't upload } else { if (process.env.TRAVIS_BRANCH != "master") buildVersion = process.env.TRAVIS_BRANCH + buildVersion upload(buildVersion); } } }) desc('build portal html pages') task('portal', ['build/portal/portal.html']); desc('build and launch local server') task('local', [ 'default' ], { async: true }, function() { var task = this; jake.mkdirP('build/local/node_modules') jake.cpR('build/shell.js', 'build/local/tdserver.js') process.chdir("build/local") var node = "node" if (process.env.TD_SHELL_DEBUG) node = "node-debug" jake.exec( [ node + ' tdserver.js --cli TD_ALLOW_EDITOR=true TD_LOCAL_EDITOR_PATH=../.. ' + (process.env.TD_SHELL_ARGS || "") ], { printStdout: true, printStderr: true }, function() { task.complete(); } ); }); task('nw', ['default', 'nw-npm'], { async: true }, function() { runAndComplete([ 'node node_modules/nw/bin/nw build/nw' ], this); }) task('update-docs', [ 'build/client.js', 'default' ], { async: true }, function() { var task = this; jake.exec( [ 'node build/client.js updatehelp', 'node build/client.js updatelang' ], { printStdout: true, printStderr: true }, function() { task.complete(); } ); }); task('azure', [ 'build/shell.js' ], { async: true }, function() { jake.mkdirP("build/azure") child_process.execFile( "C:/Program Files/Microsoft SDKs/Azure/.NET SDK/v2.5/bin/cspack.exe", [ "tdshell.csdef", "/out:../../build/azure/tdshell.cspkg", "/roleFiles:ShellRole;files.txt" ], { cwd: 'shell/azure' }, execCallback(this)) }); task('nw-npm', {async : true }, function() { var task = this; jake.mkdirP('build/nw'); ['node-webkit/app.html', 'node-webkit/logo.png', 'node-webkit/client.ico', 'node-webkit/package.json', 'build/shell.js'].forEach(function(f) { jake.cpR(f, 'build/nw') }) child_process.exec('npm install', { cwd: 'build/nw' }, execCallback(task)) }) task('nw-build', [ 'default', 'nw-npm' ], { async : true }, function() { var task = this; console.log('[I] building nw packages') var nwBuilder = require('node-webkit-builder'); var nw = new nwBuilder({ version: '0.12.0', files: 'build/nw/**', platforms: ['win32', 'osx32', 'linux32'], buildDir: 'build/nw_build', cacheDir: 'nw_cache' }); nw.on('log', console.log); nw.build().then(function () { console.log('[I] nw packaged'); task.complete(); }).catch(function (error) { console.error(error); task.fail(); }); }); task('cordova', [ 'default' ], {}, function () { // FIXME minify jake.mkdirP('build/cordova/'); ['www/index.html', 'build/browser.js', 'build/main.js', 'www/default.css', 'www/editor.css'].forEach(function (f) { jake.cpR(f, 'build/cordova/'); }); }); // vim: ft=javascript