diff --git a/Tools/press/main.j b/Tools/press/main.j index 3f15b80cd..ff567fa33 100644 --- a/Tools/press/main.j +++ b/Tools/press/main.j @@ -1,139 +1,182 @@ +var OS = require("os"); +if (system.engine !== "rhino") { + system.args.splice(1,2); // remove library path and main.j + var cmd = "NARWHAL_ENGINE_HOME='' NARWHAL_ENGINE='rhino' " + system.args.map(OS.enquote).join(" "); + OS.exit(OS.system(cmd)); +} + @import @import "objj-analysis-tools.j" -var defaultMain = "main.j", - defaultFrameworks = "Frameworks"; +var ARGS = require("args"); +var FILE = require("file"); +var OS = require("os"); +var DOM = require("browser/dom"); -var usageMessage = -"Usage: press root_directory output_directory [options]\n\ - --main path The relative path (from root_directory) to the main file (default: 'main.j')\n\ - --frameworks path The relative path (from root_directory) to the frameworks directory (default: 'Frameworks')\n\ - --platforms Platform names, colon separated (default: 'browser:objj')\n\ - --png Run pngcrush on all PNGs (pngcrush must be installed!)\n\ - --flatten Flatten all code into a single Application.js file and attempt add script tag to index.html (useful for Adobe AIR and CDN deployment)\n\ - --nostrip Don't strip any files\n\ - --v Verbose"; +var serializer = new DOM.XMLSerializer(); -function main() +var parser = new ARGS.Parser(); + +parser.usage("INPUT_PROJECT OUTPUT_PROJECT"); +parser.help("Optimizes Cappuccino applications for deployment to the web."); + +parser.option("-m", "--main", "main") + .def("main.j") + .set() + .help("The relative path (from INPUT_PROJECT) to the main file (default: 'main.j')"); + +parser.option("-F", "--framework", "frameworks") + .def(["Frameworks"]) + .push() + .help("Add a frameworks directory, relative to INPUT_PROJECT (default: ['Frameworks'])"); + +parser.option("-E", "--environment", "environments") + .def(['W3C', 'IE7', 'IE8']) + .push() + .help("Add a platform name (default: ['W3C', 'IE7', 'IE8'])"); + +parser.option("-f", "--flatten", "flatten") + .def(false) + .set(true) + .help("Flatten all code into a single Application.js file and attempt add script tag to index.html (useful for Adobe AIR and CDN deployment)"); + +parser.option("-n", "--nostrip", "strip") + .def(true) + .set(false) + .help("Do not strip any files"); + +parser.option("-p", "--pngcrush", "png") + .def(false) + .set(true) + .help("Run pngcrush on all PNGs (pngcrush must be installed!)"); + +parser.option("-v", "--verbose", "verbose") + .def(false) + .set(true) + .help("Verbose logging"); + +parser.helpful(); + +function main(args) { - var rootDirectory = null, - outputDirectory = null, - mainFilename = null, - frameworksDirectory = null, - platforms = ['browser', 'objj'], - optimizePNG = false, - flatten = false, - noStrip = false, - verbose = false; + var options = parser.parse(args); - var usageError = false; - while (system.args.length && !usageError) - { - var arg = system.args.shift(); - switch(arg) - { - case "--main": - if (system.args.length) - mainFilename = system.args.shift(); - else - usageError = true; - break; - case "--frameworks": - if (system.args.length) - frameworksDirectory = system.args.shift().replace(/\/$/, ""); - else - usageError = true; - break; - case "--platforms": - if (system.args.length) - platforms = system.args.shift().split(":"); - else - usageError = true; - break; - case "--png": - optimizePNG = true; - break; - case "--flatten": - flatten = true; - break; - case "--nostrip": - noStrip = true; - break; - case "--v": - verbose = true; - break; - default: - if (rootDirectory == null) - rootDirectory = arg.replace(/\/$/, ""); - else if (outputDirectory == null) - outputDirectory = arg.replace(/\/$/, ""); - else - usageError = true; - } - } - - if (verbose) - CPLogRegister(CPLogPrint); - else - CPLogRegisterRange(CPLogPrint, "fatal", "info"); - - if (usageError || rootDirectory == null || outputDirectory == null || !platforms.length) - { - print(usageMessage); + if (options.args.length < 2) { + parser.printUsage(options); return; } - rootDirectory = absolutePath(rootDirectory); + //if (options.verbose) + CPLogRegister(CPLogPrint); + //else + // CPLogRegisterRange(CPLogPrint, "fatal", "info"); + + var rootPath = FILE.path(options.args[0]).absolute(); + var outputPath = FILE.path(options.args[1]).absolute(); + + press(rootPath, outputPath, options); +} + +function press(rootPath, outputPath, options) { + CPLog.info("==========================================="); + CPLog.info("Application root: " + rootPath); + CPLog.info("Output directory: " + outputPath); - // determine main and frameworks paths - var mainPath = rootDirectory + "/" + (mainFilename || defaultMain), - frameworksPath = rootDirectory + "/" + (frameworksDirectory || defaultFrameworks); + var outputFiles = {}; + + // analyze and gather files for each environment: + options.environments.forEach(function(environment) { + pressEnvironment(rootPath, outputFiles, environment, options); + }); + + // phase 4: copy everything and write out the new files + CPLog.error("PHASE 4: copy to output ("+rootPath+" to "+outputPath+")"); + + FILE.copyTree(rootPath, outputPath); + + for (var path in outputFiles) { + CPLog.trace("Writing: " + path); - CPLog.info("Application root: " + rootDirectory); - CPLog.info("Output directory: " + outputDirectory); + var file = outputPath.join(relativeToRootPath(path)); + + var parent = file.dirname(); + if (!parent.exists()) { + CPLog.warn(parent + " doesn't exist, creating directories."); + parent.mkdirs(); + } + + if (typeof outputFiles[path] !== "string") + outputFiles[path] = outputFiles[path].join(""); + + CPLog.info((file.exists() ? "Overwriting: " : "Writing: ") + file); + FILE.write(file, outputFiles[path], { charset : "UTF-8" }); + } + + // outputPath.glob("**/Frameworks/Debug").forEach(function(debugFramework) { + // outputPath.join(debugFramework).rmtree(); + // }); + // outputPath.join("index-debug.html").remove(); + + if (options.png) { + pngcrushDirectory(outputPath); + } +} + +function pressEnvironment(rootPath, outputFiles, environment, options) { + + function relativeToRootPath(path) { + return pathRelativeTo(path, rootPath); + } + + var mainPath = String(rootPath.join(options.main)); + var frameworks = options.frameworks.map(function(framework) { return rootPath.join(framework); }); + + CPLog.info("==========================================="); CPLog.info("Main file: " + mainPath) - CPLog.info("Frameworks: " + frameworksPath); + CPLog.info("Frameworks: " + frameworks); + CPLog.info("Environment: " + environment); // get a Rhino context - var cx = Packages.org.mozilla.javascript.Context.getCurrentContext(), - scope = makeObjjScope(cx); + var ctx = Packages.org.mozilla.javascript.Context.getCurrentContext(); + var scope = makeObjjScope(ctx); // "scope" is the same as require("objective-j").window; - // set OBJJ_INCLUDE_PATHS to include the frameworks path - scope.OBJJ_INCLUDE_PATHS = [frameworksPath]; - scope.OBJJ_PLATFORMS = platforms; + scope.OBJJ_INCLUDE_PATHS = frameworks; + scope.OBJJ_ENVIRONMENTS = [environment, "ObjJ"]; // flattening bookkeeping. keep track of the bundles and evaled code (in the correct order!) - var bundleArchives = [], - evaledFragments = []; + var bundleArchiveResponses = []; + var evaledFragments = []; + // here we hook into didReceiveBundleResponse to record the responses for --flattening scope.objj_search.prototype.didReceiveBundleResponseOriginal = scope.objj_search.prototype.didReceiveBundleResponse; scope.objj_search.prototype.didReceiveBundleResponse = function(aResponse) { - //CPLog.trace("RESPONSE: " + aResponse); - - var __fakeResponse = { + var fakeResponse = { success : aResponse.success, - filePath : pathRelativeTo(aResponse.filePath, rootDirectory) + filePath : relativeToRootPath(aResponse.filePath) }; if (aResponse.success) { - var xmlOutput = new Packages.java.io.ByteArrayOutputStream(); - outputTransformer(xmlOutput, aResponse.xml, "UTF-8"); - //__fakeResponse.xml = String(xmlOutput.toString()); - __fakeResponse.text = CPPropertyListCreate280NorthData(CPPropertyListCreateFromXMLData({ string:String(xmlOutput.toString())})).string; - //CPLog.trace("SERIALIZED: " + __fakeResponse.xml.substring(0,100)); + var xmlString = serializer.serializeToString(aResponse.xml); + fakeResponse.text = CPPropertyListCreate280NorthData(CPPropertyListCreateFromXMLData({ string: xmlString })).string; } - bundleArchives.push(__fakeResponse); - + bundleArchiveResponses.push(fakeResponse); + this.didReceiveBundleResponseOriginal.apply(this, arguments); } + var context = { + ctx : ctx, + scope : scope, + relativeToRootPath : relativeToRootPath + }; + // phase 1: get global defines CPLog.error("PHASE 1: Loading application..."); - var globals = findGlobalDefines(cx, scope, mainPath, evaledFragments); + var globals = findGlobalDefines(context, mainPath, evaledFragments); // coalesce the results var dependencies = coalesceGlobalDefines(globals); @@ -141,14 +184,14 @@ function main() // Log CPLog.trace("Global defines:"); for (var i in dependencies) - CPLog.trace(" " + i + " => " + dependencies[i]); + CPLog.trace(" " + i + " => " + relativeToRootPath(dependencies[i])); // phase 2: walk the dependency tree (both imports and references) to determine exactly which files need to be included CPLog.error("PHASE 2: Walk dependency tree..."); var requiredFiles = {}; - if (noStrip) + if (options.nostrip) { // all files are required. no need for analysis requiredFiles = scope.objj_files; @@ -163,20 +206,13 @@ function main() CPLog.warn("Analyzing dependencies..."); - var context = { - scope : scope, - dependencies : dependencies, - processedFiles : {}, - ignoreFrameworkImports : true, - importCallback : function(importing, imported) { - requiredFiles[imported] = true; - }, - referenceCallback : function(referencing, referenced) { - requiredFiles[referenced] = true; - } - } + context.dependencies = dependencies; + context.ignoreFrameworkImports = true; // ignores "XXX/XXX.j" imports + context.importCallback = function(importing, imported) { requiredFiles[imported] = true; }; + context.referenceCallback = function(referencing, referenced) { requiredFiles[referenced] = true; } requiredFiles[mainPath] = true; + traverseDependencies(context, scope.objj_files[mainPath]); var count = 0, @@ -185,12 +221,12 @@ function main() { if (requiredFiles[path]) { - CPLog.debug("Included: " + path); + CPLog.debug("Included: " + relativeToRootPath(path)); count++; } else { - CPLog.info("Excluded: " + path); + CPLog.info("Excluded: " + relativeToRootPath(path)); } total++; } @@ -207,17 +243,14 @@ function main() //} } - var outputFiles = {}; - - if (flatten) + if (options.flatten) { // phase 3a: build single Application.js file (and modified index.html) CPLog.error("PHASE 3a: Flattening..."); - var applicationJS = [], - indexHTML = readFile(rootDirectory + "/index.html"); - - // shim for faking bundle stuff. kind of a giant hack. + // Shim for faking bundle responses. + // We're just defining it here so we can serialize the function. It's not used within press. + // ************************************************** var fakeDidReceiveBundleResponse = function(aResponse) { var bundle = new objj_bundle(); @@ -236,45 +269,52 @@ function main() objj_bundles[aResponse.filePath] = bundle; } + // ************************************************** + + var applicationScript = []; // add fake bundle response bookkeeping - applicationJS.push("var __fakeDidReceiveBundleResponse = " + String(fakeDidReceiveBundleResponse)); - applicationJS.push("var __fakeBundleArchives = " + JSON.stringify(bundleArchives) + ";"); - applicationJS.push("for (var i = 0; i < __fakeBundleArchives.length; i++) __fakeDidReceiveBundleResponse(__fakeBundleArchives[i]);") + applicationScript.push("(function() {") + applicationScript.push(" var didReceiveBundleResponse = " + String(fakeDidReceiveBundleResponse)); + applicationScript.push(" var bundleArchiveResponses = " + JSON.stringify(bundleArchiveResponses) + ";"); + applicationScript.push(" for (var i = 0; i < bundleArchiveResponses.length; i++)"); + applicationScript.push(" didReceiveBundleResponse(bundleArchiveResponses[i]);"); + applicationScript.push("})();"); // add each fragment, wrapped in a function, along with OBJJ_CURRENT_BUNDLE bookkeeping - for (var i = 0; i < evaledFragments.length; i++) - { - if (requiredFiles[evaledFragments[i].file.path]) + evaledFragments.forEach(function(fragment) { + if (requiredFiles[fragment.file.path]) { - applicationJS.push("(function() {"); - applicationJS.push("var OBJJ_CURRENT_BUNDLE = objj_bundles['"+pathRelativeTo(evaledFragments[i].bundle.path, rootDirectory)+"'];"); - applicationJS.push(evaledFragments[i].info); - applicationJS.push("})();"); + applicationScript.push("(function(OBJJ_CURRENT_BUNDLE) {"); + applicationScript.push(fragment.info); + applicationScript.push("})(objj_bundles['"+relativeToRootPath(fragment.bundle.path)+"']);"); } else { - CPLog.info("Stripping " + evaledFragments[i].file.path); + CPLog.info("Stripping " + relativeToRootPath(fragment.file.path)); } - } + }); - // call main once the page has loaded - applicationJS.push( - "if (window.addEventListener) \ - window.addEventListener('load', function(){main()}, false); \ - else if (window.attachEvent) \ - window.attachEvent('onload', function(){main()});" - ); + // call main once the page has loaded. FIXME: assumes synchronous script loading? + applicationScript.push("if (window.addEventListener)"); + applicationScript.push(" window.addEventListener('load', main, false);") + applicationScript.push("else if (window.attachEvent)") + applicationScript.push(" window.attachEvent('onload', main);"); + + var indexHTML = FILE.read(FILE.join(rootPath, "index.html"), { charset : "UTF-8" }); // comment out any OBJJ_MAIN_FILE defintions or objj_import() calls indexHTML = indexHTML.replace(/(\bOBJJ_MAIN_FILE\s*=|\bobjj_import\s*\()/g, '//$&'); + var applicationScriptName = "Application-"+environment+".js"; + var indexHTMLName = "index-"+environment+".html"; + // add a script tag for Application.js at the very end of the block - indexHTML = indexHTML.replace(/([ \t]*)(<\/head>)/, '$1 \n$1$2'); + indexHTML = indexHTML.replace(/([ \t]*)(<\/head>)/, '$1 \n$1$2'); // output Application.js and index.html - outputFiles[rootDirectory + "/Application.js"] = applicationJS.join("\n"); - outputFiles[rootDirectory + "/index.html"] = indexHTML; + outputFiles[rootPath.join(applicationScriptName)] = applicationScript.join("\n"); + outputFiles[rootPath.join(indexHTMLName)] = indexHTML; } else { @@ -282,43 +322,40 @@ function main() CPLog.error("PHASE 3b: Rebuild .sj"); var bundles = {}; - + for (var path in requiredFiles) { var file = scope.objj_files[path], - filename = basename(path), - directory = dirname(path); - + filename = FILE.basename(path), + directory = FILE.dirname(path); + if (file.path != path) CPLog.warn("Sanity check failed (file path): " + file.path + " vs. " + path); - + if (file.bundle) { - var bundleDirectory = dirname(file.bundle.path); - + var bundleDirectory = FILE.path(file.bundle.path).dirname(); + if (!bundles[file.bundle.path]) bundles[file.bundle.path] = file.bundle; - + if (bundleDirectory != directory) CPLog.warn("Sanity check failed (directory path): " + directory + " vs. " + bundleDirectory); - + // if it's in a .sj var dict = file.bundle.info, - replacedFiles = [dict objectForKey:"CPBundleReplacedFiles"]; + bundlePlatforms = [dict objectForKey:"CPBundlePlatforms"], + replacedFilePlatforms = [dict objectForKey:"CPBundleReplacedFiles"]; + + // compute the platform used for this bundle + var platform = ""; + if (bundlePlatforms) + platform = [bundlePlatforms firstObjectCommonWithArray:scope.OBJJ_PLATFORMS]; + + var replacedFiles = [replacedFilePlatforms objectForKey:platform]; if (replacedFiles && [replacedFiles containsObject:filename]) { - // compute the platform used for this bundle - var platform = "", - bundlePlatforms = [dict objectForKey:"CPBundlePlatforms"]; - - if (bundlePlatforms) - { - platform = [bundlePlatforms firstObjectCommonWithArray:scope.OBJJ_PLATFORMS]; - if (platform) - platform = platform + ".platform/"; - } - - var staticPath = bundleDirectory + "/" + platform + [dict objectForKey:"CPBundleExecutable"]; + var staticPath = bundleDirectory.join(platform + ".platform", [dict objectForKey:"CPBundleExecutable"]); if (!outputFiles[staticPath]) { outputFiles[staticPath] = []; @@ -366,7 +403,7 @@ function main() } } else - CPLog.info("Ignoring import fragment " + file.fragments[i].info + " in " + path); + CPLog.info("Ignoring import fragment " + file.fragments[i].info + " in " + relativeToRootPath(path)); } else CPLog.error("Unknown fragment type"); @@ -379,7 +416,7 @@ function main() } } else - CPLog.warn("No bundle for " + path) + CPLog.warn("No bundle for " + relativeToRootPath(path)) } // phase 3.5: fix bundle plists @@ -387,11 +424,11 @@ function main() for (var path in bundles) { - var directory = dirname(path), + var directory = FILE.dirname(path), dict = bundles[path].info, replacedFiles = [dict objectForKey:"CPBundleReplacedFiles"]; - CPLog.info("Modifying .sj: " + path); + CPLog.info("Modifying .sj: " + relativeToRootPath(path)); if (replacedFiles) { @@ -415,171 +452,31 @@ function main() outputFiles[path] = CPPropertyListCreateXMLData(dict).string; } } +} + +function pngcrushDirectory(directory) { + var directoryPath = FILE.path(directory); + var pngs = directoryPath.glob("**/*.png"); - // phase 4: copy everything and write out the new files - CPLog.error("PHASE 4: copy to output"); - - var rootDirectoryFile = new Packages.java.io.File(rootDirectory), - outputDirectoryFile = new Packages.java.io.File(outputDirectory); - - // FIXME: intelligently copy only what we need (Resources directories?) - copyDirectory(rootDirectoryFile, outputDirectoryFile, optimizePNG); - - for (var path in outputFiles) - { - var file = new java.io.File(outputDirectoryFile, pathRelativeTo(path, rootDirectory)); + system.stderr.print("Running pngcrush on " + pngs.length + " pngs:"); + pngs.forEach(function(dst) { + var dstPath = directoryPath.join(dst); + var tmpPath = FILE.path(dstPath+".tmp"); - var parent = file.getParentFile(); - if (!parent.exists()) - { - CPLog.warn(parent + " doesn't exist, creating directories."); - parent.mkdirs(); + var p = OS.popen(["pngcrush", "-rem", "alla", "-reduce", /*"-brute",*/ dstPath, tmpPath]); + if (p.wait()) { + CPLog.warn("pngcrush failed. Ensure it's installed and on your PATH."); } - - CPLog.info("Writing out " + file); - - var writer = new java.io.BufferedWriter(new java.io.FileWriter(file)); - - if (typeof outputFiles[path] == "string") - writer.write(outputFiles[path]); - else - writer.write(outputFiles[path].join("")); - - writer.close(); - } -} - -// Helper Utilities - -// TODO: moved elsewhere? - -function copyDirectory(src, dst, optimizePNG) -{ - CPLog.trace("Copying directory " + src); - - dst.mkdirs(); - - var files = src.listFiles(); - for (var i = 0; i < files.length; i++) - { - if (files[i].isFile()) - copyFile(files[i], new Packages.java.io.File(dst, files[i].getName()), optimizePNG); - else if (files[i].isDirectory()) - copyDirectory(files[i], new Packages.java.io.File(dst, files[i].getName()), optimizePNG); - } -} - -function copyFile(src, dst, optimizePNG) -{ - if (optimizePNG && (/.png$/).test(src.getName())) - { - CPLog.warn("Optimizing .png " + src); - exec(["pngcrush", "-rem", "alla", "-reduce", /*"-brute",*/ src.getAbsolutePath(), dst.getAbsolutePath()]); - } - else - { - CPLog.trace("Copying file " + src); - - var input = (new Packages.java.io.FileInputStream(src)).getChannel(), - output = (new Packages.java.io.FileOutputStream(dst)).getChannel(); - - input.transferTo(0, input.size(), output); - - input.close(); - output.close(); - } -} - -function dirname(path) -{ - return path.substring(0, path.lastIndexOf("/")); -} - -function basename(path) -{ - return path.substring(path.lastIndexOf("/") + 1); -} - -function absolutePath(path) -{ - return String((new Packages.java.io.File(path)).getCanonicalPath()); + else { + FILE.move(tmpPath, dstPath); + system.stderr.write(".").flush(); + } + }); + system.stderr.print(""); } function pathRelativeTo(target, relativeTo) { - var components = [], - targetParts = target.split("/"), - relativeParts = relativeTo ? relativeTo.split("/") : []; - - var i = 0; - while (i < targetParts.length) - { - if (targetParts[i] != relativeParts[i]) - break; - i++; - } - - for (var j = i; j < relativeParts.length; j++) - components.push(".."); - - for (var j = i; j < targetParts.length; j++) - components.push(targetParts[j]); - - var result = components.join("/"); - - return result; -} - -function exec() -{ - var printOutput = false; - - var runtime = Packages.java.lang.Runtime.getRuntime() - var p = runtime.exec.apply(runtime, arguments); - - var stdout = new Packages.java.io.BufferedReader(new Packages.java.io.InputStreamReader(p.getInputStream())), - stdoutString = "", - stderr = new Packages.java.io.BufferedReader(new Packages.java.io.InputStreamReader(p.getErrorStream())), - stderrString = ""; - - var done = false; - while (!done) - { - done = true; - if (s = stdout.readLine()) - { - stdoutString += s; - if (printOutput) - CPLog.info("exec: " + s); - done = false; - } - if (s = stderr.readLine()) - { - stderrString += s; - //if (printOutput) - CPLog.warn("exec: " + s); - done = false; - } - } - - var code = p.waitFor(); - - return { code : code, stdout : stdoutString, stderr : stderrString }; -} - -function outputTransformer(os, document, encoding, standalone) -{ - var domSource = new Packages.javax.xml.transform.dom.DOMSource(document); - var streamResult = new Packages.javax.xml.transform.stream.StreamResult(os); - var tf = Packages.javax.xml.transform.TransformerFactory.newInstance(); - - var serializer = tf.newTransformer(); - serializer.setOutputProperty(Packages.javax.xml.transform.OutputKeys.VERSION, "1.0"); - serializer.setOutputProperty(Packages.javax.xml.transform.OutputKeys.INDENT, "yes"); - if (encoding) - serializer.setOutputProperty(Packages.javax.xml.transform.OutputKeys.ENCODING, encoding); - if (standalone) - serializer.setOutputProperty(Packages.javax.xml.transform.OutputKeys.STANDALONE, (standalone ? "yes" : "no")); - - String(serializer.transform(domSource, streamResult)); + // TODO: fix FILE.relative to always treat the source as a directory + return FILE.relative(FILE.join(relativeTo, ""), target); } diff --git a/Tools/press/objj-analysis-tools.j b/Tools/press/objj-analysis-tools.j index 7ab43627f..f9f253fd0 100644 --- a/Tools/press/objj-analysis-tools.j +++ b/Tools/press/objj-analysis-tools.j @@ -1,21 +1,23 @@ -var objjPath = OBJJ_HOME+'/lib/Frameworks/Objective-J/rhino.platform/Objective-J.js', - bridgePath = OBJJ_HOME+'/lib/press/bridge.js', - envPath = "/Users/tlrobinson/280North/git/cappuccino/Tools/press/env.js"; - +var FILE = require("file"); + /* param context includes scope: a global variable containing objj_files hash - processedFiles: hash containing file paths which have already been analyzed + ctx: js context dependencies: hash mapping from paths to an array of global variables defined by that file [importCallback]: callback function that is called for each imported file (takes importing file path, and imported file path parameters) [referencedCallback]: callback function that is called for each referenced file (takes referencing file path, referenced file path parameters, and list of tokens) [importedFiles]: hash that will contain a mapping of file names to a hash of imported files [referencedFiles]: hash that will contain a mapping of file names to a hash of referenced files (which contains a hash of tokens referenced) + [processedFiles]: hash containing file paths which have already been analyzed param file is an objj_file object containing path, fragments, content, bundle, etc */ function traverseDependencies(context, file) { + if (!context.processedFiles) + context.processedFiles = {}; + if (context.processedFiles[file.path]) return; context.processedFiles[file.path] = true; @@ -23,7 +25,7 @@ function traverseDependencies(context, file) var ignoreImports = false; if (context.ignoreAllImports) { - CPLog.warn("Ignoring all import fragments. ("+file.path+")"); + CPLog.warn("Ignoring all import fragments. ("+context.relativeToRootPath(file.path)+")"); ignoreImports = true; } else if (context.ignoreFrameworkImports) @@ -31,7 +33,7 @@ function traverseDependencies(context, file) var matches = file.path.match(new RegExp("([^\\/]+)\\/([^\\/]+)\\.j$")); // Matches "ZZZ/ZZZ.j" (e.x. AppKit/AppKit.j and Foundation/Foundation.j) if (matches && matches[1] === matches[2]) { - CPLog.warn("Framework import file! Ignoring all import fragments. ("+file.path+")"); + CPLog.warn("Framework import file! Ignoring all import fragments. ("+context.relativeToRootPath(file.path)+")"); ignoreImports = true; } } @@ -40,9 +42,9 @@ function traverseDependencies(context, file) if (!file.fragments) { if (file.included) - CPLog.warn(file.path + " is included but missing fragments"); + CPLog.warn(context.relativeToRootPath(file.path) + " is included but missing fragments"); else - CPLog.warn("Preprocessing " + file.path); + CPLog.warn("Preprocessing " + context.relativeToRootPath(file.path)); file.fragments = objj_preprocess(file.contents, file.bundle, file); } @@ -53,17 +55,18 @@ function traverseDependencies(context, file) if (!context.bundleImages[file.bundle.path]) { - var resourcesFile = new java.io.File(dirname(file.bundle.path) + "/Resources"); - if (resourcesFile.exists()) + var resourcesPath = FILE.path(file.bundle.path).dirname().join("/Resources"); + if (resourcesPath.exists()) { context.bundleImages[file.bundle.path] = {}; - var pngFiles = find(resourcesFile, (new RegExp("\\.png$"))); - for (var i = 0; i < pngFiles.length; i++) - { - var path = pathRelativeTo(pngFiles[i].getCanonicalPath(), resourcesFile.getCanonicalPath()); - context.bundleImages[file.bundle.path][path] = 1; - } + resourcesPath.glob("**/*.png").forEach(function(png) { + var pngPath = resourcesPath.join(png); + var relativePath = pathRelativeTo(pngPath.absolute(), resourcesPath.absolute()); + + // this is used as a bit mask, not a boolean + context.bundleImages[file.bundle.path][relativePath] = 1; + }); } } var images = context.bundleImages[file.bundle.path]; @@ -71,7 +74,7 @@ function traverseDependencies(context, file) var referencedFiles = {}, importedFiles = {}; - CPLog.debug("Processing " + file.path + " fragments ("+file.fragments.length+")"); + CPLog.debug("Processing " + file.fragments.length + " fragments in " + context.relativeToRootPath(file.path)); for (var i = 0; i < file.fragments.length; i++) { var fragment = file.fragments[i]; @@ -119,10 +122,10 @@ function traverseDependencies(context, file) if (importedFile != file.path) importedFiles[importedFile] = true; else - CPLog.error("Ignoring self import (why are you importing yourself!?): " + file.path); + CPLog.error("Ignoring self import (why are you importing yourself?!): " + context.relativeToRootPath(file.path)); } else - CPLog.error("Couldn't find file for import " + fragment.info + "("+fragment.type+")"); + CPLog.error("Couldn't find file for import " + fragment.info + " ("+fragment.type+")"); } } } @@ -197,11 +200,9 @@ function findImportInObjjFiles(scope, fragment) } // given a fresh scope and the path to a root source file, determine which files define each global variable -function findGlobalDefines(context, scope, rootPath, evaledFragments) +function findGlobalDefines(context, mainPath, evaledFragments) { - addMockBrowserEnvironment(scope); - - var ignore = cloneProperties(scope, true); + var ignore = cloneProperties(context.scope, true); ignore['bundle'] = true; var dependencies = {}; @@ -217,18 +218,18 @@ function findGlobalDefines(context, scope, rootPath, evaledFragments) //} // OVERRIDE fragment_evaluate_file - var fragment_evaluate_file_original = scope.fragment_evaluate_file; - scope.fragment_evaluate_file = function(aFragment) { + var fragment_evaluate_file_original = context.scope.fragment_evaluate_file; + context.scope.fragment_evaluate_file = function(aFragment) { return fragment_evaluate_file_original(aFragment); } // OVERRIDE fragment_evaluate_code - var fragment_evaluate_code_original = scope.fragment_evaluate_code; - scope.fragment_evaluate_code = function(aFragment) { + var fragment_evaluate_code_original = context.scope.fragment_evaluate_code; + context.scope.fragment_evaluate_code = function(aFragment) { - CPLog.debug("Evaling "+aFragment.file.path + " / " + aFragment.bundle.path); + CPLog.debug("Evaluating " + context.relativeToRootPath(aFragment.file.path) + " (" + context.relativeToRootPath(aFragment.bundle.path) + ")"); - var before = cloneProperties(scope); + var before = cloneProperties(context.scope); if (evaledFragments) { @@ -238,20 +239,23 @@ function findGlobalDefines(context, scope, rootPath, evaledFragments) var result = fragment_evaluate_code_original(aFragment); var definedGlobals = {}; - diff(before, scope, ignore, definedGlobals, definedGlobals, null); + diff(before, context.scope, ignore, definedGlobals, definedGlobals, null); dependencies[aFragment.file.path] = definedGlobals; return result; } - - runWithScope(context, scope, function(importName) { + runWithScope(context, function(importName) { objj_import(importName, true, NULL); - }, [rootPath]); + }, [mainPath]); return dependencies; } +// takes a hash mapping from file names to hashes of global names defined in each file +// globals = { fileName { globalName : true }} +// returns a hash mapping from global names to arrays of file names in which those globals are defined +// dependencies = { globalName : [fileName] } function coalesceGlobalDefines(globals) { var dependencies = {}; @@ -269,69 +273,70 @@ function coalesceGlobalDefines(globals) return dependencies; } -// create a new scope loaded with Objective-J -function makeObjjScope(context, debug) +// create a new scope loaded with Narwhal and Objective-J +function makeObjjScope(ctx, debug) { - // init standard js scope objects - var scope = context.initStandardObjects(); - - if (debug) - { - scope.objj_alert = print; - scope.debug = true; - } + // init standard JS scope objects + var scope = ctx.initStandardObjects(); - // give the scope "print" - scope.print = function(value) { Packages.java.lang.System.out.println(String(value)); }; + // set these properties required for Narwhal bootstrapping + scope.NARWHAL_HOME = system.prefix; + scope.NARWHAL_ENGINE_HOME = FILE.join(system.prefix, "engines", "rhino"); - // load and eval fake browser environment - //var envSource = readFile(envPath); - //if (envSource) - // context.evaluateString(scope, envSource, "env.js", 1, null); - //else - // CPLog.warn("Missing env.js"); - - // load and eval the bridge - var bridgeSource = readFile(bridgePath); - if (bridgeSource) - context.evaluateString(scope, bridgeSource, "bridge.js", 1, null); - else - CPLog.warn("Missing bridge.js"); - - // load and eval obj-j - var objjSource = readFile(objjPath); - if (objjSource) - context.evaluateString(scope, objjSource, "Objective-J.js", 1, null); - else - CPLog.warn("Missing Objective-J.js"); + // load the bootstrap.js for narwhal-rhino + var bootstrapPath = FILE.join(scope.NARWHAL_ENGINE_HOME, "bootstrap.js"); + ctx.evaluateReader(scope, + new Packages.java.io.FileReader(bootstrapPath), + "bootstrap.js", + 1, + null + ); - return scope; -} - -// run a function within the given scope (func can be a function object if the source of the function is returned by toString() as it is by default) -function runWithScope(context, scope, func, arguments) -{ - scope.__runWithScopeArgs = arguments || []; - - var code = "("+func+").apply(this, this.__runWithScopeArgs); serviceTimeouts();"; - - return context.evaluateString(scope, code, "", 1, null); + // get the Objective-J module from this scope, return the window object. + var OBJJ = scope.require("objective-j"); + + addMockBrowserEnvironment(OBJJ.window); + + return OBJJ.window; } // add a mock browser environment to the provided scope function addMockBrowserEnvironment(scope) { // TODO: complete this. or use env.js? - - scope.Element = function() { - this.style = {} - } - - scope.document = { - createElement : function() { - return new scope.Element(); + + if (!scope.window) + scope.window = scope; + + if (!scope.location) + scope.location = {}; + + if (!scope.location.href) + scope.location.href = ""; + + if (!scope.Element) + scope.Element = function() { + this.style = {} } - } + + if (!scope.document) + scope.document = { + createElement : function() { + return new scope.Element(); + } + } +} + +// run a function within the given scope (func can be a function object if the source of the function is returned by toString() as it is by default) +function runWithScope(context, func, args) +{ + var functionInScope = context.ctx.compileFunction(context.scope, String(func), "", 1, null); + + var result = functionInScope.apply(context.scope, args); + + context.scope.require('browser/timeout').serviceTimeouts(); + + return result; } // does a shallow copy of an object. if onlyList is true, it sets each property to "true" instead of the actual value @@ -363,17 +368,3 @@ function allKeys(object) result.push(i) return result.sort(); } - -function find(src, regex) -{ - var results = []; - var files = src.listFiles(); - for (var i = 0; i < files.length; i++) - { - if (files[i].isFile() && regex.test(files[i].getAbsolutePath())) - results.push(files[i]); - else if (files[i].isDirectory()) - results = Array.prototype.concat.apply(results, find(files[i], regex)); - } - return results; -} \ No newline at end of file