UNPKG

bs-platform

Version:

bucklescript compiler, ocaml standard libary by bucklescript and its required runtime support

337 lines (304 loc) 10 kB
//@ts-check // For Windows, we distribute a prebuilt bsc.exe // To build on windows, we still need figure out constructing config.ml // from existing compiler // For other OSes, we detect // if there is other compiler installed and the version matches, // we get the config.ml from existing OCaml compiler and build whole_compiler // Otherwise, we build the compiler shipped with Buckle and use the // old compiler.ml // This will be run in npm postinstall, don't use too fancy features here var cp = require('child_process') var fs = require('fs') var path = require('path') var root_dir = path.join(__dirname, '..') var lib_dir = path.join(root_dir, 'lib') var jscomp_dir = path.join(root_dir, 'jscomp') var runtime_dir = path.join(jscomp_dir,'runtime') var others_dir = path.join(jscomp_dir,'others') var ocaml_dir = path.join(lib_dir,'ocaml') var config = require('./config.js') var is_windows = config.is_windows var sys_extension = config.sys_extension process.env.BS_RELEASE_BUILD = 'true' var ocamlVersion = require('./buildocaml.js').getVersionPrefix() var stdlib_dir = path.join(jscomp_dir, ocamlVersion.includes('4.02') ? 'stdlib-402' : 'stdlib-406') // Add vendor bin path // So that second try will work process.env.PATH = path.join(__dirname, '..', 'native',ocamlVersion,'bin') + path.delimiter + process.env.PATH var ninja_bin_output = path.join(root_dir, 'lib', 'ninja.exe') /** * Make sure `ninja_bin_output` exists * The installation of `ninja.exe` is re-entrant, since we always pre-check if it is already installed * This is less problematic since `ninja.exe` is very stable */ function provideNinja() { var vendor_ninja_version = '1.8.2' var ninja_source_dir = path.join(root_dir, 'vendor', 'ninja') function build_ninja() { console.log('No prebuilt Ninja, building Ninja now') var build_ninja_command = "./configure.py --bootstrap" cp.execSync(build_ninja_command, { cwd: ninja_source_dir, stdio: [0, 1, 2] }) fs.renameSync(path.join(ninja_source_dir, 'ninja'), ninja_bin_output) console.log('ninja binary is ready: ', ninja_bin_output) } // sanity check to make sure the binary actually runs. Used for Linux. Too many variants /** * * @param {string} binary_path */ function test_ninja_compatible(binary_path) { var version; try { version = cp.execSync(JSON.stringify(binary_path) + ' --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] // execSync outputs to stdout even if we catch the error. Silent it here }).trim(); } catch (e) { console.log('ninja not compatible?', String(e)) return false; } return version === vendor_ninja_version; }; var ninja_os_path = path.join(ninja_source_dir,'snapshot', 'ninja' + sys_extension) if (fs.existsSync(ninja_bin_output) && test_ninja_compatible(ninja_bin_output)) { console.log("ninja binary is already cached and installed: ", ninja_bin_output) } else if (fs.existsSync(ninja_os_path)) { if(fs.copyFileSync){ // ninja binary size is small fs.copyFileSync(ninja_os_path,ninja_bin_output) } else { fs.renameSync(ninja_os_path, ninja_bin_output) } if (test_ninja_compatible(ninja_bin_output)) { console.log("ninja binary is copied from pre-distribution") } else { build_ninja() } } else { build_ninja() } } /** * * @param {NodeJS.ErrnoException} err */ function throwWhenError(err){ if(err!==null){ throw err } } /** * * @param {string} file * @param {string} target */ function poorCopyFile(file, target) { var stat = fs.statSync(file) fs.createReadStream(file).pipe( fs.createWriteStream(target, { mode: stat.mode })) } /** * @type {(x:string,y:string)=>void} * */ var installTrytoCopy; if(fs.copyFile !== undefined){ installTrytoCopy = function(x,y){ fs.copyFile(x,y,throwWhenError) } } else if(is_windows){ installTrytoCopy = function(x,y){ fs.rename(x,y,throwWhenError) } } else { installTrytoCopy = function(x,y){ poorCopyFile(x,y) } } /** * * @param {string} src * @param {(file:string)=>boolean} filter * @param {string} dest */ function installDirBy(src,dest,filter){ fs.readdir(src,function(err,files){ if( err === null) { files.forEach(function(file){ if(filter(file)){ var x = path.join(src,file) var y = path.join(dest,file) // console.log(x, '----->', y ) installTrytoCopy(x,y) } }) } else { throw err } }) } function install(){ if (!fs.existsSync(lib_dir)) { fs.mkdirSync(lib_dir) } if (!fs.existsSync(ocaml_dir)) { fs.mkdirSync(ocaml_dir) } installDirBy(runtime_dir,ocaml_dir,function(file){ var y = path.parse(file) return y.name === 'js' || y.ext.includes('cm') }) installDirBy(others_dir,ocaml_dir,function(file){ var y = path.parse(file) return y.ext === '.ml' || y.ext === '.mli' || y.ext.includes('cm') }) installDirBy(stdlib_dir,ocaml_dir,function(file){ var y = path.parse(file) return y.ext === '.ml' || y.ext === '.mli' || y.ext.includes('cm') }) } /** * raise an exception if not matched */ function matchedCompilerExn() { var output = cp.execSync('ocamlc.opt -v', { encoding: 'ascii' }) if (output.indexOf(ocamlVersion) >= 0) { console.log(output) console.log("Use the compiler above") } else { console.log('version', output, 'needed version', ocamlVersion, "No matched compiler found, may re-try") throw "" } } function tryToProvideOCamlCompiler() { try { if (process.env.BS_ALWAYS_BUILD_YOUR_COMPILER) { throw 'FORCED TO REBUILD' } matchedCompilerExn() } catch (e) { console.log('Build a local version of OCaml compiler, it may take a couple of minutes') try { require('./buildocaml.js').build() } catch (e) { console.log(e.stdout.toString()); console.log(e.stderr.toString()); console.log('Building a local version of the OCaml compiler failed, check the output above for more information. A possible problem is that you don\'t have a compiler installed.'); throw e; } console.log('configure again with local ocaml installed') matchedCompilerExn() console.log("config finished") } } /** * * @param {string} sys_extension * */ function createCopyNinja(sys_extension){ var output = '' switch(sys_extension){ case '.win32': output += ` rule cp command = cmd /q /c copy $in $out 1>nul ` break default: output += ` rule cp command = cp $in $out ` break } output += [ 'bsc','bsb','bsb_helper','bsppx', 'refmt', 'reactjs_jsx_ppx_2', 'reactjs_jsx_ppx_3' ].map(function(x){ return `build ${x}.exe: cp ${x}${sys_extension}` }).join('\n') output += '\n' return output } function copyPrebuiltCompilers() { var filePath = path.join(lib_dir,'copy.ninja') fs.writeFileSync(filePath,createCopyNinja(sys_extension),'ascii') cp.execFileSync(ninja_bin_output, ["-f", 'copy.ninja'], { cwd: lib_dir, stdio: [0, 1, 2] }) fs.unlinkSync(filePath) } /** * @returns {boolean} */ function checkPrebuiltBscCompiler() { try { var version = cp.execFileSync(path.join(lib_dir, 'bsc' + sys_extension), ['-v']) console.log("checkoutput:", String(version)) console.log("Prebuilt compiler works good") return true } catch (e) { console.log("No working prebuilt buckleScript compiler") return false } } function buildLibs(){ var releaseNinja = ` stdlib = ${ocamlVersion.includes('4.06')? 'stdlib-406' : 'stdlib-402'} subninja runtime/release.ninja subninja others/release.ninja subninja $stdlib/release.ninja ${process.env.BS_TRAVIS_CI ? 'subninja test/build.ninja\n' : '\n'} build all: phony runtime others $stdlib ` var filePath = path.join(jscomp_dir,'release.ninja') fs.writeFileSync(filePath,releaseNinja,'ascii') cp.execFileSync(ninja_bin_output, [ "-f", "release.ninja", "-t", "clean"], { cwd: jscomp_dir, stdio: [0, 1, 2] , shell: false}) cp.execFileSync(ninja_bin_output, [ "-f", "release.ninja"], { cwd: jscomp_dir, stdio: [0, 1, 2] , shell: false}) fs.unlinkSync(filePath) console.log('Build finished') } function provideCompiler() { // FIXME: weird logic // if (fs.existsSync(path.join(lib_dir,'ocaml','pervasives.cmi'))) { // console.log('Found pervasives.cmi, assume it was already built') // return true // already built before // } if (checkPrebuiltBscCompiler()) { copyPrebuiltCompilers() } else { // when not having bsc.exe tryToProvideOCamlCompiler() // Note this ninja file only works under *nix due to the suffix // under windows require '.exe' var releaseNinja = ` ocamlopt = ocamlopt.opt ext = .exe INCL= ${require('./buildocaml.js').getVersionPrefix()} include body.ninja ` var filePath = path.join(lib_dir,'release.ninja') fs.writeFileSync(filePath,releaseNinja,'ascii') cp.execFileSync(ninja_bin_output, ['-f', 'release.ninja'], { cwd: lib_dir, stdio: [0, 1, 2] }) fs.unlinkSync(filePath) } } provideNinja() if(is_windows){ copyPrebuiltCompilers() } else{ provideCompiler() } buildLibs() install()