/* * command.jake * toolchain * * Copyright 2012 The Cappuccino Foundation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ const fs = require('fs'); const child_process = require('child_process'); const path = require('path'); const { stream } = ObjectiveJ.term; const utilsFile = ObjectiveJ.utils.file; requiresSudo = false; process.argv.slice(1).forEach(function(arg) { if (arg === "sudo-install") requiresSudo = true; }); // Record the initial process.env so we know which need to be serialized later. var envInitial = Object.freeze(JSON.parse(JSON.stringify(process.env))); // Set up development environment variables. process.env["BUILD_PATH"] = path.resolve( process.env["BUILD_PATH"] || process.env["CAPP_BUILD"] || // Global Cappuccino build directory. process.env["STEAM_BUILD"] || // Maintain backwards compatibility with steam. path.join(module.path, "Build") // Just build here. ); if (!process.env["CAPP_BUILD"] && process.env["STEAM_BUILD"]) console.error("STEAM_BUILD environment variable is deprecated; Please use CAPP_BUILD instead."); if (!process.env["CONFIG"]) process.env["CONFIG"] = "Release"; global.ENV = process.env; global.task = JAKE.task; global.directory = JAKE.directory; global.file = JAKE.file; global.filedir = JAKE.filedir; global.FileList = JAKE.FileList; global.$CONFIGURATION = process.env['CONFIG']; global.$INLINE_MSG_SEND = process.env['INLINE_MSG_SEND']; global.$BUILD_DIR = process.env['BUILD_PATH']; global.$BUILD_CONFIGURATION_DIR = path.join($BUILD_DIR, $CONFIGURATION); global.$BUILD_CJS_OBJECTIVE_J = path.join($BUILD_CONFIGURATION_DIR, "CommonJS", "objective-j"); global.$BUILD_CJS_CAPPUCCINO = path.join($BUILD_CONFIGURATION_DIR, "CommonJS", "cappuccino"); global.$BUILD_CJS_CAPPUCCINO_BIN = path.join($BUILD_CJS_CAPPUCCINO, "bin"); global.$BUILD_CJS_CAPPUCCINO_LIB = path.join($BUILD_CJS_CAPPUCCINO, "lib"); global.$BUILD_CJS_CAPPUCCINO_FRAMEWORKS = path.join($BUILD_CJS_CAPPUCCINO, "Frameworks"); global.OBJJ_INCLUDE_PATHS.push(global.$BUILD_CJS_CAPPUCCINO_FRAMEWORKS); global.CLEAN = JAKE.CLEAN_AND_CLOBBER.CLEAN; global.CLOBBER = JAKE.CLEAN_AND_CLOBBER.CLOBBER; global.CLEAN.include(path.join(global.$BUILD_DIR, "*.build")); global.CLOBBER.include(global.$BUILD_DIR); global.$HOME_DIR = path.resolve(path.dirname(module.path)); global.$LICENSE_FILE = path.resolve(path.join(path.dirname(module.path), 'LICENSE')); global.FIXME_fileDependency = function(destinationPath, sourcePath) { file(destinationPath, [sourcePath], function() { var time = new Date(); try { fs.utimesSync(destinationPath, time, time); } catch (err) { fs.closeSync(fs.openSync(destinationPath, 'w')); } }); }; // logic to determine which packages should be loaded but are not. // used in serializedENV() function additionalPackages() { var builtObjectiveJPackage = path.join(path.resolve($BUILD_CONFIGURATION_DIR), "CommonJS", "objective-j"); var builtCappuccinoPackage = path.join(path.resolve($BUILD_CONFIGURATION_DIR), "CommonJS", "cappuccino"); var packages = []; // load built objective-j if exists, otherwise unbuilt if (fs.existsSync(path.join(builtObjectiveJPackage, "package.json"))) { //if (!packageInCatalog(builtObjectiveJPackage)) packages.push(builtObjectiveJPackage); } // load built cappuccino if it exists if (fs.existsSync(path.join(builtCappuccinoPackage, "package.json"))) { //if (!packageInCatalog(builtCappuccinoPackage)) packages.push(builtCappuccinoPackage); } return packages; } // checks to see if a path is in the package catalog function packageInCatalog(path) { var catalog = require("narwhal/packages").catalog; for (var name in catalog) if (String(catalog[name].directory) === String(path)) return true; return false; } serializedENV = function() { var envNew = {}; // add changed keys to the new ENV Object.keys(process.env).forEach(function(key) { if (process.env[key] !== envInitial[key]) envNew[key] = process.env[key]; }); // pseudo-HACK: add NARWHALOPT with packages we should ensure are loaded var packages = additionalPackages(); if (packages.length) { envNew["OBJJ_OPT"] = packages.map(function(p) { return "-I " + utilsFile.enquote(path.join(p, "Frameworks")); }).join(" "); envNew["PATH"] = packages.map(function(p) { return path.join(p, "bin"); }).concat(process.env["PATH"]).join(":"); } return Object.keys(envNew).map(function(key) { return key + "=" + utilsFile.enquote(envNew[key]); }).join(" "); }; function getShellConfigFile() { var homeDir = process.env["HOME"] + "/"; // use order outlined by http://hayne.net/MacDev/Notes/unixFAQ.html#shellStartup var possibilities = [homeDir + ".bash_profile", homeDir + ".bash_login", homeDir + ".profile", homeDir + ".bashrc"]; for (var i = 0; i < possibilities.length; i++) { if (fs.existsSync(possibilities[i])) return possibilities[i]; } } function reforkWithPackages() { console.log("in reforkWithPackages"); if (additionalPackages().length > 0) { var cmd = serializedENV() + " " + process.argv.map(enquote).join(" "); console.log("cmd: " + cmd); child_process.execSync(cmd, {stdio: 'inherit'}); process.exit(); } } // reforkWithPackages(); function handleSetupEnvironmentError(e) { if (String(e).indexOf("require error") == -1) { console.log("setupEnvironment warning: " + e); //throw e; } } function setupEnvironment() { try { ObjectiveJ.OBJJ_INCLUDE_PATHS.push(path.join($BUILD_CONFIGURATION_DIR, "CommonJS", "cappuccino", "Frameworks")); } catch (e) { handleSetupEnvironmentError(e); } } setupEnvironment(); global.rm_rf = function(/*String*/ aFilename) { try { fs.rmSync(aFilename, {recursive: true, force: true}); } catch (anException) { } }; function systemSync(command) { //console.log("command: " + command) try { child_process.execSync(command, {stdio: 'inherit'}); return 0; } catch (error) { //console.log(error); //console.log(error.output.toString()); return error.status; } } global.subjake = function(/*Array*/ directories, /*String*/ aTaskName) { if (!Array.isArray(directories)) directories = [directories]; directories.forEach(function(/*String*/ aDirectory) { if (fs.lstatSync(aDirectory).isDirectory() && fs.lstatSync(path.join(aDirectory, "Jakefile")).isFile()) { var cmd = "cd " + utilsFile.enquote(aDirectory) + " && " + serializedENV() + " " + "jake" + " " + utilsFile.enquote(aTaskName); var returnCode = systemSync(cmd); if (returnCode) { process.exit(returnCode); } } else print("warning: subjake missing: " + aDirectory + " (this is not necessarily an error, " + aDirectory + " may be optional)"); }); }; global.executableExists = function(/*String*/ executableName) { var paths = process.env["PATH"].split(':'); for (var i = 0; i < paths.length; i++) { path.join(paths[i], executableName); var p = path.join(paths[i], executableName); if (fs.existsSync(p)) return p } return null; }; $OBJJ_TEMPLATE_EXECUTABLE = path.join(__dirname, "Objective-J", "CommonJS", "objj-executable"); global.make_objj_executable = function(aPath) { utilsFile.cp($OBJJ_TEMPLATE_EXECUTABLE, aPath); fs.chmodSync(aPath, 0o755); }; global.symlink_executable = function(source) { rel = path.relative($ENVIRONMENT_NARWHAL_BIN_DIR, source); dest = path.join($ENVIRONMENT_NARWHAL_BIN_DIR, path.basename(source)); fs.symlinkSync(rel, dest); }; global.getCappuccinoVersion = function() { var versionFile = path.join(module.path, "package.json"); return JSON.parse(fs.readFileSync(versionFile, { encoding: "utf8" })).version; }; global.setPackageMetadata = function(packagePath) { var pkg = JSON.parse(fs.readFileSync(packagePath, { encoding: "utf8" } )); try { var output = child_process.execSync("git rev-parse --verify HEAD"); var sha = output.toString().split("\n")[0]; if (sha.length === 40) pkg["cappuccino-revision"] = sha; } catch (error) { console.log(error.output.toString()); console.log("setPackageMetadata error " + error.status); } pkg["cappuccino-timestamp"] = new Date().getTime(); pkg["version"] = getCappuccinoVersion(); stream.print(" Version: \0purple(" + pkg["version"] + "\0)"); stream.print(" Revision: \0purple(" + pkg["cappuccino-revision"] + "\0)"); stream.print(" Timestamp: \0purple(" + pkg["cappuccino-timestamp"] + "\0)"); fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 4), 'utf8'); }; global.subtasks = function(subprojects, taskNames) { taskNames.forEach(function(aTaskName) { var subtaskName = aTaskName + "_subprojects"; task (aTaskName, [subtaskName]); task (subtaskName, function() { subjake(subprojects, aTaskName); }); }); }; global.installSymlink = function(sourcePath) { if (!fs.lstatSync(sourcePath).isDirectory()) return; var packageName = path.basename(sourcePath), targetPath = path.join(SYSTEM.prefix, "packages", packageName); if (FILE.isDirectory(targetPath)) FILE.rmtree(targetPath); else if (FILE.linkExists(targetPath)) FILE.remove(targetPath); stream.print("Symlinking \0cyan(" + targetPath + "\0) ==> \0cyan(" + sourcePath + "\0)"); FILE.symlink(sourcePath, targetPath); var binPath = FILE.Path(FILE.join(targetPath, "bin")); if (binPath.isDirectory()) { var narwhalBin = FILE.Path(FILE.join(SYSTEM.prefix, "bin")); binPath.list().forEach(function (name) { var binary = binPath.join(name); binary.chmod(0755); var target = narwhalBin.join(name), relative = FILE.relative(target, binary); if (target.linkExists()) target.remove(); FILE.symlink(relative, target); }); } }; global.installCopy = function(sourcePath, useSudo) { if (!fs.existsSync(sourcePath)) return; var packageName = path.basename(sourcePath), targetPath = path.join(__dirname, "dist", packageName), binPath = path.resolve(path.join(targetPath, "bin")); // create the dist directory if it does not exist if (!fs.existsSync(targetPath)) { fs.mkdirSync(targetPath, { recursive: true }); } if (fs.lstatSync(targetPath).isDirectory()) fs.rmSync(targetPath, {recursive: true, force: true}); else if (fs.fstatSync(targetPath).isSymbolicLink() || fs.existsSync(targetPath)) fs.rmSync(targetPath); stream.print("Copying \0cyan(" + sourcePath + "\0) ==> \0cyan(" + targetPath + "\0)"); // hacky way to do a sudo copy. if (useSudo) child_process.execSync(["sudo", "cp", "-Rf", sourcePath, path.dirname(targetPath)].map(utilsFile.enquote).join(" ")); else utilsFile.copyRecursiveSync(sourcePath, targetPath); // create the bin directory if it does not exist if (!fs.existsSync(binPath)) { fs.mkdirSync(targetPath) } if (fs.lstatSync(binPath).isDirectory()) { fs.readdirSync(binPath).forEach(function (name) { var binary = path.join(binPath, name); if (useSudo) { child_process.execSync(["sudo", "chmod", "755", binary].map(utilsFile.enquote).join(" ")); } else { fs.chmodSync(binary, 0o755); } }); } }; global.installGlobal = function(sourcePath, useSudo) { if (!fs.existsSync(sourcePath)) return; var packageName = path.basename(sourcePath), targetPath = path.join(__dirname, "dist", packageName), binPath = path.resolve(path.join(targetPath, "bin")); // create the dist directory if it does not exist if (!fs.existsSync(targetPath)) { fs.mkdirSync(targetPath, { recursive: true }); } var prefix = child_process.execSync("npm prefix -g").toString().trim(); fs.readdirSync(binPath).forEach(function (name) { var p = path.join(binPath, name), prefixBinPath = path.join(prefix, "bin", name); stream.print("Symlink \0cyan(" + prefixBinPath + "\0) ==> \0cyan(" + p + "\0)"); child_process.execSync( (useSudo ? "sudo " : "") + "ln -sf " + utilsFile.enquote(p) + " " + utilsFile.enquote(prefixBinPath)); }); } global.spawnJake = function(/*String*/ aTaskName) { if (systemSync(serializedENV() + " " + "jake" + " " + aTaskName)) { console.log("exited in spawnJake with error"); process.exit(1); //rake abort if ($? != 0) } }; var normalizeCommand = function(/*Array or String*/ command) { if (Array.isArray(command)) return command.map(utilsFile.enquote).join(" "); else return command; }; global.sudo = function(/*Array or String*/ command) { // First try without sudo command = normalizeCommand(command); var returnCode = systemSync(command + " >/dev/null 2>&1"); if (returnCode) { // if this is set, then disable the use of sudo. // This is very usefull for CI scripts and stuff like that if (process.env["CAPP_NOSUDO"] == 1) return returnCode; return systemSync("sudo -p '\nEnter your admin password: ' " + command); } return 0; }; global.exec = function(/*Array or String*/ command, quiet) { command = normalizeCommand(command) + (quiet === true ? " >/dev/null 2>&1" : ""); return systemSync(command); }; global.copyManPage = function(/*String*/ name, /*int*/ section) { var manDir = "/usr/local/share/man/man" + section, pageFile = name + "." + section, manPagePath = path.join(manDir, pageFile); if (!fs.existsSync(manPagePath) || fs.lstatSync(pageFile).mtime > fs.lstatSync(manPagePath).mtime) { if (!fs.lstatSync(manDir).isDirectory()) { if (sudo(["mkdir", "-p", "-m", "0755", manDir])) stream.print("\0red(Unable to create the man directory.\0)"); } if (sudo(["cp", "-f", pageFile, manDir])) stream.print("\0red(Unable to copy the man file.\0)"); } }; global.xcodebuildCanListSDKs = function() { return global.exec("xcodebuild -showsdks", true) === 0; }; global.xcodebuildHasTenPointFiveSDK = function() { if (xcodebuildCanListSDKs()) return global.exec("xcodebuild -showsdks | grep 'macosx10.5'", true) === 0; return fs.existsSync(path.join("/", "Developer", "SDKs", "MacOSX10.5.sdk")); }; global.colorize = function(/* String */ message, /* String */ color) { var matches = color.match(/(bold(?: |\+))?(.+)/); if (!matches) return; message = "\0" + matches[2] + "(" + message + "\0)"; if (matches[1]) message = "\0bold(" + message + "\0)"; return message; }; global.colorPrint = function(/* String */ message, /* String */ color) { stream.print(colorize(message, color)); }; // built in tasks task ("build"); task ("default", "build"); task ("release", function() { process.env["CONFIG"] = "Release"; spawnJake("build"); }); task ("debug", function() { process.env["CONFIG"] = "Debug"; spawnJake("build"); }); task ("all", ["debug", "release"]); task ("sudo-install-symlinks", function() { sudo("jake install-symlinks"); }); task ("sudo-install-debug-symlinks", function() { sudo("jake install-debug-symlinks"); }); task ("clean-debug", function() { process.env['CONFIG'] = 'Debug'; spawnJake("clean"); }); task ("cleandebug", ["clean-debug"]); task ("clean-release", function() { process.env["CONFIG"] = "Release"; spawnJake("clean"); }); task ("cleanrelease", ["clean-release"]); task ("clean-all", ["clean-debug", "clean-release"]); task ("cleanall", ["clean-all"]); task ("clobber-debug", function() { process.env["CONFIG"] = "Debug"; spawnJake("clobber"); }); task ("clobberdebug", ["clobber-debug"]); task ("clobber-release", function() { process.env["CONFIG"] = "Release"; spawnJake("clobber"); }); task ("clobberrelease", ['clobber-release']); task ("clobber-all", ["clobber-debug", "clobber-release"]); task ("clobberall", ["clobber-all"]);