UNPKG

rapidgame

Version:

A cross-platform commandline tool that prebuilds cocos2d-x libraries for Windows, Mac, Linux, Android and iOS. Also a game templating system.

1,743 lines (1,588 loc) 48.6 kB
// // Part of the [RapidGame](https://github.com/natweiss/rapidgame) project. // See the `LICENSE` file for the license governing this code. // Developed by Nathanael Weiss. // var http = require("http"), path = require("path-extra"), fs = require("fs"), os = require("os"), cmd = require("commander"), replace = require("replace"), glob = require("glob"), wrench = require("wrench"), child_process = require("child_process"), packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"))), cmdName = packageJson.name, version = packageJson.version, category, engines = [], templates = [], builds = [], orientations = ["landscape", "portrait"], platforms = ["headers", "ios", "mac", "android", "windows", "linux"], copyCount = 0, msBuildExePath, libExePath, vcTargetsPath, doJSB = false, doPhysics = false, doNavmesh = false, doWebp = false, defaults = { engine: "cocos2dx", template: "TwoScene", package: "org.mycompany.mygame", dest: process.cwd(), prefix: path.join(path.homedir(), ".rapidgame"), src: path.join(path.homedir(), ".rapidgame", "src", "cocos2d-x"), dest: path.join(path.homedir(), ".rapidgame", "lib"), orientation: orientations[0] }; // // list directories (path.join is called on all arguments) // var listDirectories = function() { var i, src, dirs; for (i = 0; i < arguments.length; i += 1) { src = (src ? path.join(src, arguments[i]) : arguments[i]); } dirs = glob.sync(src); for (i = 0; i < dirs.length; i++) { dirs[i] = path.basename(dirs[i]); } return dirs; }; // // get engines and templates // engines = listDirectories(__dirname, "templates", "*"); templates = listDirectories(__dirname, "templates", "cocos2dx", "*"); // // Main run method. // var run = function(args) { var i, commands = [], commandFound = false; //checkUpdate(); args = args || process.argv; cmd .version(version) .option("-s, --src <path>", "cocos2d-x source path [" + defaults.src + "]", defaults.src) .option("-d, --dest <name>", "destination libraries path [" + defaults.dest + "]", defaults.dest) .option("-p, --prefix <path>", "rapidgame home [" + defaults.prefix + "]", defaults.prefix) .option("-t, --template <name>", "template [" + defaults.template + "]", defaults.template) .option("-f, --folder <path>", "output folder of created project [" + defaults.dest + "]", defaults.dest) .option("-v, --verbose", "be verbose", false) .option("--minimal", "prebuild only debug libraries and use minimal architectures", false) //.option("--i386", "on iphonesimulator, build i386 instead of x86_64", false) .option("--nostrip", "do not strip the prebuilt libraries", false); cmd .command("show") .description(" Show where static libraries and headers reside") .action(showPrefix); commands.push("show"); cmd .command("prebuild <platform>") .description(" Prebuild cocos2d-x static libraries and headers") .action(prebuild); commands.push("prebuild"); cmd .command("clean") .description(" Clean the temporary build files") .action(clean); commands.push("clean"); cmd .command("init <directory>") .description(" Create a symlink named 'lib' to the static libraries") .action(init); commands.push("init"); cmd .command("create <engine> <project-name> <package-name>") .description(" Create a new cross-platform game project") .action(createProject); commands.push("create"); cmd.on("--help", usageExamples); cmd .parse(args) .name = cmdName; if (!cmd.args.length) { usage(); } else { // Check if command exists for (i = 0; i < commands.length; i += 1) { if (args[2] === commands[i]) { commandFound = true; break; } } if (!commandFound) { console.log("Command '" + args[2] + "' not found"); usage(); } } }; // // check that prefix directory is writeable // var checkPrefix = function() { return true; }; // // Resolve dirs. // var resolveDirs = function() { // Resolve prefix. if (cmd.prefix !== defaults.prefix) { cmd.prefix = path.resolve(cmd.prefix); } if (!isWriteableDir(cmd.prefix)) { // Complain if specified prefix dir. if (cmd.prefix !== defaults.prefix) { logErr("Cannot write files to prefix directory: " + cmd.prefix); return false; } // Make dir wrench.mkdirSyncRecursive(defaults.prefix); if (!isWriteableDir(defaults.prefix)) { logErr("Cannot write files to default prefix directory: " + defaults.prefix); return false; } // Success. cmd.prefix = defaults.prefix; if (cmd.verbose) { console.log("Can successfully write files to prefix directory: " + cmd.prefix); } } if (!dirExists(cmd.prefix)) { logBuild("Invalid prefix dir: " + cmd.prefix, true); return false; } // Resolve source. if (cmd.src !== defaults.src) { cmd.src = path.resolve(cmd.src); } if (!dirExists(cmd.src)) { if (cmd.src !== defaults.src) { logBuild("Invalid src dir: " + cmd.src, true); } else { logBuild("Please specify a source directory with the -s option.", true); //usage(); } return false; } // Resolve dest. if (cmd.dest !== defaults.dest) { cmd.dest = path.resolve(cmd.dest); } return true; }; // // Initialize the given directory. // var init = function(directory) { var src, dest; if (!resolveDirs()) {return 1;} if (!dirExists(directory)) { console.log("Output directory must exist: " + directory); return 1; } // Create lib symlink // Windows "EPERM: operation not permitted" probably means user needs to Run As Administrator // http://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights src = cmd.dest; dest = path.join(directory, "lib"); console.log("Symlinking" + (cmd.verbose ? ": " + dest + " -> " + src : " lib folder")); try { fs.symlinkSync(src, dest); } catch(e) { logErr("Error creating symlink"); if (process.platform === "win32") { logErr("\nPlease 'Run As Administrator'.\n"); } } }; // // Clean the temp build files. // var clean = function(directory) { var i, j, dest, files = [], globbed = [], configs = ["Debug", "Release"]; if (!resolveDirs()) {return 1;} // List dirs to clean. for (i = 0; i < configs.length; i += 1) { globbed = glob.sync(path.join(cmd.src, "build", configs[i] + "-*")); for (j = 0; j < globbed.length; j += 1) { files.push(globbed[j]); } } // Add Windows dirs to list. globbed = glob.sync(path.join(cmd.src, "**", "Debug.win32")); for (j = 0; j < globbed.length; j += 1) { files.push(globbed[j]); } globbed = glob.sync(path.join(cmd.src, "**", "Release.win32")); for (j = 0; j < globbed.length; j += 1) { files.push(globbed[j]); } // Remove dirs. for (i = 0; i < files.length; i += 1) { if (dirExists(files[i])) { console.log("Cleaning: " + files[i]); try { wrench.rmdirSyncRecursive(files[i], true); } catch(e) { logErr(e, cmd.verbose); } } } }; // // Show prefix. // var showPrefix = function(directory) { if (!resolveDirs()) {return 1;} console.log("Rapidgame lives here: " + cmd.prefix); console.log("Prebuilt headers and libs path: " + cmd.dest); console.log("Headers have been copied: " + (dirExists(path.join(cmd.dest, "cocos2d", "x", "include")) ? "YES" : "NO")); console.log("Libraries have been built: " + (dirExists(path.join(cmd.dest, "cocos2d", "x", "lib")) ? "YES" : "NO")); if (cmd.src != defaults.src) { console.log("src: " + cmd.src); } }; // // Create project. // var createProject = function(engine, name, package) { var dir = path.join(process.cwd(), name), src, dest, fileCount, i, onFinished, files, isCocos2d = false, packageSrc = "com.wizardfu." + cmd.template.toLowerCase(); cmd.engine = engine.toString().toLowerCase(); if (!resolveDirs()) {return 1;} category = "createProject"; // Check engine and name if (!cmd.engine || !name || !package) { console.log("Engine, project name and package name are required, for example: " + cmdName + " cocos2dx \"HeckYeah\" com.mycompany.heckyeah"); usage(); return 1; } // Check if dirs exist if (dirExists(dir) || fileExists(dir)) { console.log("Output directory already exists: " + dir); return 1; } // Check engine if (engines.indexOf(cmd.engine) < 0) { console.log("Engine '" + cmd.engine + "' not found"); console.log("Available engines are: " + engines.join(", ")); usage(); return 1; } // Check template src = path.join(__dirname, "templates", cmd.engine, cmd.template); if (!dirExists(src)) { console.log("Missing template directory: " + src); files = listDirectories(__dirname, "templates", cmd.engine, "*"); if (files.length > 0) { console.log("Available templates for " + cmd.engine + " are: " + files.join(", ") + "."); } usage(); return 1; } // Start logReport("start", cmd.engine + "/" + cmd.template); console.log("Rapidly creating a game"); console.log("Engine: " + cmd.engine.charAt(0).toUpperCase() + cmd.engine.slice(1)); console.log("Template: " + cmd.template + (cmd.verbose ? " " + packageSrc : "")); isCocos2d = (cmd.engine.indexOf("cocos") >= 0); // Copy all template files to destination dest = dir; console.log("Copying project files" + (cmd.verbose ? " from " + src + " to " + dest : "")); fileCount = copyRecursive(src, dest, true); if (cmd.verbose) { console.log("Successfully copied " + fileCount + " files"); } // Replace project name console.log("Setting project name: " + name); replace({ regex: cmd.template, replacement: name, paths: [dest], include: "*.js,*.plist,*.cpp,*.md,*.lua,*.html,*.json,*.xml,*.xib,*.pbxproj,*.xcscheme,*.xcworkspacedata,*.xccheckout,*.sh,*.cmd,*.py,*.rc,*.sln,*.txt,.classpath,.project,.cproject,makefile,manifest,*.vcxproj,*.user,*.filters,.name", recursive: true, silent: !cmd.verbose }); // Replace package name console.log("Setting package name: " + package); replace({ regex: packageSrc, replacement: package, paths: [dest], include: "*.js,*.plist,*.xml,makefile,manifest,*.settings,*.lua,.project,.identifier", recursive: true, silent: !cmd.verbose }); // Rename files & dirs from = path.join(dest, "**", cmd.template + ".*"); if (cmd.verbose) { console.log("Renaming all " + from + " files"); } files = glob.sync(from); for (i = 0; i < files.length; i++) { from = files[i]; to = path.join(path.dirname(from), path.basename(from).replace(cmd.template, name)); if (cmd.verbose) { console.log("Moving " + from + " to " + to); } try { fs.renameSync(from, to); } catch(e) { logErr("Error moving file " + e); } } // Symlink if (isCocos2d) { init(dir); } // Npm install i = null; dest = path.join(dir, "server"); onFinished = function(){ // Show readme console.log("Done"); try { var text = fs.readFileSync(path.join(dir, "README.md")).toString(); console.log(""); console.log(text); console.log(""); } catch(e) { } logReport("done"); // Auto prebuild if (isCocos2d && !dirExists(cmd.dest)) { console.log(""); console.log("Static libraries must be prebuilt"); prebuild(); } }; if (dirExists(dest) && !dirExists(path.join(dest, "node_modules"))) { console.log("Installing node modules"); try { child_process.exec("npm install", {cwd: dest, env: process.env}, function(a, b, c){ execCallback(a, b, c); onFinished(); }); } catch(e) { logErr("Error installing node modules: " + e); } } else { onFinished(); } }; // // run the prebuild command // var prebuild = function(platform, config, arch) { if (!resolveDirs()) {return 1;} category = "prebuild"; platform = (platform || ""); platform = platform.toString().toLowerCase(); config = config || ""; arch = arch || ""; if (platforms.indexOf(platform) < 0) { platform = ""; } // initialize build log cmd.buildLog = path.join(cmd.prefix, "build-" + process.platform + ".log"); try { fs.writeFileSync(cmd.buildLog, ""); logBuild("Happily prebuilding: " + platform, true); console.log("Writing build log to: " + cmd.buildLog); } catch(e) { cmd.buildLog = ""; } getToolPaths(function(success) { if (!success) { return; } logReport("start"); copySrcFiles(function() { setupPrebuild(platform, function() { runPrebuild(platform, config, arch, function() { logReport("done"); }); }); }); }); }; // // copy src directory to prefix // var copySrcFiles = function(callback) { var src = path.join(__dirname, "src"), dest = path.join(cmd.prefix, "src"); // Synchronously copy src directory to dest if (src !== dest && !dirExists(dest)) { logBuild("Copying " + src + " to " + dest, true); copyRecursive(src, dest, true); } callback(); }; // // prebuild setup (copies headers, java files, etc.) // var setupPrebuild = function(platform, callback) { var dir, src, dest, i, files; // Return if we are prebuilding a specific platform. if (platform && platform !== "headers") { logBuild("Not building headers", true); callback(); return; } // Return if we have already prebuilt headers. if (!platform && dirExists(path.join(cmd.dest, "cocos2d", "x", "java", "mk"))) { logBuild("Already built headers", true); callback(); return; } logBuild("Checking if symlinks can be created", true); // symlink ./latest -> ./version src = path.basename(cmd.dest); dest = path.join(cmd.prefix, "latest"); try { fs.unlinkSync(dest); } catch(e) { //logErr("Error deleting symlink: " + e); } try { fs.symlinkSync(src, dest); logErr("Ok"); } catch(e) { logErr("Error creating symlink " + dest + " => " + src); if (process.platform === "win32") { logErr("\nPlease 'Run As Administrator'. Proceeding with unpredictable results.\n"); } } logBuild("Copying header files...", true); // reset cocos2d dir dest = path.join(cmd.dest, "cocos2d"); files = ["html", path.join("x", "include"), path.join("x", "cmake"), path.join("x", "java"), path.join("x", "script")]; try { for (i = 0; i < files.length; i += 1) { src = path.join(dest, files[i]); logBuild("rm -r " + src, cmd.verbose); wrench.rmdirSyncRecursive(src, true); logBuild("mkdir " + src, cmd.verbose); wrench.mkdirSyncRecursive(src); } } catch(e) { logBuild("Error cleaning destination", cmd.verbose); logBuild(e, cmd.verbose); } // copy cmake files dest = path.join(cmd.dest, "cocos2d", "x", "cmake"); src = path.join(cmd.src, "cmake"); copyRecursive(src, dest, false, true); // copy cocos2d-html5 dest = path.join(cmd.dest, "cocos2d", "html"); src = path.join(cmd.src, "web"); copyRecursive(src, dest, false, true); // copy headers dir = dest = path.join(cmd.dest, "cocos2d", "x", "include"); src = cmd.src; copyGlobbed(src, dest, '*.h'); copyGlobbed(src, dest, '*.hpp'); copyGlobbed(src, dest, '*.msg'); copyGlobbed(src, dest, '*.inl'); // remove unneeded files = ["docs", "build", "tests", "samples", "templates", "tools", path.join("plugin", "samples"), path.join("plugin", "plugins"), path.join("extensions", "proj.win32")]; for (i = 0; i < files.length; i += 1) { wrench.rmdirSyncRecursive(path.join(dir, files[i]), true); } // jsb dest = path.join(cmd.dest, "cocos2d", "x", "script"); src = path.join(cmd.src, "cocos", "scripting", "js-bindings", "script"); copyGlobbed(src, dest, '*.js'); // java dir = path.join(cmd.dest, "cocos2d", "x", "java"); dest = path.join(dir, "cocos2d-x"); src = path.join(cmd.src, "cocos", "platform", "android", "java"); copyRecursive(src, dest); // mk dest = path.join(cmd.dest, "cocos2d", "x", "java", "mk"); copyGlobbed(cmd.src, dest, "*.mk"); // clean up files = ["proj.android", "cocos2d-js", path.join("cocos2d-x", "tools"), path.join("cocos2d-x", "templates"), path.join("cocos2d-x", "tests")]; for (i = 0; i < files.length; i += 1) { wrench.rmdirSyncRecursive(path.join(dest, files[i]), true); } files = glob.sync(path.join(dir, "*", "bin")); for (i = 0; i < files.length; i += 1) { wrench.rmdirSyncRecursive(files[i], true); } files = glob.sync(path.join(dir, "*", "gen")); for (i = 0; i < files.length; i += 1) { wrench.rmdirSyncRecursive(files[i], true); } // find ${dir} | xargs xattr -c >> ${logFile} 2>&1 callback(); }; // // run the prebuild command // var runPrebuild = function(platform, config, arch, callback) { config = ""; arch = ""; // check whether to prebuild javascript bindings try{ var configDest = path.join(cmd.dest, "cocos2d", "x", "include", "cocos", "base", "ccConfig.h"), ccConfig = fs.readFileSync(configDest).toString().trim(); doJSB = (ccConfig.indexOf("CC_ENABLE_SCRIPT_BINDING 1") >= 0); doPhysics = (ccConfig.indexOf("CC_USE_PHYSICS 1") >= 0); doNavmesh = (ccConfig.indexOf("CC_USE_NAVMESH 1") >= 0); doWebp = (ccConfig.indexOf("CC_USE_WEBP 1") >= 0); console.log("Prebuild options:"); console.log(" Javascript bindings: " + (doJSB ? "yes" : "no")); console.log(" Physics: " + (doPhysics ? "yes" : "no")); console.log(" WebP: " + (doWebp ? "yes" : "no")); } catch(e) { } if (platform === "headers") { callback(); } else if (platform === "mac") { prebuildMac("Mac", config, arch, callback); } else if (platform === "ios") { prebuildMac("iOS", config, arch, callback); } else if (platform === "linux") { prebuildLinux("Linux", config, arch, callback); } else if (platform === "windows") { prebuildWin("Windows", config, arch, callback); } else if (platform === "android") { prebuildAndroid("Android", config, arch, callback); } else { prebuildMac("Mac", config, arch, function(){ prebuildMac("iOS", config, arch, function(){ prebuildLinux("Linux", config, arch, function(){ prebuildWin("Windows", config, arch, function(){ prebuildAndroid("Android", config, arch, function(){ callback(); }); }); }); }); }); } }; // // launch the next build // var nextBuild = function(platform, callback){ // Show remaining builds. if (cmd.verbose) { logBuild("Remaining builds: ", true); for (i = 0; i < builds.length; i+=1) { logBuild(" Config: " + builds[i][0] + "\n Dir: " + builds[i][2] + "\n Command: " + builds[i][1] + " " + builds[i][3].join(" ") + "\n", true); } } // Launch this build. if (builds.length) { startBuild(platform, callback, builds.shift()); } else { callback(); } }; // // start a given build // var startBuild = function(platform, callback, settings) { var i, config = settings[0], command = settings[1], dir = settings[2], args = settings[3], func = settings[4], funcArg = settings[5]; logBuild("Building: " + platform + (config ? " " + config : "") + (command ? " " + (cmd.verbose ? command : path.basename(command)) : "") + ((cmd.verbose && dir) ? " " + dir : ""), true); spawn(command, args, {cwd: path.resolve(dir), env: process.env}, function(err){ var onFinished = function(){ logBuild("Succeeded.", true); nextBuild(platform, callback); }; if (!err){ // Run callback. if (typeof func === "function") { func(funcArg, onFinished); } else { onFinished(); } } else { // Failed. if (!cmd.verbose) { console.log("Build failed. Please run with --verbose or check the build log: " + cmd.buildLog); } } }); }; // // prebuild mac // var prebuildMac = function(platform, config, arch, callback) { var i, j, k, dir, project, func, funcArg, derivedDir, dest, sdks = (platform === "Mac" ? ["macosx"] : ["iphoneos", "iphonesimulator"]), configs = (config ? [config] : (cmd.minimal ? ["Debug"] : ["Debug", "Release"])), projs = [ path.join(cmd.src, "build", "cocos2d_libs.xcodeproj") ]; if (doJSB !== false) { projs.push(path.join(cmd.src, "cocos", "scripting", "js-bindings", "proj.ios_mac", "cocos2d_js_bindings.xcodeproj")); } // Bail if not on Mac. if (process.platform !== "darwin") { logBuild("Can only build " + platform + " on Mac", true); callback(); return; } // Create builds array. for (i = 0; i < configs.length; i += 1) { for (j = 0; j < sdks.length; j += 1) { for (k = 0; k < projs.length; k += 1) { command = "xcodebuild"; dir = path.dirname(projs[k]); derivedDir = path.join(cmd.src, "build", configs[i] + "-" + sdks[j]); args = [ "-project", path.basename(projs[k]), "-configuration", configs[i], "-sdk", sdks[j], "-derivedDataPath", path.resolve(derivedDir) ]; if (k == 0) { // first proj is libcocos2d //"-scheme", "\"libcocos2d " + platform + "\"", // this doesn't spawn correctly args = args.concat(["-scheme", "libcocos2d " + platform]); } else { // second proj is libjscocos2d args = args.concat(["-scheme", "libjscocos2d " + platform]); } if (sdks[j] === "iphoneos") { if (cmd.minimal) { args = args.concat(["-arch", "armv7"]); } /*else { args = args.concat([ "-destination", "platform=iOS,name=iPhone 6s", "-destination-timeout", "5" ]); }*/ } else if (sdks[j] === "iphonesimulator") { // des it need "generic/" in the destination? is that why it doesn't run correctly on 5s? args = args.concat([ "-destination", "platform=iphonesimulator,name=iPhone 6s", "-destination-timeout", "5" ]); /*if (cmd.i386) { args = args.concat(["-arch", "i386"]); } else { // why doesn't this make iphonesimulator libs have both i386 and x86_64? is one being stripped away? //args = args.concat(["-arch", "x86_64", "-arch", "i386"]); args = args.concat(["-arch", "x86_64"]); }*/ } // final bit of command (xcode settings) args.push("GCC_SYMBOLS_PRIVATE_EXTERN=NO"); args.push("OTHER_CPLUSPLUSFLAGS=-w"); if (!cmd.nostrip) { args.push("DEPLOYMENT_POSTPROCESSING=YES"); args.push("STRIP_INSTALLED_PRODUCT=YES"); args.push("STRIP_STYLE=non-global"); } // Post-build function. func = linkMac; funcArg = configs[i] + "-" + sdks[j]; // Push this build. builds.push([configs[i], command, dir, args, func, funcArg]); // Prepare link command. command = "libtool"; dir = path.join(path.resolve(derivedDir), "Build", "Products"); dest = path.join(cmd.dest, "cocos2d", "x", "lib", configs[i] + "-" + platform, sdks[j]); wrench.mkdirSyncRecursive(dest); dest = path.join(dest, "libcocos2dx-prebuilt.a"); // Link. args = [ "-static", "-o", dest, "-filelist", path.join(dir, "list.txt") // this doesn't use derived data: // xcodebuild -project src/cocos2d-x/build/cocos2d_libs.xcodeproj -target "libcocos2d Mac" -showBuildSettings | grep BUILD_DIR ]; builds.push([configs[i], command, dir, args, false, false]); } } } nextBuild(platform, callback); }; // // link mac // var linkMac = function(configPlatform, callback) { var i, txt, d = path.join(cmd.src, "build", configPlatform, "Build", "Products"), files = []; files = glob.sync(path.join(d, "**", "*.a")); d = path.join(d, "list.txt"); txt = files.join("\n") + "\n"; logBuild("Writing file list:\n " + d + "\n " + files.join("\n ")); fs.writeFileSync(d, txt); callback(); }; // // prebuild linux // var prebuildLinux = function(platform, config, arch, callback) { var i, j, dir, args, func, funcArg, archs = [/*"i386", */"x86_64"], // a second arch would currently clobber the first arch's intermediate files configs = (config ? [config] : (cmd.minimal ? ["Debug"] : ["Debug", "Release"])); // Bail if not on Linux. if (process.platform !== "linux") { logBuild("Can only build " + platform + " on Linux", true); callback(); return; } // create builds array builds = []; for (j = 0; j < archs.length; j += 1) { for (i = 0; i < configs.length; i += 1) { dir = path.join(cmd.src, "build", configs[i] + "-linux"); funcArg = configs[i] + "-" + archs[j]; args = [ path.join("..", ".."), "-DDEBUG_MODE=" + (configs[i] === "Debug" ? "ON" : "OFF"), "-DBUILD_SHARED_LIBS=OFF", "-DBUILD_EXTENSIONS=OFF", "-DBUILD_EDITOR_SPINE=OFF", "-DBUILD_EDITOR_COCOSTUDIO=OFF", "-DBUILD_EDITOR_COCOSBUILDER=OFF", "-DBUILD_CPP_TESTS=OFF", "-DBUILD_LUA_LIBS=OFF", "-DBUILD_LUA_TESTS=OFF", "-DBUILD_JS_TESTS=OFF", "-DUSE_BOX2D=OFF" // USE_PREBUILT_LIBS=ON (use default here) ]; if (doJSB !== false) { args.push("-DBUILD_JS_LIBS=ON"); } else { args.push("-DBUILD_JS_LIBS=OFF"); } if (doPhysics !== false) { args.push("-DUSE_CHIPMUNK=ON"); args.push("-DUSE_BULLET=ON"); } else { args.push("-DUSE_CHIPMUNK=OFF"); args.push("-DUSE_BULLET=OFF"); } if (doNavmesh !== false) { args.push("-DUSE_RECAST=ON"); } else { args.push("-DUSE_RECAST=OFF"); } if (doWebp !== false) { args.push("-DUSE_WEBP=ON"); } else { args.push("-DUSE_WEBP=OFF"); } wrench.mkdirSyncRecursive(dir); builds.push([configs[i], "cmake", dir, args, false, false]); builds.push([configs[i], "make", dir, ["-j", parseInt(Math.max(4, os.cpus.length * 1.5))], linkLinux, funcArg]); } } nextBuild(platform, callback); }; // // link linux // var linkLinux = function(configArch, callback) { var a, src, dest, config = "", arch = "", bits = ""; // Copy these prebuilt files. a = configArch.split("-"); if (a.length >= 2) { config = a[0]; arch = a[1]; bits = (arch === "x86_64" ? "64-bit" : "32-bit"); // copy libcocos2d.a src = path.join(cmd.src, "build", config + "-linux", "lib"); dest = path.join(cmd.dest, "cocos2d", "x", "lib", config + "-Linux", arch); logBuild("Copying Linux libraries from " + src + " to " + dest, false); copyGlobbed(src, dest, "*.a"); // copy other prebuilt libraries src = path.join(cmd.src, "external"); logBuild("Copying Linux libraries from " + src + " to " + dest, false); copyGlobbed(src, dest, "*.a", path.join("prebuilt", "linux", bits), "none"); src = path.join(cmd.src, "external"); logBuild("Copying Linux libraries from " + src + " to " + dest, false); copyGlobbed(src, dest, "*.so", path.join("prebuilt", bits), "none"); } else { logBuild("Failed to link Linux because couldn't get config and arch from: " + configArch, true); } // remember to call callback when finished callback(); }; // // prebuild windows // var prebuildWin = function(platform, config, arch, callback) { var i, j, command, dir, args, func, funcArg, targets, projs = [], configs = (config ? [config] : (cmd.minimal ? ["Debug"] : ["Debug", "Release"])); // Bail if not on Windows. if (process.platform !== "win32") { logBuild("Can only build win32 on Windows", true); callback(); return; } // set vc targets path process.env["VCTargetsPath"] = vcTargetsPath; // create builds builds = []; for (i = 0; i < configs.length; i += 1) { command = msBuildExePath; dir = path.homedir();// cmd.prefix; func = linkWin; funcArg = configs[i]; targets = ["libcocos2d"]; if (doJSB) { targets.push("libjscocos2d"); } args = [ path.join(cmd.src, "build", "cocos2d-win32.sln"), "/nologo", "/maxcpucount:4", "/t:" + targets.join(";"), //"/p:VisualStudioVersion=12.0", //"/p:PlatformTarget=x86", //"/verbosity:diag", //"/clp:ErrorsOnly", //"/p:nowarn=4005", //'/p:WarningLevel=0', "/p:configuration=" + configs[i] + ";platform=Win32" ]; // push this build builds.push([configs[i], command, dir, args, func, funcArg]); } // start nextBuild("Windows", callback); }; // // link windows // var linkWin = function(config, callback) { var i, command = path.basename(libExePath), src = path.join(cmd.src, "build", config + ".win32"), dest = path.join(cmd.dest, "cocos2d", "x", "lib", config + "-win32", "x86"), args = [ '/NOLOGO', '/IGNORE:4006', //'/OPT:REF', //'/OPT:ICF', //'/OUT:"' + path.join(dest, "libcocos2dx-prebuilt.lib") + '"', //'"' + path.join(src, "*.lib") + '"' '/OUT:' + path.join(dest, "libcocos2dx-prebuilt.lib"), path.join(src, "*.lib") ]; // make output dir wrench.mkdirSyncRecursive(dest); // copy dlls and finish creating command //copyGlobbed(path.join(src, "external", "lua", "luajit", "prebuilt", "win32"), dest, "*.dll"); copyGlobbed(src, dest, "*.dll"); copyGlobbed(src, dest, "*.pdb"); copyGlobbed(src, dest, "*.exp"); copyGlobbed(src, dest, "glfw3.lib"); // possibly because of the new duplicate -2015.lib files, this is necessary... copyGlobbed(src, dest, "glfw3-2015.lib"); copyGlobbed(src, dest, "libchipmunk.lib"); copyGlobbed(src, dest, "libchipmunk-2015.lib"); copyGlobbed(src, dest, "libjpeg.lib"); copyGlobbed(src, dest, "libjpeg-2015.lib"); copyGlobbed(src, dest, "libpng.lib"); copyGlobbed(src, dest, "libpng-2015.lib"); copyGlobbed(src, dest, "libtiff.lib"); copyGlobbed(src, dest, "libtiff-2015.lib"); // move unneeded file(s) try { // this must be done because both of these libpng.lib files contain pngwin.res and lib.exe errors with LNK1241 // (consider instead using /REMOVE option: https://msdn.microsoft.com/en-us/library/0xb6w1f8.aspx) fs.renameSync(path.join(src, "libpng-2015.lib"), path.join(src, "libpng-2015.duplicate-lib")); } catch(e) { } // execute spawn(command, args, {cwd: path.dirname(libExePath), env: process.env}, function(err){ if (!err) { callback(); } else { logBuild(err, true); } }); }; // // prebuild android // var prebuildAndroid = function(platform, config, arch, callback) { var i, j, src, dest, command, args = [], func, funcArg, ndkToolchainVersion = "", configs = (config ? [config] : (cmd.minimal ? ["Debug"] : ["Debug", "Release"])), archs = (cmd.minimal ? ["armeabi"] : ["armeabi", "armeabi-v7a", "x86"]), toolchains = ["4.9", "4.8", "4.7"]; // All platforms require NDK_ROOT environment variable. if (!("NDK_ROOT" in process.env)) { logBuild("Can only build Android with a proper SDK and NDK installation.", true); logBuild("(Missing NDK_ROOT environment variable.)", true); logBuild("See: http://bit.ly/rapidgame-android-readme", true); callback(); return; } // Determine which toolchain to use. for (i = 0; i < toolchains.length; i += 1) { if (dirExists(path.join(process.env["NDK_ROOT"], "toolchains", "arm-linux-androideabi-" + toolchains[i]))) { ndkToolchainVersion = toolchains[i]; break; } } if (ndkToolchainVersion.length <= 0) { logBuild("No suitable toolchain version found in: " + path.join(process.env["NDK_ROOT"], "toolchains"), true); logBuild("Acceptable versions: " + toolchains.join(", "), true); callback(); return; } // create builds array builds = []; for (i = 0; i < configs.length; i += 1) { // Copy proj.android to dest only if not exists (otherwise would remove intermediate build files). src = path.join(cmd.prefix, "src", "proj.android"); dest = path.join(cmd.src, "build", configs[i] + "-Android"); // Always copy latest Android build files. logBuild("Copying " + src + " to " + dest, false); wrench.mkdirSyncRecursive(dest); //copyGlobbed(src, dest, "build.sh"); //copyGlobbed(src, dest, "makefile"); copyGlobbed(path.join(src, "jni"), path.join(dest, "jni"), "*"); // Set func. func = linkAndroid; for (j = 0; j < archs.length; j += 1) { // Run the ndk-build. command = path.join(process.env["NDK_ROOT"], "ndk-build" + (process.platform === "win32" ? ".cmd" : "")); args = [ "-C", dest, "NDK_TOOLCHAIN_VERSION=" + ndkToolchainVersion, "NDK_MODULE_PATH=" + cmd.src, "APP_PLATFORM=" + "android-18", "APP_ABI=" + archs[j], "CONFIG=" + configs[i], "DO_JS=" + (doJSB ? "1" : "0") ]; funcArg = configs[i] + "-" + archs[j]; builds.push([configs[i], command, dest, args, func, funcArg]); } } nextBuild("Android", callback); }; // // link android // var linkAndroid = function(configArch, callback) { var a, src, dest, config = "", arch = ""; // Copy these prebuilt files. a = configArch.split("-"); if (a.length >= 2) { config = a[0]; arch = a[1]; src = path.join(cmd.src, "build", config + "-Android", "obj", "local", arch); dest = path.join(cmd.dest, "cocos2d", "x", "lib", config + "-Android", arch); logBuild("Copying Android libraries from " + src + " to " + dest, false); copyGlobbed(src, dest, "*.a"); } else { logBuild("Failed to link Android because couldn't get config and arch from: " + configArch, true); } // Copy other prebuilt libs. dest = path.join(cmd.dest, "cocos2d", "x", "lib", "Debug-Android"); copyGlobbed(path.join(cmd.src, "external"), dest, "*.a", "android", 1); dest = path.join(cmd.dest, "cocos2d", "x", "lib", "Release-Android"); copyGlobbed(path.join(cmd.src, "external"), dest, "*.a", "android", 1); callback(); // build.sh used to combine all the .a files into one libcocos2dx-prebuilt.a like this: /* ar=$(ndk-which ar) strip=$(ndk-which strip) lib="libcocos2dx-prebuilt.a" rm -f ${dest}/${lib} for dir in audioengine_static cocos2dx_internal_static cocos_extension_static cocos_ui_static cocostudio_static spine_static \ box2d_static cocos2dxandroid_static cocos_flatbuffers_static cocosbuilder_static bullet_static cocos3d_static \ cocos_network_static cocosdenshion_static recast_static do ${ar} rs ${dest}/${lib} $(find ${src}/${dir} -name *.o) if [ "$3" != "nostrip" ]; then ${strip} -x ${dest}/${lib} fi done */ }; // // Get paths to needed tools. // var getToolPaths = function(callback) { if (process.platform === "win32") { getMSBuildPath(function(success){ if (!success) { callback(false); return; } getLibExePath(function(success){ if (!success) { callback(false); return; } getVCTargetsPath(function(success){ callback(success); }); }); }); } else { callback(true); } }; // // get the ms build tools path // var getMSBuildPath = function(cb) { var Winreg = require("winreg"), root = '\\Software\\Microsoft\\MSBuild\\ToolsVersions', regKey, savePath = path.join(cmd.prefix, "msbuildpath.txt"), callback = function() { if (fileExists(msBuildExePath)) { try{ fs.writeFileSync(savePath, msBuildExePath).toString().trim(); } catch (e) { } logBuild("MSBUILD: " + msBuildExePath, true); cb(true); } else { console.log("Unable to locate MSBuild.exe. Please set the contents of the following file to the absolute path to MSBuild.exe:\n\n\t" + savePath + "\n\nExample: C:\\Path\\to\\MSBuild.exe"); cb(); } }; // try to load saved path try{ msBuildExePath = fs.readFileSync(savePath).toString().trim(); if (msBuildExePath.length > 0) { callback(); return; } } catch(e) { } // find path to MSBuildToolsPath regKey = new Winreg({key: root}); regKey.keys(function (err, items) { var i, key, highest = 0, buildPath = '', count = 0; if (err) { logBuild(err, true); callback(); } for (i = 0; i < items.length; i += 1) { key = path.basename(items[i].key); regKey = new Winreg({key: root + '\\' + key}); (function(key){ regKey.get("MSBuildToolsPath", function(err, item) { count += 1; if (err) { logBuild(err, true); } else if (typeof item === "object" && typeof item.value === "string" && item.value.length) { logBuild("Potential MSBuildToolsPath: " + item.value); logBuild("Checking version = " + parseFloat(key) + " against highest " + highest); if (parseFloat(key) > highest) { buildPath = item.value; highest = parseFloat(key); } } if (count === items.length) { if (buildPath.length) { logBuild("Final MSBuildToolsPath: " + buildPath); msBuildExePath = path.join(buildPath, "MSBuild.exe"); } callback(); } }); })(key); } }); }; // // get the VCTargetsPath // (todo: mine this from the registry \\Software\\Microsoft\\MSBuild\\ToolsVersions\\X\\VCTargetsPath) // var getVCTargetsPath = function(cb) { var i, ret, bases = [ "\\MSBuild\\Microsoft.Cpp\\v4.0\\V140\\", "\\MSBuild\\Microsoft.Cpp\\v4.0\\V120\\", "\\MSBuild\\Microsoft.Cpp\\v4.0\\V110\\", "\\MSBuild\\Microsoft.Cpp\\v4.0\\" ], names = [ "C:\\Program Files (x86)", "\\Program Files (x86)", "C:\\Program Files", "\\Program Files" ], savePath = path.join(cmd.prefix, "vctargetspath.txt"), callback = function() { if (dirExists(vcTargetsPath)) { try{ fs.writeFileSync(savePath, vcTargetsPath).toString().trim(); } catch (e) { } logBuild("VCTARGETS: " + vcTargetsPath, true); cb(true); } else { console.log("Unable to locate VCTargetsPath directory. Please set the contents of the following file to the absolute path to VCTargets:\n\n\t" + savePath + "\n\nExample: " + names[0] + bases[0]); cb(); } }; // try to load saved path try{ vcTargetsPath = fs.readFileSync(savePath).toString().trim(); if (vcTargetsPath.length > 0) { callback(); return; } } catch(e) { } // determine path for (j = 0; j < bases.length; j += 1) { for (i = 0; i < names.length; i += 1) { ret = names[i] + bases[j]; if (dirExists(ret)) { if (cmd.verbose) { logBuild("VCTargetsPath: " + ret); } vcTargetsPath = ret; callback(); return; } } } callback(); }; // // get the vc bin dir and append lib.exe // todo: mine this from the registry // or mimic python's find_vcvarsall // http://stackoverflow.com/questions/6551724/how-do-i-point-easy-install-to-vcvarsall-bat // or: // http://cmake.3232098.n2.nabble.com/How-to-find-vcvarsall-bat-e-g-at-quot-C-Program-Files-x86-Microsoft-Visual-Studio-12-0-VC-quot-CMAKE-td7587359.html // var getLibExePath = function(cb) { var i, j, ret, bases = [ "C:\\Program Files (x86)", "\\Program Files (x86)", "C:\\Program Files", "\\Program Files" ], names = [ "\\Microsoft Visual Studio 14.0\\VC\\bin\\", "\\Microsoft Visual Studio 12.0\\VC\\bin\\" ], savePath = path.join(cmd.prefix, "libexepath.txt"), callback = function() { if (fileExists(libExePath)) { try{ fs.writeFileSync(savePath, libExePath).toString().trim(); } catch (e) { } logBuild("LIB: " + libExePath, true); cb(true); } else { console.log("Unable to locate lib.exe. Please set the contents of the following file to the absolute path to lib.exe:\n\n\t" + savePath + "\n\nExample: " + bases[0] + names[0] + "lib.exe"); cb(); } }; // try to load saved path try{ libExePath = fs.readFileSync(savePath).toString().trim(); if (libExePath.length > 0) { callback(); return; } } catch(e) { } // determine path for (j = 0; j < bases.length; j += 1) { for (i = 0; i < names.length; i += 1) { ret = bases[j] + names[i]; if (dirExists(ret)) { if (cmd.verbose) { logBuild("Lib.exe: " + ret); } libExePath = path.join(ret, 'lib.exe'); callback(); return; } } } callback(); }; // // Copy files recursively with a special exclude filter. // var copyRecursive = function(src, dest, filter, overwrite) { logBuild("cp -r " + path.relative(cmd.prefix, src) + " " + path.relative(cmd.prefix, dest), cmd.verbose); // copy using wrench overwrite = overwrite || false; var options = { forceDelete: overwrite, // false Whether to overwrite existing directory or not excludeHiddenUnix: false, // Whether to copy hidden Unix files or not (preceding .) preserveFiles: !overwrite, // true If we're overwriting something and the file already exists, keep the existing preserveTimestamps: true, // Preserve the mtime and atime when copying files inflateSymlinks: false // Whether to follow symlinks or not when copying files }; if (filter === true) { options.exclude = excludeFilter; } copyCount = 0; try { wrench.copyDirSyncRecursive(src, dest, options); } catch (e) { } return copyCount; }; // // copy files using glob // var copyGlobbed = function(src, dest, pattern, grep, depth) { var i, j, file, files; pattern = path.join("**", pattern); logBuild(" " + path.relative(cmd.prefix, path.join(src, pattern)) + " => " + path.relative(cmd.prefix, dest), cmd.verbose); try { files = glob.sync(path.join(src, pattern)); } catch(e) { logBuild(e, true); } for (i = 0; i < files.length; i += 1) { if (grep && files[i].indexOf(grep) < 0) { continue; } file = path.relative(src, files[i]); //logBuild(path.relative(cmd.prefix, files[i]) + " -> " + path.relative(cmd.prefix, path.join(dest, file))); if (depth === "none") { file = path.basename(file); } else if (depth == 1) { file = path.join(path.basename(path.dirname(file)), path.basename(file)); } file = path.join(dest, file); logBuild(path.relative(cmd.prefix, files[i]) + " -> " + path.relative(cmd.prefix, file)); wrench.mkdirSyncRecursive(path.dirname(file)); try{ fs.writeFileSync(file, fs.readFileSync(files[i])); } catch(e) { logBuild(e, true); } } return files.length; }; // // filters files for exclusion // var excludeFilter = function(filename, dir){ // Shall this file/dir be excluded? var i, ignore, doExclude = false; if (dir.indexOf("proj.android") >= 0) { if (filename === "obj" || filename === "gen" || filename === "assets" || filename === "bin") { doExclude = true; } else if (dir.indexOf("libs") >= 0) { if (filename === "armeabi" || filename === "armeabi-v7a" || filename === "x86" || filename === "mips") { doExclude = true; } } } else if (dir.indexOf(".xcodeproj") >= 0) { if (filename === "project.xcworkspace" || filename === "xcuserdata") { doExclude = true; } } else if (filename === "lib") { try { i = fs.lstatSync(path.join(dir, filename)); if (i.isSymbolicLink()) { doExclude = true; } } catch(e) { logBuild(e, true); } } else if (dir.indexOf(path.join("build", "build")) >= 0) { doExclude = true; } else if (cmd.engine === "titanium" && filename === "build") { doExclude = true; } else if (cmd.engine === "unity" && (filename === "Library" || filename === "Temp" || filename.indexOf(".sln") >= 0 || filename.indexOf(".unityproj") >= 0 || filename.indexOf(".userprefs") >= 0)) { doExclude = true; } else if (filename.substr(0,2) === "._") { doExclude = true; } else { ignore = [ ".DS_Store", //"node_modules", //"wsocket.c", "wsocket.h", //"usocket.c", "usocket.h", //"unix.c", "unix.h", //"serial.c", ]; for (i = 0; i < ignore.length; i += 1) { if (filename === ignore[i]) { doExclude = true; break; } } } // Report and return if (doExclude) { logBuild("Ignoring filename '" + filename + "' in " + dir, cmd.verbose); } if (!doExclude) { copyCount += 1; } return doExclude; }; // // check for upgrade // /*var checkUpdate = function() { var req = http.get("http://registry.npmjs.org/rapidgame"); req.on("response", function(response) { var oldVersion = packageJson.version.toString(), newVersion = ""; response.on("data", function(chunk) { newVersion += chunk; }); response.on("end", function() { try { newVersion = JSON.parse(newVersion); if (typeof newVersion === "object" && typeof newVersion["dist-tags"] === "object") { newVersion = newVersion["dist-tags"]["latest"].toString().trim(); var n1 = oldVersion.substring(oldVersion.lastIndexOf(".")+1), n2 = newVersion.substring(newVersion.lastIndexOf(".")+1); if (newVersion !== oldVersion && n1 < n2) { console.log("\nAn update is available."); console.log("\t" + oldVersion + " -> " + newVersion); console.log("Upgrade instructions:"); if (cmdName.indexOf("pro") >= 0) { console.log("\tcd " + __dirname + " && npm update"); } else { console.log("\t" + (process.platform === "win32" ? "" : "sudo ") + "npm update " + cmdName + " -g"); } console.log(" "); } } } catch(e) { } }); }); req.on("error", function(){}); }*/ // // append to the build log // var logBuild = function(str, echo) { if (echo) { console.log(str); } if (cmd.buildLog) { try { fs.appendFileSync(cmd.buildLog, str + "\n"); } catch(e) { } } }; // // execute a command // var exec = function(command, options, callback) { // logging if (cmd.verbose) { console.log("Current dir: " + options.cwd + "\n"); console.log(command + "\n"); } logBuild("\nCurrent dir:\n\t" + options.cwd + "\n\nExecuting:\n\t" + command + "\n\n"); // execute try { child_process.exec(command, options, function(err, stdout, stderr){ execCallback(err, stdout, stderr); callback(err, stdout, stderr); }); } catch(e) { logBuild(e, true); } }; // // child_process.exec callback // var execCallback = function(error, stdout, stderr) { if (error !== null) { logErr("exec error: " + error); } logBuild(stdout, cmd.verbose || error); logBuild(stderr, cmd.verbose || error); }; // // execute a streaming command // var spawn = function(command, args, options, callback) { if (cmd.verbose) { console.log("Current dir: " + options.cwd + "\n"); console.log(command + ' ' + args.join(' ') + "\n"); } logBuild("\nCurrent dir:\n\t" + options.cwd + "\n\nSpawning:\n\t" + command + ' ' + args.join(' ') + "\n\n"); try { // spawn the process var child, exitCode = 0; if (args && args.length) { child = child_process.spawn(command, args, options); } else { child = child_process.spawn(command, options); } // watch its output child.stdout.on("data", function(chunk) { logBuild(chunk.toString(), cmd.verbose); }); child.stderr.on("data", function(chunk) { logBuild(chunk.toString(), cmd.verbose); }); child.on("error", function(e) { logErr("Spawn error " + e); callback(e); }); // close child.on("exit", function(code, signal) { exitCode = code; //if (code != 0 & cmd.verbose) { // console.log("Code: " + code); // console.log("Signal: " + signal); //} }); child.on("close", function(code) { if (exitCode) { callback(exitCode); } else { callback(); } }); } catch(e) { logErr("Spawn error " + e); callback(e); } }; // // replace occurrences a string with another in a specific file // var sed = function(search, replace, dest) { logBuild("Replacing all '" + search + "' with '" + replace + "' in: " + dest, cmd.verbose); try{ var text = fs.readFileSync(dest).toString(), pos = 0; do { pos = text.indexOf(search); if (pos >= 0) { text = text.substr(0,pos) + replace + text.substr(pos + search.length); } } while(pos >= 0); fs.writeFileSync(dest, text); } catch(e) { logBuild(e, true); } } // // log an error // var logErr = function(str) { console.log(str); logReport("error", str); }; // // Test if a directory exists // var dirExists = function(path) { var stat; try { stat = fs.statSync(path); } catch(e) { } return (stat && stat.isDirectory()); }; // // Test if a file exists // var fileExists = function(path) { var stat; try { stat = fs.statSync(path); } catch(e) { } return (stat && stat.isFile()); }; // // tests if a directory is writeable // var isWriteableDir = function(dir) { try { dir = path.join(dir, ".testfile"); fs.writeFileSync(dir, "test"); fs.unlinkSync(dir); return true; } catch(e) { } return false; }; // // reporting feature no longer used // var logReport = (function() { return function(action, label, value, path) { return; } }()); // // show usage instructions // var usage = function() { cmd.help(); usageExamples(); }; var usageExamples = function() { console.log(" Available:\n"); console.log(" Engines: " + engines.join(", ")); console.log(" Platforms: all (default), " + platforms.join(", ")); console.log(" Templates: " + templates.join(", ")); console.log(""); console.log(" Examples:\n"); console.log(" " + cmdName + " show -s path/to/cocos2d-x/src/"); console.log(" " + cmdName + " prebuild -s path/to/cocos2d-x/src/"); console.log(" " + cmdName + " clean -s path/to/cocos2d-x/src/"); console.log(" " + cmdName + " create cocos2dx \"HeckYeah\" com.mycompany.heckyeah"); console.log(""); }; module.exports = { run: run, version: version }; // // To-do: // - Skip release build for iphonesimulator. // - Fix Mac project name search and replace so names with spaces work. (It used to...) // - Finish orientation option: // .option("-o, --orientation <orientation>", "orientation (" + orientations.join(", ") + ") [" + defaults.orientation + "]", defaults.orientation) // - Consider calling android/strip on copied *.a //