#!/usr/bin/env node /* * Builds with webpack and generates a Makefile include that * lists all dependencies, inputs, outputs, and installable files */ function fatal(message, code) { console.log("webpack-make: " + message); process.exit(code || 1); } var webpack, path, stdio, fs; try { webpack = require("webpack"); path = require("path"); stdio = require("stdio"); fs = require("fs"); } catch(ex) { fatal(ex.message, 127); /* missing looks for this */ } var ops = stdio.getopt({ deps: { key: "d", args: 1, description: "Output dependencies in Makefile format" } }); if (!ops.args || ops.args.length < 1 || ops.args.length > 2) { console.log("usage: webpack-make config [section]"); process.exit(2); } var srcdir = process.env.SRCDIR; var makefile = ops.deps; var prefix = "packages"; var npm = { "dependencies": { } }; if (makefile) { prefix = makefile.split("/").slice(-2, -1)[0]; process.env["ONLYDIR"] = prefix + "/"; npm = JSON.parse(fs.readFileSync(path.join(srcdir, "package.json"), "utf8")); } var cwd = process.cwd(); var config_path = path.resolve(cwd, ops.args[0]); var config = require(config_path); // An alternate jshint reporter when used with make config.jshint.reporter = function(errors) { var loader = this; errors.forEach(function(err) { console.log(loader.resource + ":" + err.line + ":" + err.character + ": " + err.reason); }); process.exit(1); }; // The latest input file time updated and used below var latest = fs.statSync(config_path).mtime; webpack(config, function(err, stats) { // process.stdout.write(stats.toString({colors: true}) + "\n"); if (err) { console.log(JSON.stringify(err)); process.exit(1); return; } // Failure exit code when compilation fails if (stats.hasErrors() || stats.hasWarnings()) { console.log(stats.toString("normal")); process.exit(1); return; } if (makefile) generateDeps(makefile, stats); }); function generateDeps(makefile, stats) { // Note that these are cheap ways of doing a set var inputs = { }; var po_locations = { }; var outputs = { }; var installs = { }; var tests = { }; var debugs = { }; var pkgdir = path.dirname(makefile); stats.compilation.modules.forEach(function(module) { var parts = module.identifier().split("!"); parts.concat(module.fileDependencies || []).forEach(function(part) { var input = part.split("?")[0]; maybePushInput(inputs, po_locations, input); /* We distribute licenses, so treat them as inputs */ moduleLicenses(input).forEach(function(input) { maybePushInput(inputs, { }, input); }); }); }); stats.compilation.fileDependencies.forEach(function(file) { maybePushInput(inputs, po_locations, file); }); // All the dependent files var asset, output; var now = Math.floor(Date.now() / 1000); for(asset in stats.compilation.assets) { output = path.join(stats.compilation.outputOptions.path, asset); fs.utimesSync(output, now, now); /* * The manifest.json files are installed and built by Makefile.am. * When webpack is used on its own it *should* copy these files * (albeit poorly without substitution) ... but now that make is * in play lets have them generated by make */ if (endsWith(output, "manifest.json")) fs.unlinkSync(output); else outputs[output] = output; if (output.indexOf("/test-") !== -1 && endsWith(output, ".html")) { tests[output] = output; continue; } var install = output; if (!endsWith(output, "manifest.json") && !endsWith(output, "override.json") && !endsWith(output, "shell/index.html") && !endsWith(output, "shell/stub.html") && !endsWith(output, "shell/simple.html") && !endsWith(output, ".png") && !endsWith(output, ".map") && !endsWith(output, ".ttf") && !endsWith(output, ".woff") && !endsWith(output, ".gif")) { install += ".gz"; } // Debug output and tests gets installed separately if (endsWith(install, ".map")) debugs[install] = install; else if (output.indexOf("/test-") === -1) installs[install] = install; } // Finalize all the sets into arrays inputs = Object.keys(inputs).sort(); po_locations = Object.keys(po_locations).sort(); outputs = Object.keys(outputs).sort(); installs = Object.keys(installs).sort(); tests = Object.keys(tests).sort(); debugs = Object.keys(debugs).sort(); var lines = [ "# Generated Makefile data for " + prefix, "# Stamp: " + latest, "" ]; function makeArray(name, values) { lines.push(name + " = \\"); values.forEach(function(value) { lines.push("\t" + value + " \\"); }); lines.push("\t$(NULL)"); lines.push(""); } makeArray(prefix + "_INPUTS", inputs); makeArray(prefix + "_OUTPUTS", outputs); lines.push(prefix + "_PO = " + path.join(pkgdir, "po.js") + " $(patsubst %," + path.join(pkgdir, "po.%.js") + ",$(LINGUAS))"); lines.push(""); installs.push("$(addsuffix .gz,$(" + prefix + "_PO))"); makeArray(prefix + "_INSTALL", installs); makeArray(prefix + "_DEBUG", debugs); makeArray(prefix + "_TESTS", tests); var filters = po_locations.map(function (l) { return "-N " + l; }); if (prefix === "shell") filters.push("$(addprefix -N ,$(shell find pkg/ -name manifest.json))"); lines.push(path.join(pkgdir, "%.po") + ": po/%.po"); lines.push("\t$(AM_V_GEN) $(MKDIR_P) $(dir $@) && \\"); lines.push("\t$(MSGGREP) " + filters.join(" ") + " $< > $@.tmp && mv $@.tmp $@"); lines.push(""); lines.push(makefile + ": $(WEBPACK_CONFIG) $(" + prefix + "_INPUTS)"); lines.push("\t$(WEBPACK_RULE) -d " + makefile + " $(WEBPACK_CONFIG)"); lines.push(""); outputs.forEach(function(name) { lines.push(name + ": " + makefile); lines.push("") }); inputs.forEach(function(name) { lines.push(name + ":"); lines.push("") }); lines.push("WEBPACK_INPUTS += $(" + prefix + "_INPUTS)"); lines.push("WEBPACK_OUTPUTS += $(" + prefix + "_OUTPUTS)"); lines.push("WEBPACK_PO += $(" + prefix + "_PO)"); lines.push("WEBPACK_INSTALL += $(" + prefix + "_INSTALL)"); lines.push("WEBPACK_DEBUG += $(" + prefix + "_DEBUG)"); lines.push("TESTS += $(" + prefix + "_TESTS)"); lines.push(""); lines.push(prefix + ": " + makefile + " $(" + prefix + "_OUTPUTS) $(" + prefix + "_INSTALL)"); lines.push("\t@true"); lines.push("clean-" + prefix + ": "); lines.push("\trm -rf $(" + prefix + "_OUTPUTS) $(" + prefix + "_INSTALL) " + makefile); lines.push("all-local:: " + prefix); lines.push("\t@true"); lines.push("clean-local:: clean-" + prefix); lines.push("\t@true"); data = lines.join("\n") + "\n"; fs.writeFileSync(makefile, data); } function moduleLicenses(input) { var parts = input.split(path.sep); var pos = parts.indexOf("node_modules"); var directory, results = []; if (pos !== -1) { directory = parts.slice(0, pos + 2).join(path.sep); fs.readdirSync(directory).forEach(function(name) { if (name.indexOf("COPYING") !== -1 || name.indexOf("LICENSE") !== -1 || name.indexOf("README.md") !== -1) results.push(path.join(directory, name)); }); } return results; } function maybePushInput(inputs, po_locations, input) { var po_location; // Don't include or external refs if (endsWith(input, '/') || endsWith(input, 'manifest.json.in') || input.indexOf("external ") === 0 || input.indexOf("multi ") === 0) { return; } // Don't include node devDependencies var parts = input.split(path.sep); var pos = parts.indexOf("node_modules"); if (pos !== -1) { deps = npm["dependencies"] || { }; have = parts[pos + 1] in deps; if (!have) return; } // The latest modified date var stats = fs.statSync(input); if (stats.mtime > latest) latest = stats.mtime; // Qualify the input file and add it input = path.relative(cwd, input); if (srcdir && input.indexOf(srcdir) === 0) { po_location = input.substr(srcdir.length+1); input = "$(srcdir)" + input.substr(srcdir.length); } else { po_location = input; } po_locations[po_location] = true; inputs[input] = true; } function endsWith(string, suffix) { return (string.lastIndexOf(suffix) === (string.length - suffix.length)) }