#!/usr/bin/env node //@ts-check var os = require("os"); var fs = require("fs"); var path = require("path"); var cp = require("child_process"); var semver = require("semver"); var jscompDir = path.join(__dirname, "..", "jscomp"); var runtimeDir = path.join(jscompDir, "runtime"); var othersDir = path.join(jscompDir, "others"); var testDir = path.join(jscompDir, "test"); var jsDir = path.join(__dirname, "..", "lib", "js"); var runtimeFiles = fs.readdirSync(runtimeDir, "ascii"); var runtimeMlFiles = runtimeFiles.filter( x => !x.startsWith("bs_stdlib_mini") && x.endsWith(".ml") && x !== "js.ml" ); var runtimeMliFiles = runtimeFiles.filter( x => !x.startsWith("bs_stdlib_mini") && x.endsWith(".mli") && x !== "js.mli" ); var runtimeSourceFiles = runtimeMlFiles.concat(runtimeMliFiles); var runtimeJsFiles = [...new Set(runtimeSourceFiles.map(baseName))]; var commonBsFlags = `-no-keep-locs -no-alias-deps -bs-no-version-header -bs-no-check-div-by-zero -nostdlib `; var js_package = pseudoTarget("js_pkg"); var runtimeTarget = pseudoTarget("runtime"); var othersTarget = pseudoTarget("others"); var stdlibTarget = pseudoTarget("$stdlib"); var my_target = require("./bin_path").absolutePath; var bsc_exe = require("./bin_path").bsc_exe; var vendorNinjaPath = require("./bin_path").ninja_exe; // Let's enforce a Node version >= 16 to make sure M1 users don't trip up on // cryptic issues caused by mismatching assembly architectures Node 16 ships // with a native arm64 binary, and will set process.arch to "arm64" (instead of // Rosetta emulated "x86") if (semver.lt(process.version, "16.0.0")) { console.error("Requires node version 16 or above... Abort."); process.exit(1); } exports.vendorNinjaPath = vendorNinjaPath; /** * By default we use vendored, * we produce two ninja files which won't overlap * one is build.ninja which use vendored config * the other is env.ninja which use binaries from environment * * In dev mode, files generated for vendor config * * build.ninja * compiler.ninja * snapshot.ninja * runtime/build.ninja * others/build.ninja * $stdlib/build.ninja * test/build.ninja * * files generated for env config * * env.ninja * compilerEnv.ninja (no snapshot since env can not provide snapshot) * runtime/env.ninja * others/env.ninja * $stdlib/env.ninja * test/env.ninja * * In release mode: * * release.ninja * runtime/release.ninja * others/release.ninja * $stdlib/release.ninja * * Like that our snapshot is so robust that * we don't do snapshot in CI, we don't * need do test build in CI either * */ /** * @type {string} */ var versionString = undefined; /** * * @returns {string} */ var getVersionString = () => { if (versionString === undefined) { var searcher = "version"; try { var output = cp.execSync(`ocamldep.opt -version`, { encoding: "ascii", }); versionString = output .substring(output.indexOf(searcher) + searcher.length) .trim(); } catch (err) { // console.error(`This error probably came from that you don't have our vendored ocaml installed If this is the first time you clone the repo try this git submodule init && git submodule update node ./scripts/buildocaml.js `); console.error(err.message); process.exit(err.status); } } return versionString; }; /** * * @param {string} ninjaCwd */ function ruleCC(ninjaCwd) { return ` rule cc command = $bsc -bs-cmi -bs-cmj $bsc_flags -I ${ninjaCwd} $in description = $in -> $out rule cc_cmi command = $bsc -bs-read-cmi -bs-cmi -bs-cmj $bsc_flags -I ${ninjaCwd} $in description = $in -> $out `; } /** * Fixed since it is already vendored */ var cppoMonoFile = `../vendor/cppo/cppo_bin.ml`; /** * * @param {string} name * @param {string} content */ function writeFileAscii(name, content) { fs.writeFile(name, content, "ascii", throwIfError); } /** * * @param {string} name * @param {string} content */ function writeFileSync(name, content) { return fs.writeFileSync(name, content, "ascii"); } /** * * @param {NodeJS.ErrnoException} err */ function throwIfError(err) { if (err !== null) { throw err; } } /** * * @typedef { {kind : "file" , name : string} | {kind : "pseudo" , name : string}} Target * @typedef {{key : string, value : string}} Override * @typedef { Target[]} Targets * @typedef {Map<string,TargetSet>} DepsMap */ class TargetSet { /** * * @param {Targets} xs */ constructor(xs = []) { this.data = xs; } /** * * @param {Target} x */ add(x) { var data = this.data; var found = false; for (var i = 0; i < data.length; ++i) { var cur = data[i]; if (cur.kind === x.kind && cur.name === x.name) { found = true; break; } } if (!found) { this.data.push(x); } return this; } /** * @returns {Targets} a copy * */ toSortedArray() { var newData = this.data.concat(); newData.sort((x, y) => { var kindx = x.kind; var kindy = y.kind; if (kindx > kindy) { return 1; } else if (kindx < kindy) { return -1; } else { if (x.name > y.name) { return 1; } else if (x.name < y.name) { return -1; } else { return 0; } } }); return newData; } /** * * @param {(item:Target)=>void} callback */ forEach(callback) { this.data.forEach(callback); } } /** * * @param {string} target * @param {string} dependency * @param {DepsMap} depsMap */ function updateDepsKVByFile(target, dependency, depsMap) { var singleTon = fileTarget(dependency); if (depsMap.has(target)) { depsMap.get(target).add(singleTon); } else { depsMap.set(target, new TargetSet([singleTon])); } } /** * * @param {string} s */ function uncapitalize(s) { if (s.length === 0) { return s; } return s[0].toLowerCase() + s.slice(1); } /** * * @param {string} target * @param {string[]} dependencies * @param {DepsMap} depsMap */ function updateDepsKVsByFile(target, dependencies, depsMap) { var targets = fileTargets(dependencies); if (depsMap.has(target)) { var s = depsMap.get(target); for (var i = 0; i < targets.length; ++i) { s.add(targets[i]); } } else { depsMap.set(target, new TargetSet(targets)); } } /** * * @param {string} target * @param {string[]} modules * @param {DepsMap} depsMap */ function updateDepsKVsByModule(target, modules, depsMap) { if (depsMap.has(target)) { let s = depsMap.get(target); for (let module of modules) { let filename = uncapitalize(module); let filenameAsCmi = filename + ".cmi"; let filenameAsCmj = filename + ".cmj"; if (target.endsWith(".cmi")) { if (depsMap.has(filenameAsCmi) || depsMap.has(filenameAsCmj)) { s.add(fileTarget(filenameAsCmi)); } } else if (target.endsWith(".cmj")) { if (depsMap.has(filenameAsCmj)) { s.add(fileTarget(filenameAsCmj)); } else if (depsMap.has(filenameAsCmi)) { s.add(fileTarget(filenameAsCmi)); } } } } } /** * * @param {string[]}sources * @return {DepsMap} */ function createDepsMapWithTargets(sources) { /** * @type {DepsMap} */ let depsMap = new Map(); for (let source of sources) { let target = sourceToTarget(source); depsMap.set(target, new TargetSet([])); } depsMap.forEach((set, name) => { let cmiFile; if ( name.endsWith(".cmj") && depsMap.has((cmiFile = replaceExt(name, ".cmi"))) ) { set.add(fileTarget(cmiFile)); } }); return depsMap; } /** * * @param {Target} file * @param {string} cwd */ function targetToString(file, cwd) { switch (file.kind) { case "file": return path.join(cwd, file.name); case "pseudo": return file.name; default: throw Error; } } /** * * @param {Targets} files * @param {string} cwd * * @returns {string} return a string separated with whitespace */ function targetsToString(files, cwd) { return files.map(x => targetToString(x, cwd)).join(" "); } /** * * @param {Targets} outputs * @param {Targets} inputs * @param {Targets} deps * @param {Override[]} overrides * @param {string} rule * @param {string} cwd * @return {string} */ function ninjaBuild(outputs, inputs, rule, deps, cwd, overrides) { var fileOutputs = targetsToString(outputs, cwd); var fileInputs = targetsToString(inputs, cwd); var stmt = `o ${fileOutputs} : ${rule} ${fileInputs}`; // deps.push(pseudoTarget('../lib/bsc')) if (deps.length > 0) { var fileDeps = targetsToString(deps, cwd); stmt += ` | ${fileDeps}`; } if (overrides.length > 0) { stmt += `\n` + overrides .map(x => { return ` ${x.key} = ${x.value}`; }) .join("\n"); } return stmt; } /** * * @param {Target} outputs * @param {Targets} inputs * @param {string} cwd */ function phony(outputs, inputs, cwd) { return ninjaBuild([outputs], inputs, "phony", [], cwd, []); } /** * * @param {string | string[]} outputs * @param {string | string[]} inputs * @param {string | string[]} fileDeps * @param {string} rule * @param {string} cwd * @param {[string,string][]} overrides * @param {Target | Targets} extraDeps */ function ninjaQuickBuild( outputs, inputs, rule, cwd, overrides, fileDeps, extraDeps ) { var os = Array.isArray(outputs) ? fileTargets(outputs) : [fileTarget(outputs)]; var is = Array.isArray(inputs) ? fileTargets(inputs) : [fileTarget(inputs)]; var ds = Array.isArray(fileDeps) ? fileTargets(fileDeps) : [fileTarget(fileDeps)]; var dds = Array.isArray(extraDeps) ? extraDeps : [extraDeps]; return ninjaBuild( os, is, rule, ds.concat(dds), cwd, overrides.map(x => { return { key: x[0], value: x[1] }; }) ); } /** * @typedef { (string | string []) } Strings * @typedef { [string,string]} KV * @typedef { [Strings, Strings, string, string, KV[], Strings, (Target|Targets)] } BuildList * @param {BuildList[]} xs * @returns {string} */ function ninjaQuickBuidList(xs) { return xs .map(x => ninjaQuickBuild(x[0], x[1], x[2], x[3], x[4], x[5], x[6])) .join("\n"); } /** * @typedef { [string,string,string?]} CppoInput * @param {CppoInput[]} xs * @param {string} cwd * @returns {string} */ function cppoList(cwd, xs) { return xs .map(x => { /** * @type {KV[]} */ var variables; if (x[2]) { variables = [["type", `-D ${x[2]}`]]; } else { variables = []; } var extraDeps = pseudoTarget(cppoFile); return ninjaQuickBuild( x[0], x[1], cppoRuleName, cwd, variables, [], extraDeps ); }) .join("\n"); } /** * * @param {string} cwd * @param {string[]} xs * @returns {string} */ function mllList(cwd, xs) { return xs .map(x => { var output = baseName(x) + ".ml"; return ninjaQuickBuild(output, x, mllRuleName, cwd, [], [], []); }) .join("\n"); } /** * * @param {string} cwd * @param {string[]} xs * @returns {string} */ function mlyList(cwd, xs) { return xs .map(x => { var output = baseName(x) + ".ml"; return ninjaQuickBuild(output, x, mlyRuleName, cwd, [], [], []); }) .join("\n"); } /** * * @param {string} name * @returns {Target} */ function fileTarget(name) { return { kind: "file", name }; } /** * * @param {string} name * @returns {Target} */ function pseudoTarget(name) { return { kind: "pseudo", name }; } /** * * @param {string[]} args * @returns {Targets} */ function fileTargets(args) { return args.map(name => fileTarget(name)); } /** * * @param {string[]} outputs * @param {string[]} inputs * @param {DepsMap} depsMap * @param {Override[]} overrides * @param {Targets} extraDeps * @param {string} rule * @param {string} cwd */ function buildStmt(outputs, inputs, rule, depsMap, cwd, overrides, extraDeps) { var os = outputs.map(fileTarget); var is = inputs.map(fileTarget); var deps = new TargetSet(); for (var i = 0; i < outputs.length; ++i) { var curDeps = depsMap.get(outputs[i]); if (curDeps !== undefined) { curDeps.forEach(x => deps.add(x)); } } extraDeps.forEach(x => deps.add(x)); return ninjaBuild(os, is, rule, deps.toSortedArray(), cwd, overrides); } /** * * @param {string} x */ function replaceCmj(x) { return x.trim().replace("cmx", "cmj"); } /** * * @param {string} y */ function sourceToTarget(y) { if (y.endsWith(".ml") || y.endsWith(".res")) { return replaceExt(y, ".cmj"); } else if (y.endsWith(".mli") || y.endsWith(".resi")) { return replaceExt(y, ".cmi"); } return y; } /** * * @param {string[]} files * @param {string} dir * @param {DepsMap} depsMap * @return {Promise<void>} * Note `bsdep.exe` does not need post processing and -one-line flag * By default `ocamldep.opt` only list dependencies in its args */ function ocamlDepForBscAsync(files, dir, depsMap) { return new Promise((resolve, reject) => { var tmpdir = null; const mlfiles = []; // convert .res files to temporary .ml files in tmpdir files.forEach(f => { const { name, ext } = path.parse(f); if (ext === ".res" || ext === ".resi") { const mlname = ext === ".resi" ? name + ".mli" : name + ".ml"; if (tmpdir == null) { tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "resToMl")); } try { const mlfile = path.join(tmpdir, mlname); cp.execSync(`${bsc_exe} -dsource -only-parse ${f} 2>${mlfile}`, { cwd: dir, encoding: "ascii", }); mlfiles.push(mlfile); } catch (err) { console.log(err); } } }); const minusI = tmpdir == null ? "" : `-I ${tmpdir}`; cp.exec( `ocamldep.opt -allow-approx -one-line ${minusI} -native ${files.join( " " )} ${mlfiles.join(" ")}`, { cwd: dir, encoding: "ascii", }, function (error, stdout, stderr) { if (tmpdir != null) { fs.rmSync(tmpdir, { recursive: true, force: true }); } if (error !== null) { return reject(error); } else { const pairs = stdout.split("\n").map(x => x.split(":")); pairs.forEach(x => { var deps; let source = replaceCmj(path.basename(x[0])); if (x[1] !== undefined && (deps = x[1].trim())) { deps = deps.split(" "); updateDepsKVsByFile( source, deps.map(x => replaceCmj(path.basename(x))), depsMap ); } }); return resolve(); } } ); }); } /** * * @param {string[]} files * @param {string} dir * @param {DepsMap} depsMap * @return { Promise<void> []} * Note `bsdep.exe` does not need post processing and -one-line flag * By default `ocamldep.opt` only list dependencies in its args */ function depModulesForBscAsync(files, dir, depsMap) { let ocamlFiles = files.filter(x => x.endsWith(".ml") || x.endsWith(".mli")); let resFiles = files.filter(x => x.endsWith(".res") || x.endsWith(".resi")); /** * * @param {(value:void) =>void} resolve * @param {(value:any)=>void} reject */ let cb = (resolve, reject) => { /** * @param {any} error * @param {string} stdout * @param {string} stderr */ let fn = function (error, stdout, stderr) { if (error !== null) { return reject(error); } else { var pairs = stdout.split("\n").map(x => x.split(":")); pairs.forEach(x => { var modules; let source = sourceToTarget(x[0].trim()); if (x[1] !== undefined && (modules = x[1].trim())) { modules = modules.split(" "); updateDepsKVsByModule(source, modules, depsMap); } }); return resolve(); } }; return fn; }; let config = { cwd: dir, encoding: "ascii", }; return [ new Promise((resolve, reject) => { cp.exec( `${bsc_exe} -modules -bs-syntax-only ${resFiles.join( " " )} ${ocamlFiles.join(" ")}`, config, cb(resolve, reject) ); }), ]; } /** * @typedef {('HAS_ML' | 'HAS_MLI' | 'HAS_BOTH' | 'HAS_RES' | 'HAS_RESI' | 'HAS_BOTH_RES')} FileInfo * @param {string[]} sourceFiles * @returns {Map<string, FileInfo>} * We make a set to ensure that `sourceFiles` are not duplicated */ function collectTarget(sourceFiles) { /** * @type {Map<string,FileInfo>} */ var allTargets = new Map(); sourceFiles.forEach(x => { var { ext, name } = path.parse(x); var existExt = allTargets.get(name); if (existExt === undefined) { if (ext === ".ml") { allTargets.set(name, "HAS_ML"); } else if (ext === ".mli") { allTargets.set(name, "HAS_MLI"); } else if (ext === ".res") { allTargets.set(name, "HAS_RES"); } else if (ext === ".resi") { allTargets.set(name, "HAS_RESI"); } } else { switch (existExt) { case "HAS_ML": if (ext === ".mli") { allTargets.set(name, "HAS_BOTH"); } break; case "HAS_RES": if (ext === ".resi") { allTargets.set(name, "HAS_BOTH_RES"); } break; case "HAS_MLI": if (ext === ".ml") { allTargets.set(name, "HAS_BOTH"); } break; case "HAS_RESI": if (ext === ".res") { allTargets.set(name, "HAS_BOTH_RES"); } break; case "HAS_BOTH": case "HAS_BOTH_RES": break; } } }); return allTargets; } /** * * @param {Map<string, FileInfo>} allTargets * @param {string[]} collIn * @returns {string[]} A new copy which is * */ function scanFileTargets(allTargets, collIn) { var coll = collIn.concat(); allTargets.forEach((ext, mod) => { switch (ext) { case "HAS_RESI": case "HAS_MLI": coll.push(`${mod}.cmi`); break; case "HAS_BOTH_RES": case "HAS_BOTH": coll.push(`${mod}.cmi`, `${mod}.cmj`); break; case "HAS_RES": case "HAS_ML": coll.push(`${mod}.cmi`, `${mod}.cmj`); break; } }); return coll; } /** * * @param {DepsMap} depsMap * @param {Map<string,string>} allTargets * @param {string} cwd * @param {Targets} extraDeps * @return {string[]} */ function generateNinja(depsMap, allTargets, cwd, extraDeps = []) { /** * @type {string[]} */ var build_stmts = []; allTargets.forEach((x, mod) => { let ouptput_cmj = mod + ".cmj"; let output_cmi = mod + ".cmi"; let input_ml = mod + ".ml"; let input_mli = mod + ".mli"; let input_res = mod + ".res"; let input_resi = mod + ".resi"; /** * @type {Override[]} */ var overrides = []; // if (mod.endsWith("Labels")) { // overrides.push({ key: "bsc_flags", value: "$bsc_flags -nolabels" }); // } /** * * @param {string[]} outputs * @param {string[]} inputs * */ let mk = (outputs, inputs, rule = "cc") => { return build_stmts.push( buildStmt(outputs, inputs, rule, depsMap, cwd, overrides, extraDeps) ); }; switch (x) { case "HAS_BOTH": mk([ouptput_cmj], [input_ml], "cc_cmi"); mk([output_cmi], [input_mli]); break; case "HAS_BOTH_RES": mk([ouptput_cmj], [input_res], "cc_cmi"); mk([output_cmi], [input_resi], "cc"); break; case "HAS_RES": mk([output_cmi, ouptput_cmj], [input_res], "cc"); break; case "HAS_ML": mk([output_cmi, ouptput_cmj], [input_ml]); break; case "HAS_RESI": mk([output_cmi], [input_resi], "cc"); break; case "HAS_MLI": mk([output_cmi], [input_mli]); break; } }); return build_stmts; } var COMPILIER = bsc_exe; var BSC_COMPILER = `bsc = ${COMPILIER}`; async function runtimeNinja(devmode = true) { var ninjaCwd = "runtime"; var compilerTarget = pseudoTarget("$bsc"); var externalDeps = devmode ? [compilerTarget] : []; var ninjaOutput = devmode ? "build.ninja" : "release.ninja"; var templateRuntimeRules = ` bsc_no_open_flags = ${commonBsFlags} -bs-cross-module-opt -make-runtime -nopervasives -unsafe -w +50 -warn-error A bsc_flags = $bsc_no_open_flags -open Bs_stdlib_mini ${ruleCC(ninjaCwd)} ${ninjaQuickBuidList([ [ "bs_stdlib_mini.cmi", "bs_stdlib_mini.mli", "cc", ninjaCwd, [["bsc_flags", "-nostdlib -nopervasives"]], [], externalDeps, ], [ ["js.cmj", "js.cmi"], "js.ml", "cc", ninjaCwd, [["bsc_flags", "$bsc_no_open_flags"]], [], externalDeps, ], ])} `; /** * @type {DepsMap} */ var depsMap = new Map(); var allTargets = collectTarget([...runtimeMliFiles, ...runtimeMlFiles]); var manualDeps = ["bs_stdlib_mini.cmi", "js.cmj", "js.cmi"]; var allFileTargetsInRuntime = scanFileTargets(allTargets, manualDeps); allTargets.forEach((ext, mod) => { switch (ext) { case "HAS_MLI": case "HAS_BOTH": updateDepsKVsByFile(mod + ".cmi", manualDeps, depsMap); break; case "HAS_ML": updateDepsKVsByFile(mod + ".cmj", manualDeps, depsMap); break; } }); // FIXME: in dev mode, it should not rely on reading js file // since it may cause a bootstrapping issues try { await Promise.all([ runJSCheckAsync(depsMap), ocamlDepForBscAsync(runtimeSourceFiles, runtimeDir, depsMap), ]); var stmts = generateNinja(depsMap, allTargets, ninjaCwd, externalDeps); stmts.push( phony(runtimeTarget, fileTargets(allFileTargetsInRuntime), ninjaCwd) ); writeFileAscii( path.join(runtimeDir, ninjaOutput), templateRuntimeRules + stmts.join("\n") + "\n" ); } catch (e) { console.log(e); } } var dTypeString = "TYPE_STRING"; var dTypeInt = "TYPE_INT"; var dTypeFunctor = "TYPE_FUNCTOR"; var dTypeLocalIdent = "TYPE_LOCAL_IDENT"; var dTypeIdent = "TYPE_IDENT"; var dTypePoly = "TYPE_POLY"; var cppoRuleName = `cppo`; var cppoFile = `./bin/cppo.exe`; var cppoRule = (flags = "") => ` rule ${cppoRuleName} command = ${cppoFile} -V OCAML:${getVersionString()} ${flags} $type $in -o $out generator = true `; var mllRuleName = `mll`; var mllRule = ` rule ${mllRuleName} command = $ocamllex $in generator = true `; var mlyRuleName = `mly`; var mlyRule = ` rule ${mlyRuleName} command = $ocamlyacc -v --strict $in generator = true `; async function othersNinja(devmode = true) { var compilerTarget = pseudoTarget("$bsc"); var externalDeps = [ compilerTarget, fileTarget("belt_internals.cmi"), fileTarget("js.cmi"), ]; var ninjaOutput = devmode ? "build.ninja" : "release.ninja"; var ninjaCwd = "others"; var templateOthersRules = ` bsc_primitive_flags = ${commonBsFlags} -bs-cross-module-opt -make-runtime -nopervasives -unsafe -w +50 -warn-error A bsc_flags = $bsc_primitive_flags -open Belt_internals ${ruleCC(ninjaCwd)} ${ninjaQuickBuidList([ [ ["belt.cmj", "belt.cmi"], "belt.ml", "cc", ninjaCwd, [["bsc_flags", "$bsc_primitive_flags"]], [], [compilerTarget], ], [ ["js.cmj", "js.cmi"], "js.ml", "cc", ninjaCwd, [["bsc_flags", "$bsc_primitive_flags"]], [], [compilerTarget], ], [ ["belt_internals.cmi"], "belt_internals.mli", "cc", ninjaCwd, [["bsc_flags", "$bsc_primitive_flags"]], [], [compilerTarget], ], [ ["node.cmj", "node.cmi"], "node.ml", "cc", ninjaCwd, [], // depends on belt_internals [], [compilerTarget, fileTarget("js.cmi"), fileTarget("belt_internals.cmi")], // need js.cm* ], ])} `; var othersDirFiles = fs.readdirSync(othersDir, "ascii"); var jsPrefixSourceFiles = othersDirFiles.filter( x => x.startsWith("js") && (x.endsWith(".ml") || x.endsWith(".mli") || x.endsWith(".res") || x.endsWith(".resi")) && !x.includes(".cppo") && !x.includes(".pp") && !x.includes("#") && x !== "js.ml" ); var othersFiles = othersDirFiles.filter( x => !x.startsWith("js") && x !== "belt.ml" && x !== "belt_internals.mli" && x !== "node.ml" && (x.endsWith(".ml") || x.endsWith(".mli")) && !x.includes("#") && !x.includes(".cppo") // we have node .. ); var jsTargets = collectTarget(jsPrefixSourceFiles); var allJsTargets = scanFileTargets(jsTargets, []); let jsDepsMap = new Map(); let depsMap = new Map(); await Promise.all([ ocamlDepForBscAsync(jsPrefixSourceFiles, othersDir, jsDepsMap), ocamlDepForBscAsync(othersFiles, othersDir, depsMap), ]); var jsOutput = generateNinja(jsDepsMap, jsTargets, ninjaCwd, externalDeps); jsOutput.push(phony(js_package, fileTargets(allJsTargets), ninjaCwd)); // Note compiling belt.ml still try to read // belt_xx.cmi we need enforce the order to // avoid data race issues var beltPackage = fileTarget("belt.cmi"); var nodePackage = fileTarget("node.cmi"); var beltTargets = collectTarget(othersFiles); depsMap.forEach((s, k) => { if (k.startsWith("belt")) { s.add(beltPackage); } else if (k.startsWith("node")) { s.add(nodePackage); } s.add(js_package); }); var allOthersTarget = scanFileTargets(beltTargets, []); var beltOutput = generateNinja(depsMap, beltTargets, ninjaCwd, externalDeps); beltOutput.push(phony(othersTarget, fileTargets(allOthersTarget), ninjaCwd)); // ninjaBuild([`belt_HashSetString.ml`,]) writeFileAscii( path.join(othersDir, ninjaOutput), templateOthersRules + jsOutput.join("\n") + "\n" + beltOutput.join("\n") + "\n" ); } /** * * @param {boolean} devmode * generate build.ninja/release.ninja for stdlib-402 */ async function stdlibNinja(devmode = true) { var stdlibVersion = "stdlib-406"; var ninjaCwd = stdlibVersion; var stdlibDir = path.join(jscompDir, stdlibVersion); var compilerTarget = pseudoTarget("$bsc"); var externalDeps = [compilerTarget, othersTarget]; var ninjaOutput = devmode ? "build.ninja" : "release.ninja"; var bsc_flags = "bsc_flags"; /** * @type [string,string][] */ var bsc_builtin_overrides = [[bsc_flags, `$${bsc_flags} -nopervasives`]]; // It is interesting `-w -a` would generate not great code sometimes // deprecations diabled due to string_of_float var warnings = "-w -9-3-106 -warn-error A"; var templateStdlibRules = ` ${bsc_flags} = ${commonBsFlags} -bs-cross-module-opt -make-runtime ${warnings} -I others ${ruleCC(ninjaCwd)} ${ninjaQuickBuidList([ // we make it still depends on external // to enjoy free ride on dev config for compiler-deps [ "pervasives.cmj", "pervasives.ml", "cc_cmi", ninjaCwd, bsc_builtin_overrides, "pervasives.cmi", externalDeps, ], [ "pervasives.cmi", "pervasives.mli", "cc", ninjaCwd, bsc_builtin_overrides, [], externalDeps, ], ])} `; var stdlibDirFiles = fs.readdirSync(stdlibDir, "ascii"); var sources = stdlibDirFiles.filter(x => { return ( !x.startsWith("pervasives") && (x.endsWith(".ml") || x.endsWith(".mli")) ); }); let depsMap = new Map(); await ocamlDepForBscAsync(sources, stdlibDir, depsMap); var targets = collectTarget(sources); var allTargets = scanFileTargets(targets, [ "pervasives.cmi", "pervasives.cmj", ]); targets.forEach((ext, mod) => { switch (ext) { case "HAS_MLI": case "HAS_BOTH": updateDepsKVByFile(mod + ".cmi", "pervasives.cmj", depsMap); break; case "HAS_ML": updateDepsKVByFile(mod + ".cmj", "pervasives.cmj", depsMap); break; } }); var output = generateNinja(depsMap, targets, ninjaCwd, externalDeps); output.push(phony(stdlibTarget, fileTargets(allTargets), ninjaCwd)); writeFileAscii( path.join(stdlibDir, ninjaOutput), templateStdlibRules + output.join("\n") + "\n" ); } /** * * @param {string} text */ function getDeps(text) { /** * @type {string[]} */ var deps = []; text.replace( /(\/\*[\w\W]*?\*\/|\/\/[^\n]*|[.$]r)|\brequire\s*\(\s*["']([^"']*)["']\s*\)/g, function (_, ignore, id) { if (!ignore) deps.push(id); return ""; // TODO: examine the regex } ); return deps; } /** * * @param {string} x * @param {string} newExt * @example * * ```js * replaceExt('xx.cmj', '.a') // return 'xx.a' * ``` * */ function replaceExt(x, newExt) { let index = x.lastIndexOf("."); if (index < 0) { return x; } return x.slice(0, index) + newExt; } /** * * @param {string} x */ function baseName(x) { return x.substr(0, x.indexOf(".")); } /** * * @returns {Promise<void>} */ async function testNinja() { var ninjaOutput = "build.ninja"; var ninjaCwd = `test`; var templateTestRules = ` bsc_flags = -bs-no-version-header -bs-cross-module-opt -make-runtime-test -bs-package-output commonjs:jscomp/test -w -3-6-26-27-29-30-32..40-44-45-52-60-9-106+104 -warn-error A -I runtime -I $stdlib -I others ${ruleCC(ninjaCwd)} ${mllRule} ${mllList(ninjaCwd, [ "arith_lexer.mll", "number_lexer.mll", "simple_lexer_test.mll", ])} `; var testDirFiles = fs.readdirSync(testDir, "ascii"); var sources = testDirFiles.filter(x => { return ( x.endsWith(".resi") || x.endsWith(".res") || ((x.endsWith(".ml") || x.endsWith(".mli")) && !x.endsWith("bspack.ml")) ); }); let depsMap = createDepsMapWithTargets(sources); await Promise.all(depModulesForBscAsync(sources, testDir, depsMap)); var targets = collectTarget(sources); var output = generateNinja(depsMap, targets, ninjaCwd, [ runtimeTarget, stdlibTarget, pseudoTarget("$bsc"), ]); output.push( phony( pseudoTarget("test"), fileTargets(scanFileTargets(targets, [])), ninjaCwd ) ); writeFileAscii( path.join(testDir, ninjaOutput), templateTestRules + output.join("\n") + "\n" ); } /** * * @param {DepsMap} depsMap */ function runJSCheckAsync(depsMap) { return new Promise(resolve => { var count = 0; var tasks = runtimeJsFiles.length; var updateTick = () => { count++; if (count === tasks) { resolve(count); } }; runtimeJsFiles.forEach(name => { var jsFile = path.join(jsDir, name + ".js"); fs.readFile(jsFile, "utf8", function (err, fileContent) { if (err === null) { var deps = getDeps(fileContent).map(x => path.parse(x).name + ".cmj"); fs.exists(path.join(runtimeDir, name + ".mli"), exist => { if (exist) { deps.push(name + ".cmi"); } updateDepsKVsByFile(`${name}.cmj`, deps, depsMap); updateTick(); }); } else { // file non exist or reading error ignore updateTick(); } }); }); }); } function checkEffect() { var jsPaths = runtimeJsFiles.map(x => path.join(jsDir, x + ".js")); var effect = jsPaths .map(x => { return { file: x, content: fs.readFileSync(x, "utf8"), }; }) .map(({ file, content: x }) => { if (/No side effect|This output is empty/.test(x)) { return { file, effect: "pure", }; } else if (/Not a pure module/.test(x)) { return { file, effect: "false", }; } else { return { file, effect: "unknown", }; } }) .filter(({ effect }) => effect !== "pure") .map(({ file, effect }) => { return { file: path.basename(file), effect }; }); var black_list = new Set([ "caml_int32.js", "caml_int64.js", "caml_lexer.js", "caml_parser.js", ]); var assert = require("assert"); // @ts-ignore assert( effect.length === black_list.size && effect.every(x => black_list.has(x.file)) ); console.log(effect); } /** * * @param {string[]} domain * @param {Map<string,Set<string>>} dependency_graph * @returns {string[]} */ function sortFilesByDeps(domain, dependency_graph) { /** * @type{string[]} */ var result = []; var workList = new Set(domain); /** * * @param {Set<string>} visiting * @param {string[]} path * @param {string} current */ var visit = function (visiting, path, current) { if (visiting.has(current)) { throw new Error(`cycle: ${path.concat(current).join(" ")}`); } if (workList.has(current)) { visiting.add(current); var next = dependency_graph.get(current); if (next !== undefined && next.size > 0) { next.forEach(x => { visit(visiting, path.concat(current), x); }); } visiting.delete(current); workList.delete(current); result.push(current); } }; while (workList.size > 0) { visit(new Set(), [], workList.values().next().value); } return result; } function updateRelease() { runtimeNinja(false); stdlibNinja(false); othersNinja(false); } function updateDev() { writeFileAscii( path.join(jscompDir, "build.ninja"), ` subninja compiler.ninja stdlib = stdlib-406 ${BSC_COMPILER} ocamllex = ocamllex.opt subninja runtime/build.ninja subninja others/build.ninja subninja $stdlib/build.ninja subninja test/build.ninja o all: phony runtime others $stdlib test ` ); writeFileAscii( path.join(jscompDir, "..", "lib", "build.ninja"), ` ocamlopt = ocamlopt.opt ext = exe INCL= "4.06.1+BS" include body.ninja ` ); preprocessorNinjaSync(); // This is needed so that ocamldep makes sense nativeNinja(); runtimeNinja(); stdlibNinja(true); if (fs.existsSync(bsc_exe)) { testNinja(); } othersNinja(); } exports.updateDev = updateDev; exports.updateRelease = updateRelease; /** * * @param {string} dir */ function readdirSync(dir) { return fs.readdirSync(dir, "ascii"); } /** * @type {string[]} */ var black_list = []; /** * * @param {string} dir */ function test(dir) { return readdirSync(path.join(jscompDir, dir)) .filter(x => { return ( (x.endsWith(".ml") || x.endsWith(".mli")) && !(x.endsWith(".cppo.ml") || x.endsWith(".cppo.mli")) && !(x.endsWith(".pp.ml") || x.endsWith(".pp.mli")) && !black_list.some(name => x.includes(name)) ); }) .map(x => path.join(dir, x)); } /** * * @param {Set<string>} xs * @returns {string} */ function setSortedToStringAsNativeDeps(xs) { var arr = Array.from(xs).sort(); // it relies on we have -opaque, so that .cmx is dummy file return arr.join(" ").replace(/\.cmx/g, ".cmi"); } /** * Built cppo.exe for dev purpose */ function preprocessorNinjaSync() { var napkinFiles = fs .readdirSync(path.join(jscompDir, "..", "res_syntax", "src"), "ascii") .filter(x => x.endsWith(".ml") || x.endsWith(".mli")); var napkinCliFiles = fs .readdirSync(path.join(jscompDir, "..", "res_syntax", "cli"), "ascii") .filter(x => x.endsWith(".ml") || x.endsWith(".mli")); var buildNapkinFiles = napkinFiles .map(file => `o napkin/${file} : copy ../res_syntax/src/${file}`) .join("\n"); var buildNapkinCliFiles = napkinCliFiles .map(file => `o napkin/${file} : copy ../res_syntax/cli/${file}`) .join("\n"); var cppoNative = ` ocamlopt = ocamlopt.opt ocamllex = ocamllex.opt ocamlc = ocamlc.opt ocamlmklib = ocamlmklib ocaml = ocaml ocamlyacc = ocamlyacc rule link command = $ocamlopt -g $flags $libs $in -o $out rule bytelink command = $ocamlc -g $flags $libs $in -o $out o ${cppoFile}: link ${cppoMonoFile} libs = unix.cmxa str.cmxa generator = true ${cppoRule()} ${cppoList("ext", [ ["hash_set_string.ml", "hash_set.cppo.ml", dTypeString], ["hash_set_int.ml", "hash_set.cppo.ml", dTypeInt], ["hash_set_ident.ml", "hash_set.cppo.ml", dTypeIdent], ["hash_set.ml", "hash_set.cppo.ml", dTypeFunctor], ["hash_set_poly.ml", "hash_set.cppo.ml", dTypePoly], ["vec_int.ml", "vec.cppo.ml", dTypeInt], ["vec.ml", "vec.cppo.ml", dTypeFunctor], ["set_string.ml", "set.cppo.ml", dTypeString], ["set_int.ml", "set.cppo.ml", dTypeInt], ["set_ident.ml", "set.cppo.ml", dTypeIdent], ["map_string.ml", "map.cppo.ml", dTypeString], ["map_int.ml", "map.cppo.ml", dTypeInt], ["map_ident.ml", "map.cppo.ml", dTypeIdent], [ "ordered_hash_map_local_ident.ml", "ordered_hash_map.cppo.ml", dTypeLocalIdent, ], ["hash_string.ml", "hash.cppo.ml", dTypeString], ["hash_int.ml", "hash.cppo.ml", dTypeInt], ["hash_ident.ml", "hash.cppo.ml", dTypeIdent], ["hash.ml", "hash.cppo.ml", dTypeFunctor], ["ext_string.ml", "ext_string.pp.ml"], ["ext_string.mli", "ext_string.pp.mli"], ["ext_sys.ml", "ext_sys.pp.ml"], ])} ${cppoList("stubs", [["bs_hash_stubs.ml", "bs_hash_stubs.pp.ml"]])} ${cppoList("ml", [ ["cmt_format.ml", "cmt_format.pp.ml"], ["pprintast.ml", "pprintast.pp.ml"], ])} ${cppoList("common", [["js_config.ml", "js_config.pp.ml"]])} ${cppoList("frontend", [["external_ffi_types.ml", "external_ffi_types.pp.ml"]])} ${cppoList("core", [ ["lam_compile_main.ml", "lam_compile_main.pp.ml"], ["bs_cmi_load.ml", "bs_cmi_load.pp.ml"], ["bs_conditional_initial.ml", "bs_conditional_initial.pp.ml"], ["js_cmj_load.ml", "js_cmj_load.pp.ml"], ["js_name_of_module_id.ml", "js_name_of_module_id.pp.ml"], ["js_pass_debug.ml", "js_pass_debug.pp.ml"], ["lam_pass_lets_dce.ml", "lam_pass_lets_dce.pp.ml"], ["lam_util.ml", "lam_util.pp.ml"], ])} ${cppoList("others", [ ["belt_HashSetString.ml", "hashset.cppo.ml", dTypeString], ["belt_HashSetString.mli", "hashset.cppo.mli", dTypeString], ["belt_HashSetInt.ml", "hashset.cppo.ml", dTypeInt], ["belt_HashSetInt.mli", "hashset.cppo.mli", dTypeInt], ["belt_HashMapString.ml", "hashmap.cppo.ml", dTypeString], ["belt_HashMapString.mli", "hashmap.cppo.mli", dTypeString], ["belt_HashMapInt.ml", "hashmap.cppo.ml", dTypeInt], ["belt_HashMapInt.mli", "hashmap.cppo.mli", dTypeInt], ["belt_MapString.ml", "map.cppo.ml", dTypeString], ["belt_MapString.mli", "map.cppo.mli", dTypeString], ["belt_MapInt.ml", "map.cppo.ml", dTypeInt], ["belt_MapInt.mli", "map.cppo.mli", dTypeInt], ["belt_SetString.ml", "belt_Set.cppo.ml", dTypeString], ["belt_SetString.mli", "belt_Set.cppo.mli", dTypeString], ["belt_SetInt.ml", "belt_Set.cppo.ml", dTypeInt], ["belt_SetInt.mli", "belt_Set.cppo.mli", dTypeInt], ["belt_MutableMapString.ml", "mapm.cppo.ml", dTypeString], ["belt_MutableMapString.mli", "mapm.cppo.mli", dTypeString], ["belt_MutableMapInt.ml", "mapm.cppo.ml", dTypeInt], ["belt_MutableMapInt.mli", "mapm.cppo.mli", dTypeInt], ["belt_MutableSetString.ml", "setm.cppo.ml", dTypeString], ["belt_MutableSetString.mli", "setm.cppo.mli", dTypeString], ["belt_MutableSetInt.ml", "setm.cppo.ml", dTypeInt], ["belt_MutableSetInt.mli", "setm.cppo.mli", dTypeInt], ["belt_SortArrayString.ml", "sort.cppo.ml", dTypeString], ["belt_SortArrayString.mli", "sort.cppo.mli", dTypeString], ["belt_SortArrayInt.ml", "sort.cppo.ml", dTypeInt], ["belt_SortArrayInt.mli", "sort.cppo.mli", dTypeInt], ["belt_internalMapString.ml", "internal_map.cppo.ml", dTypeString], ["belt_internalMapInt.ml", "internal_map.cppo.ml", dTypeInt], ["belt_internalSetString.ml", "internal_set.cppo.ml", dTypeString], ["belt_internalSetInt.ml", "internal_set.cppo.ml", dTypeInt], ["js_typed_array.ml", "js_typed_array.cppo.ml", ""], ["js_typed_array2.ml", "js_typed_array2.cppo.ml", ""], ])} ${mllRule} ${mllList("ext", ["ext_json_parse.mll"])} ${mllList("ml", ["lexer.mll"])} rule copy command = cp $in $out description = $in -> $out ${buildNapkinFiles} ${buildNapkinCliFiles} `; var cppoNinjaFile = "cppoVendor.ninja"; writeFileSync(path.join(jscompDir, cppoNinjaFile), cppoNative); cp.execFileSync(vendorNinjaPath, ["-f", cppoNinjaFile, "--verbose", "-v"], { cwd: jscompDir, stdio: [0, 1, 2], encoding: "utf8", }); } var sourceDirs = [ "bsb", "bsb_helper", "common", "core", "depends", "frontend", "gentype", "js_parser", "ext", "main", "ml", "ounit", "ounit_tests", "outcome_printer", "napkin", "stubs", "super_errors", ]; /** * * @param {string[]} dirs */ function makeLibs(dirs) { return dirs.map(x => `${x}/${x}.cmxa`).join(" "); } var compiler_libs = ["ml"]; var bsc_libs = [ "stubs", "ext", ...compiler_libs, "js_parser", "napkin", "common", "frontend", "depends", "gentype", "super_errors", "outcome_printer", "core", ]; var bsb_helper_libs = ["stubs", "ext", "common", "bsb_helper"]; var rescript_libs = ["stubs", "ext", "common", "bsb"]; var cmjdumps_libs = [ "stubs", ...compiler_libs, "ext", "common", "frontend", "depends", "core", ]; var cmij_libs = [ "stubs", "ext", ...compiler_libs, "common", "frontend", "depends", "core", ]; var tests_libs = [ "stubs", "ext", ...compiler_libs, "ounit", "common", "frontend", "depends", "bsb", "bsb_helper", "core", "ounit_tests", ]; /** * Note don't run `ninja -t clean -g` * Since it will remove generated ml file which has * an effect on depfile */ function nativeNinja() { var ninjaOutput = "compiler.ninja"; var includes = sourceDirs.map(x => `-I ${x}`).join(" "); var flags = "-w A-4-9-40..42-30-48-50-44-45"; var minor_version = +getVersionString().split(".")[1]; if (minor_version > 12) { flags += "-69-70"; // we should turn -69 on except for vendored files } if (minor_version > 7) { flags += "-3-67 -error-style short"; } var templateNative = ` ocamlopt = ocamlopt.opt ocamllex = ocamllex.opt ocamlc = ocamlc.opt ocamlmklib = ocamlmklib ocaml = ocaml ocamlyacc = ocamlyacc subninja cppoVendor.ninja rule optc command = $ocamlopt -bin-annot -strict-sequence -safe-string -opaque ${includes} -g -linscan ${flags} -warn-error A -absname -c $in description = $out : $in rule archive command = $ocamlopt -a $in -o $out description = arcive -> $out rule link command = $ocamlopt -g $flags $libs $in -o $out description = linking -> $out rule mk_bsversion command = node $in generator = true rule gcc command = $ocamlopt -ccopt -fPIC -ccopt -O2 -ccopt -o -ccopt $out -c $in o stubs/ext_basic_hash_stubs.o : gcc stubs/ext_basic_hash_stubs.c rule ocamlmklib command = $ocamlmklib -v $in -o $name && touch $out rule mk_keywords command = $ocaml $in generator = true o ext/js_reserved_map.ml: mk_keywords ../scripts/build_sorted.ml keywords.list o stubs/libbs_hash.a stubs/dllbs_hash.so: ocamlmklib stubs/ext_basic_hash_stubs.o name = stubs/bs_hash rule stubslib command = $ocamlopt -a $ml -o $out -cclib $clib o stubs/stubs.cmxa : stubslib stubs/bs_hash_stubs.cmx stubs/libbs_hash.a ml = stubs/bs_hash_stubs.cmx clib = stubs/libbs_hash.a o ${my_target}/bsc.exe: link ${makeLibs( bsc_libs )} main/rescript_compiler_main.cmx libs = unix.cmxa str.cmxa o ${my_target}/rescript.exe: link ${makeLibs( rescript_libs )} main/rescript_main.cmx libs = unix.cmxa str.cmxa o ${my_target}/bsb_helper.exe: link ${makeLibs( bsb_helper_libs )} main/bsb_helper_main.cmx libs = unix.cmxa str.cmxa o ./bin/bspack.exe: link ./main/bspack_main.cmx libs = unix.cmxa flags = -I ./bin -w -40-30-50 o ./bin/cmjdump.exe: link ${makeLibs(cmjdumps_libs)} main/cmjdump_main.cmx o ./bin/cmij.exe: link ${makeLibs(cmij_libs)} main/cmij_main.cmx o ./bin/tests.exe: link ${makeLibs(tests_libs)} main/ounit_tests_main.cmx libs = str.cmxa unix.cmxa build native: phony ${my_target}/bsc.exe ${my_target}/rescript.exe ${my_target}/bsb_helper.exe ./bin/bspack.exe ./bin/cmjdump.exe ./bin/cmij.exe ./bin/tests.exe ${mllRule} ${mlyRule} ${mlyList("ml", ["parser.mly"])} `; /** * @type { {name : string, libs: string[]}[]} */ var libs = []; sourceDirs.forEach(name => { if (name !== "main" && name !== "stubs") { libs.push({ name, libs: [] }); } }); /** * @type{string[]} */ var files = []; for (let dir of sourceDirs) { files = files.concat(test(dir)); } cp.exec( `ocamldep.opt -allow-approx -one-line -native ${includes} ${files.join( " " )}`, { cwd: jscompDir, encoding: "ascii" }, function (error, out) { if (error !== null) { throw error; } /** * @type {Map<string,Set<string>>} */ var map = new Map(); var pairs = out.split("\n").map(x => x.split(":").map(x => x.trim())); pairs.forEach(pair => { /** * @type {string[]|string} */ var deps; var key = pair[0]; if (pair[1] !== undefined && (deps = pair[1].trim())) { // deps = deps.replace(/.cmx/g, ".cmi"); deps = deps.split(" "); map.set(key, new Set(deps)); } if (key.endsWith("cmx")) { libs.forEach(x => { if (path.dirname(key) === x.name) { x.libs.push(key); } }); } }); // not ocamldep output // when no mli exists no deps for cmi otherwise add cmi var stmts = pairs.map(pair => { if (pair[0]) { var target = pair[0]; var y = path.parse(target); /** * @type {Set<string>} */ var deps = map.get(target) || new Set(); if (y.ext === ".cmx") { var intf = path.join(y.dir, y.name + ".cmi"); var ml = path.join(y.dir, y.name + ".ml"); return `o ${ deps.has(intf) ? target : [target, intf].join(" ") } : optc ${ml} | ${setSortedToStringAsNativeDeps(deps)}`; } else { // === 'cmi' var mli = path.join(y.dir, y.name + ".mli"); return `o ${target} : optc ${mli} | ${setSortedToStringAsNativeDeps( deps )}`; } } }); libs.forEach(x => { var output = sortFilesByDeps(x.libs, map); var name = x.name; stmts.push(`o ${name}/${name}.cmxa : archive ${output.join(" ")}`); }); writeFileAscii( path.join(jscompDir, ninjaOutput), templateNative + stmts.join("\n") + "\n" ); } ); } function main() { var emptyCount = 2; var isPlayground = false; if (require.main === module) { if (process.argv.includes("-check")) { checkEffect(); } if (process.argv.includes("-playground")) { isPlayground = true; emptyCount++; } var subcommand = process.argv[2]; switch (subcommand) { case "build": try { cp.execFileSync(vendorNinjaPath, ["native", "all"], { encoding: "utf8", cwd: jscompDir, stdio: [0, 1, 2], }); if (!isPlayground) { cp.execFileSync( path.join(__dirname, "..", "jscomp", "bin", "cmij.exe"), { encoding: "utf8", cwd: jscompDir, stdio: [0, 1, 2], } ); } cp.execFileSync(vendorNinjaPath, ["-f", "snapshot.ninja"], { encoding: "utf8", cwd: jscompDir, stdio: [0, 1, 2], }); } catch (e) { console.log(e.message); console.log(`please run "./scripts/ninja.js config" first`); process.exit(2); } cp.execSync(`node ${__filename} config`, { cwd: __dirname, stdio: [0, 1, 2], }); break; case "clean": try { cp.execFileSync(vendorNinjaPath, ["-t", "clean"], { encoding: "utf8", cwd: jscompDir, stdio: [0, 1], }); } catch (e) {} cp.execSync( `git clean -dfx jscomp ${my_target} lib && rm -rf lib/js/*.js && rm -rf lib/es6/*.js`, { encoding: "utf8", cwd: path.join(__dirname, ".."), stdio: [0, 1, 2], } ); break; case "config": console.log(`config for the first time may take a while`); updateDev(); updateRelease(); break; case "cleanbuild": console.log(`run cleaning first`); cp.execSync(`node ${__filename} clean`, { cwd: __dirname, stdio: [0, 1, 2], }); cp.execSync(`node ${__filename} config`, { cwd: __dirname, stdio: [0, 1, 2], }); cp.execSync(`node ${__filename} build`, { cwd: __dirname, stdio: [0, 1, 2], }); break; case "help": console.log(`supported subcommands: [exe] config [exe] build [exe] cleanbuild [exe] help [exe] clean `); break; default: if (process.argv.length === emptyCount) { updateDev(); updateRelease(); } else { var dev = process.argv.includes("-dev"); var release = process.argv.includes("-release"); var all = process.argv.includes("-all"); if (all) { updateDev(); updateRelease(); } else if (dev) { updateDev(); } else if (release) { updateRelease(); } } break; } } } main();