require("./common.jake"); var fs = require('fs'); var path = require('path'); var childProcess = require("child_process"); var os = require('os'); const term = ObjectiveJ.term; const utilsFile = ObjectiveJ.utils.file; var subprojects = ["Objective-J", "CommonJS", "Foundation", "AppKit", "Tools"]; /* * ----------------------------------------------------------------------------- * Build Orchestration * ----------------------------------------------------------------------------- */ var isMasterBuild = process.env.CAPP_INNER_BUILD !== "1"; /* * Initialises the build environment and routes execution. Spawns the complete * pipeline on master invocations to avoid circular dependency resolution, while * delegating to subjake during internal subproject compilation. */ task ("build", function() { if (isMasterBuild) { childProcess.execSync(["mkdir", "-p", $BUILD_DIR].map(utilsFile.enquote).join(" "), {stdio: 'inherit'}); childProcess.execSync(['ln', '-sf', '"$PWD"/node_modules', utilsFile.enquote($BUILD_DIR)].join(" "), {stdio: 'inherit'}); process.env.CAPP_INNER_BUILD = "1"; childProcess.execSync("jake CommonJS assemble-frameworks", {stdio: 'inherit'}); } else { subjake(subprojects, "build"); } }); /* * Traverses subprojects and manual tests for clean/clobber operations, * then explicitly removes the top-level build artifact directory. */ ["clean", "clobber"].forEach(function(aTaskName) { task (aTaskName, function() { subjake(subprojects, aTaskName); subjake(["Tests/Manual"], aTaskName); utilsFile.rm_rf($BUILD_DIR); }); }); $BUILD_CJS_OBJECTIVE_J_DEBUG_FRAMEWORKS = path.join($BUILD_CJS_OBJECTIVE_J, "Frameworks", "Debug"); filedir ($BUILD_CJS_OBJECTIVE_J_DEBUG_FRAMEWORKS, ["debug", "release"], function() { fs.mkdirSync($BUILD_CJS_OBJECTIVE_J_DEBUG_FRAMEWORKS, { recursive: true }); utilsFile.cp_r(path.join($BUILD_DIR, "Debug", "Objective-J"), path.join($BUILD_CJS_OBJECTIVE_J_DEBUG_FRAMEWORKS, "Objective-J")); }); $BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS = path.join($BUILD_CJS_CAPPUCCINO, "Frameworks", "Debug"); filedir ($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, ["debug", "release"], function() { fs.mkdirSync($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, { recursive: true }); utilsFile.cp_r(path.join($BUILD_DIR, "Debug", "Foundation"), path.join($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "Foundation")); utilsFile.cp_r(path.join($BUILD_DIR, "Debug", "AppKit"), path.join($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "AppKit")); utilsFile.cp_r(path.join($BUILD_DIR, "Debug", "BlendKit"), path.join($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "BlendKit")); }); task ("CommonJS", [$BUILD_CJS_OBJECTIVE_J_DEBUG_FRAMEWORKS,$BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "debug", "release"], function() { }); /* * Assembles a concrete Build/Frameworks directory containing all built frameworks, * serving as the central target for manual test symlinks. */ task ("assemble-frameworks", function() { var fwDir = path.join($BUILD_DIR, "Frameworks"); fs.mkdirSync(fwDir, { recursive: true }); var frameworks = [ { src: path.join($BUILD_CJS_OBJECTIVE_J_DEBUG_FRAMEWORKS, "Objective-J"), name: "Objective-J" }, { src: path.join($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "Foundation"), name: "Foundation" }, { src: path.join($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "AppKit"), name: "AppKit" }, { src: path.join($BUILD_CJS_CAPPUCCINO_DEBUG_FRAMEWORKS, "BlendKit"), name: "BlendKit" } ]; frameworks.forEach(function(fw) { var dest = path.join(fwDir, fw.name); utilsFile.rm_rf(dest); utilsFile.cp_r(fw.src, dest); }); }); /* * ----------------------------------------------------------------------------- * NPM Distribution & Legacy Artifacts * ----------------------------------------------------------------------------- */ task ("dist", ["CommonJS"], function() { installCopy($BUILD_CJS_OBJECTIVE_J, false); installCopy($BUILD_CJS_CAPPUCCINO, false); }); task ("sudo-dist", ["CommonJS"], function() { installCopy($BUILD_CJS_OBJECTIVE_J, true); installCopy($BUILD_CJS_CAPPUCCINO, true); }); task ("install", ["dist"], function() { installGlobal($BUILD_CJS_OBJECTIVE_J, false); installGlobal($BUILD_CJS_CAPPUCCINO, false); }); task ("sudo-install", ["sudo-dist"], function() { installGlobal($BUILD_CJS_OBJECTIVE_J, true); installGlobal($BUILD_CJS_CAPPUCCINO, true); }); task ("install-symlinks", function() { installSymlink($BUILD_CJS_OBJECTIVE_J); installSymlink($BUILD_CJS_CAPPUCCINO); }); task ("install-debug-symlinks", function() { SYSTEM.env.CONFIG = "Debug"; spawnJake("install-symlinks"); }); task ("clean-sprites", function() { var f = new FileList(path.join(SYSTEM.env.CAPP_BUILD, "**/dataURLs.txt")), paths = f.items(); f = new FileList(path.join(SYSTEM.env.CAPP_BUILD, "**/MHTML*.txt")); paths = paths.concat(f.items()); paths.forEach(function(path) { fs.rmSync(path); }); }); task ("clobber-theme", function() { var f = new FileList(path.join(SYSTEM.env.CAPP_BUILD, "**/Aristo.blend"), path.join(SYSTEM.env.CAPP_BUILD, "**/Aristo2.blend")), paths = f.items(); f = new FileList(path.join(SYSTEM.env.CAPP_BUILD, "Aristo.build"), path.join(SYSTEM.env.CAPP_BUILD, "Aristo2.build")); paths = paths.concat(f.items()); paths.forEach(function(path) { utilsFile.rm_rf(path); }); }); /* * ----------------------------------------------------------------------------- * Documentation Generation * ----------------------------------------------------------------------------- */ $DOCUMENTATION_BUILD = path.join($BUILD_DIR, "Documentation"); task ("docs", ["documentation"]); task ("documentation", function() { generateDocs(false); }); task ("docs-no-frame", ["documentation-no-frame"]); task ("documentation-no-frame", function() { generateDocs(true); }); task ("docset", function() { if (!executableExists("docsetutil")) { console.error("\nError: 'docsetutil' is not installed, but it's required to build the docset.".red); console.log("This tool is no longer bundled with Xcode, but can be installed with Homebrew:"); console.log("\n brew install swiftdocorg/formulae/docsetutil\n".yellow); process.exit(1); } generateDocs(true, true); }); function executableExists(command) { try { const checkCmd = process.platform === 'win32' ? 'where' : 'which'; childProcess.execSync(`${checkCmd} ${command}`, { stdio: 'pipe' }); return true; } catch (e) { return false; } } function generateDocs(/* boolean */ noFrame, /* boolean */ buildDocset = false) { var doxygen = null; if (executableExists("doxygen")) { doxygen = "doxygen"; } if (!doxygen) { console.log("Doxygen not installed or not found, skipping documentation generation."); return; } const projectRoot = process.cwd(); const tempDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'capp-docs-'))); console.log(`Using temporary directory for build: ${tempDir}`); try { var documentationDir = path.join(projectRoot, "Tools", "Documentation"); console.log("Pre-processing source files..."); const preProcessorsDir = path.join(documentationDir, "preprocess"); var processors = fs.readdirSync(preProcessorsDir).sort(); for (const processor of processors) { const processorPath = path.join(preProcessorsDir, processor); childProcess.execSync(`"${processorPath}" "${projectRoot}"`, { stdio: 'inherit', cwd: tempDir }); } const doxygenConfigFile = path.join(documentationDir, "Cappuccino.doxygen"); const doxygenTempConfig = path.join(tempDir, "Cappuccino.doxygen"); fs.copyFileSync(doxygenConfigFile, doxygenTempConfig); if (noFrame) { console.log("Disabling treeview for no-frame documentation."); childProcess.execSync(`sed -i.bak 's/GENERATE_TREEVIEW.*=.*YES/GENERATE_TREEVIEW = NO/' "${doxygenTempConfig}"`); } console.log("Running Doxygen..."); childProcess.execSync(`"${doxygen}" "${doxygenTempConfig}"`, { stdio: 'inherit', cwd: tempDir }); const generatedDocsRoot = path.join(tempDir, "Documentation"); const htmlOutputDir = path.join(generatedDocsRoot, "html"); const makefilePath = path.join(htmlOutputDir, "Makefile"); if (buildDocset && fs.existsSync(makefilePath)) { console.log("Patching Makefile to use 'docsetutil' from PATH..."); childProcess.execSync(`sed -i.bak 's|"\\$(XCODE_INSTALL_DIR)"/usr/bin/docsetutil|docsetutil|' "${makefilePath}"`); console.log("Building docset with 'make'..."); childProcess.execSync('make', { stdio: 'inherit', cwd: htmlOutputDir }); } console.log("Post-processing generated documentation..."); const postProcessorsDir = path.join(documentationDir, "postprocess"); processors = fs.readdirSync(postProcessorsDir).sort(); for (const processor of processors) { const processorPath = path.join(postProcessorsDir, processor); childProcess.execSync(`"${processorPath}" "${projectRoot}" "${htmlOutputDir}"`, { stdio: 'inherit', cwd: tempDir }); } if (fs.existsSync(generatedDocsRoot)) { if (fs.existsSync($DOCUMENTATION_BUILD)) { fs.rmSync($DOCUMENTATION_BUILD, { recursive: true, force: true }); } fs.renameSync(generatedDocsRoot, $DOCUMENTATION_BUILD); const finalHtmlPath = path.join($DOCUMENTATION_BUILD, "html"); const customCSS = path.join(documentationDir, "doxygen.css"); if (fs.existsSync(customCSS)) { console.log("Applying custom stylesheet..."); fs.copyFileSync(customCSS, path.join(finalHtmlPath, "doxygen.css")); } console.log("Documentation successfully built in " + finalHtmlPath); } else { console.error("Doxygen or post-processing failed to produce the 'Documentation' directory."); } } catch (e) { console.error("An error occurred during documentation generation:", e.message); process.exit(1); } finally { console.log(`Cleaning up temporary directory: ${tempDir}`); fs.rmSync(tempDir, { recursive: true, force: true }); } } /* * ----------------------------------------------------------------------------- * Starter Downloads & Deployment * ----------------------------------------------------------------------------- */ task ("downloads", ["starter_download"]); $STARTER_README = path.join('Tools', 'READMEs', 'STARTER-README');$STARTER_BOOTSTRAP = 'bootstrap.sh'; $STARTER_DOWNLOAD = path.join($BUILD_DIR, 'Cappuccino', 'Starter'); $STARTER_DOWNLOAD_APPLICATION = path.join($STARTER_DOWNLOAD, 'NewApplication'); $STARTER_DOWNLOAD_README = path.join($STARTER_DOWNLOAD, 'README'); $STARTER_DOWNLOAD_BOOTSTRAP = path.join($STARTER_DOWNLOAD, 'bootstrap.sh'); task ("starter_download", [$STARTER_DOWNLOAD_APPLICATION, $STARTER_DOWNLOAD_README,$STARTER_DOWNLOAD_BOOTSTRAP, "documentation"], function() { if (FILE.exists($DOCUMENTATION_BUILD)) { utilsFile.rm_rf(path.join($STARTER_DOWNLOAD, 'Documentation')); utilsFile.cp_r(path.join($DOCUMENTATION_BUILD, 'html', '.'), path.join($STARTER_DOWNLOAD, 'Documentation')); } }); filedir ($STARTER_DOWNLOAD_APPLICATION, ["CommonJS"], function() { utilsFile.rm_rf($STARTER_DOWNLOAD_APPLICATION); FILE.mkdirs($STARTER_DOWNLOAD); if (OS.system(["capp", "gen", $STARTER_DOWNLOAD_APPLICATION, "-t", "Application", "--noconfig"])) OS.exit(1); }); filedir ($STARTER_DOWNLOAD_README, [$STARTER_README], function() { utilsFile.cp($STARTER_README,$STARTER_DOWNLOAD_README); }); filedir ($STARTER_DOWNLOAD_BOOTSTRAP, [$STARTER_BOOTSTRAP], function() { var bootstrap = FILE.read($STARTER_BOOTSTRAP, { charset : "UTF-8" }).replace('install_capp=""', 'install_capp="yes"'); FILE.write($STARTER_DOWNLOAD_BOOTSTRAP, bootstrap, { charset : "UTF-8" }); OS.system(["chmod", "+x", $STARTER_DOWNLOAD_BOOTSTRAP]); }); task ("deploy", ["downloads", "demos"], function() { var cappuccino_output_path = path.join($BUILD_DIR, 'Cappuccino'); var starter_zip_output = path.join($BUILD_DIR, 'Cappuccino', 'Starter.zip'); utilsFile.rm_rf(starter_zip_output); OS.system("cd " + OS.enquote(cappuccino_output_path) + " && zip -ry -8 Starter.zip Starter"); }); task ("demos", function() { var demosDir = path.join($BUILD_DIR, "CappuccinoDemos"), zipDir = path.join(demosDir, "demos.zip"), demosQuoted = OS.enquote(demosDir), zipQuoted = OS.enquote(zipDir); utilsFile.rm_rf(demosDir); FILE.mkdirs(demosDir); OS.system("curl -L http://github.com/cappuccino/cappuccino-demos/zipball/master > " + zipQuoted); OS.system("(cd " + demosQuoted + " && unzip " + zipQuoted + " -d demos)"); require("objective-j"); function Demo(aPath) { this._path = aPath; this._plist = CFPropertyList.readPropertyListFromFile(path.join(aPath, 'Info.plist')); } Demo.prototype.plist = function(key) { if (key) return this._plist.valueForKey(key); return this._plist; }; Demo.prototype.name = function() { return this.plist("CPBundleName"); }; Demo.prototype.path = function() { return this._path; }; Demo.prototype.excluded = function() { return !!this.plist("CPDemoExcluded"); }; Demo.prototype.toString = function() { return this.name(); }; FILE.glob(path.join(demosDir, "demos", "**/Info.plist")).map(function(demoPath){ return new Demo(FILE.dirname(demoPath)); }).filter(function(demo){ return !demo.excluded(); }).forEach(function(demo) { utilsFile.cp_r(path.join($STARTER_DOWNLOAD_APPLICATION, "Frameworks"), path.join(demo.path(), "Frameworks")); utilsFile.rm_rf(path.join(demo.path(), "Frameworks", "Debug")); var outputPath = demo.name().replace(/\s/g, "-") + ".zip"; OS.system("cd " + OS.enquote(FILE.dirname(demo.path()))+" && zip -ry -8 " + OS.enquote(outputPath) + " " + OS.enquote(FILE.basename(demo.path()))); utilsFile.rm_rf(path.join(demo.path(), "Frameworks")); }); }); /* * ----------------------------------------------------------------------------- * Testing and Package Publishing * ----------------------------------------------------------------------------- */ task("test", ["CommonJS", "test-only"]); task("test-only", function() { var tests = new FileList('Tests/**/*Test.j'); var cmd = ["ojtest"].concat(tests.items()); var cmdString = cmd.map(utilsFile.enquote).join(" "); try { childProcess.execSync(serializedENV() + " " + cmdString, {stdio: 'inherit'}); } catch (e) { process.exit(1); } }); task("check-missing-imports", function() { var code = OS.system(serializedENV() + " " + ["js", "Tests/DetectMissingImports.js"].map(OS.enquote).join(" ")); if (code !== 0) OS.exit(code); }); task("push-packages", ["push-cappuccino", "push-objective-j"]); task("push-cappuccino", function() { pushPackage( $BUILD_CJS_CAPPUCCINO, "git@github.com:280north/cappuccino-package.git", SYSTEM.env.PACKAGE_BRANCH ); }); task("push-objective-j", function() { pushPackage( $BUILD_CJS_OBJECTIVE_J, "git@github.com:280north/objective-j-package.git", SYSTEM.env.PACKAGE_BRANCH ); }); function pushPackage(path, remote, branch) { branch = branch || "master"; var pushPackagesPath = FILE.path(".push-package"); pushPackagesPath.mkdirs(); var packagePath = pushPackagesPath.join(remote.replace(/[^\w]/g, "_")); stream.print("Pushing " + colorize(path, "blue") + " to " + branch + " of " + colorize(remote, "blue")); if (packagePath.isDirectory()) OS.system(buildCmd([["cd", packagePath], ["git", "fetch"]])); else OS.system(["git", "clone", remote, packagePath]); if (OS.system(buildCmd([["cd", packagePath], ["git", "checkout", "origin/"+branch]]))) { if (OS.system(buildCmd([ ["cd", packagePath], ["git", "symbolic-ref", "HEAD", "refs/heads/"+branch], ["rm", ".git/index"], ["git", "clean", "-fdx"] ]))) throw "pushPackage failed"; } if (OS.system("cd "+OS.enquote(packagePath)+" && git rm --ignore-unmatch -r * && rm -rf *")) throw "pushPackage failed"; if (OS.system("cp -R "+OS.enquote(path)+"/* "+OS.enquote(packagePath)+"/.")) throw "pushPackage failed"; var pkg = JSON.parse(packagePath.join("package.json").read({ charset : "UTF-8" })); term.stream.print(" Version: " + colorize(pkg.version, "purple")); term.stream.print(" Revision: " + colorize(pkg["cappuccino-revision"], "purple")); term.stream.print(" Timestamp: " + colorize(pkg["cappuccino-timestamp"], "purple")); var cmd = [ ["cd", packagePath], ["git", "add", "."], ["git", "commit", "-m", "version="+pkg.version+"; revision="+pkg["cappuccino-revision"]+"; timestamp="+pkg["cappuccino-timestamp"]+";"] ]; if (pkg["cappuccino-revision"]) cmd.push(["git", "tag", "rev-"+pkg["cappuccino-revision"].slice(0,6)]); OS.system(buildCmd(cmd)); if (OS.system(buildCmd([ ["cd", packagePath], ["git", "push", "--tags", "origin", "HEAD:"+branch] ]))) throw "pushPackage failed"; } function buildCmd(arrayOfCommands) { return arrayOfCommands.map(function(cmd) { return cmd.map(OS.enquote).join(" "); }).join(" && "); }