From c0400d636242ddda2ccf6bdb1b2956a9dbfada4d Mon Sep 17 00:00:00 2001 From: Ross Boucher Date: Mon, 27 Jul 2009 13:16:31 -0700 Subject: [PATCH] Fix a bug where press was clobbering CPApplication arguments. Also changed line endings to Mac/Unix from Windows. --- AppKit/CPApplication.j | 12 +- Tools/press/main.j | 1170 ++++++++++++++++++++-------------------- 2 files changed, 593 insertions(+), 589 deletions(-) diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index f150bb026..dd71a604c 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -772,14 +772,18 @@ function CPApplicationMain(args, namedArgs) [principalClass sharedApplication]; //FIXME? - if (!args && !namedArgs) + if (!args) { - var args = [CPApp arguments], - searchParams = window.location.search.substring(1).split("&"); - namedArgs = [CPDictionary dictionary]; + var args = [CPApp arguments]; if([args containsObject:"debug"]) CPLogRegister(CPLogPopup); + } + + if (!namedArgs) + { + var searchParams = window.location.search.substring(1).split("&"); + namedArgs = [CPDictionary dictionary]; for(var i=0; i - -@import "objj-analysis-tools.j" - -var defaultMain = "main.j", - defaultFrameworks = "Frameworks"; - -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"; - -function main() -{ - var rootDirectory = null, - outputDirectory = null, - mainFilename = null, - frameworksDirectory = null, - platforms = ['browser', 'objj'], - optimizePNG = false, - flatten = false, - noStrip = false, - verbose = false; - - 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); - return; - } - - rootDirectory = absolutePath(rootDirectory); - - // determine main and frameworks paths - var mainPath = rootDirectory + "/" + (mainFilename || defaultMain), - frameworksPath = rootDirectory + "/" + (frameworksDirectory || defaultFrameworks); - - CPLog.info("Application root: " + rootDirectory); - CPLog.info("Output directory: " + outputDirectory); - CPLog.info("Main file: " + mainPath) - CPLog.info("Frameworks: " + frameworksPath); - - // get a Rhino context - var cx = Packages.org.mozilla.javascript.Context.getCurrentContext(), - scope = makeObjjScope(cx); - - // set OBJJ_INCLUDE_PATHS to include the frameworks path - scope.OBJJ_INCLUDE_PATHS = [frameworksPath]; - scope.OBJJ_PLATFORMS = platforms; - - // flattening bookkeeping. keep track of the bundles and evaled code (in the correct order!) - var bundleArchives = [], - evaledFragments = []; - - scope.objj_search.prototype.didReceiveBundleResponseOriginal = scope.objj_search.prototype.didReceiveBundleResponse; - scope.objj_search.prototype.didReceiveBundleResponse = function(aResponse) { - //CPLog.trace("RESPONSE: " + aResponse); - - var __fakeResponse = { - success : aResponse.success, - filePath : pathRelativeTo(aResponse.filePath, rootDirectory) - }; - - 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)); - } - - bundleArchives.push(__fakeResponse); - - this.didReceiveBundleResponseOriginal.apply(this, arguments); - } - - // phase 1: get global defines - CPLog.error("PHASE 1: Loading application..."); - - var globals = findGlobalDefines(cx, scope, mainPath, evaledFragments); - - // coalesce the results - var dependencies = coalesceGlobalDefines(globals); - - // Log - CPLog.trace("Global defines:"); - for (var i in dependencies) - CPLog.trace(" " + i + " => " + 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) - { - // all files are required. no need for analysis - requiredFiles = scope.objj_files; - } - else - { - if (!scope.objj_files[mainPath]) - { - CPLog.error("Root file not loaded!"); - return; - } - - 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; - } - } - - requiredFiles[mainPath] = true; - traverseDependencies(context, scope.objj_files[mainPath]); - - var count = 0, - total = 0; - for (var path in scope.objj_files) - { - if (requiredFiles[path]) - { - CPLog.debug("Included: " + path); - count++; - } - else - { - CPLog.info("Excluded: " + path); - } - total++; - } - CPLog.warn("Total required files: " + count + " out of " + total); - - // FIXME: sprite images - //for (var i in context.bundleImages) - //{ - // var images = context.bundleImages[i]; - // - // CPLog.debug("Bundle images for " + i); - // for (var j in images) - // CPLog.trace(j + " = " + images[j]); - //} - } - - var outputFiles = {}; - - if (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. - var fakeDidReceiveBundleResponse = function(aResponse) - { - var bundle = new objj_bundle(); - - bundle.path = aResponse.filePath; - - if (aResponse.success) - { - var data = new objj_data(); - data.string = aResponse.text; - bundle.info = CPPropertyListCreateFrom280NorthData(data); - //bundle.info = CPPropertyListCreateFromXMLData({ string : aResponse.xml }); - } - else - bundle.info = new objj_dictionary(); - - objj_bundles[aResponse.filePath] = bundle; - } - - // 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]);") - - // 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]) - { - applicationJS.push("(function() {"); - applicationJS.push("var OBJJ_CURRENT_BUNDLE = objj_bundles['"+pathRelativeTo(evaledFragments[i].bundle.path, rootDirectory)+"'];"); - applicationJS.push(evaledFragments[i].info); - applicationJS.push("})();"); - } - else - { - CPLog.info("Stripping " + evaledFragments[i].file.path); - } - } - - // call main once the page has loaded - applicationJS.push( - "if (window.addEventListener) \ - window.addEventListener('load', main, false); \ - else if (window.attachEvent) \ - window.attachEvent('onload', main);" - ); - - // comment out any OBJJ_MAIN_FILE defintions or objj_import() calls - indexHTML = indexHTML.replace(/(\bOBJJ_MAIN_FILE\s*=|\bobjj_import\s*\()/g, '//$&'); - - // add a script tag for Application.js at the very end of the block - 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; - } - else - { - // phase 3b: rebuild .sj files with correct imports, copy .j files - 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); - - if (file.path != path) - CPLog.warn("Sanity check failed (file path): " + file.path + " vs. " + path); - - if (file.bundle) - { - var bundleDirectory = dirname(file.bundle.path); - - 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"]; - 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"]; - if (!outputFiles[staticPath]) - { - outputFiles[staticPath] = []; - outputFiles[staticPath].push("@STATIC;1.0;"); - } - outputFiles[staticPath].push("p;"); - outputFiles[staticPath].push(filename.length+";"); - outputFiles[staticPath].push(filename); - - for (var i = 0; i < file.fragments.length; i++) - { - if (file.fragments[i].type & FRAGMENT_CODE) - { - outputFiles[staticPath].push("c;"); - outputFiles[staticPath].push(file.fragments[i].info.length+";"); - outputFiles[staticPath].push(file.fragments[i].info); - } - else if (file.fragments[i].type & FRAGMENT_FILE) - { - var ignoreFragment = false; - if (file.fragments[i].conditionallyIgnore) - { - var importPath = findImportInObjjFiles(scope, file.fragments[i]); - if (!importPath || !requiredFiles[importPath]) - { - ignoreFragment = true; - } - } - - if (!ignoreFragment) - { - if (file.fragments[i].type & FRAGMENT_LOCAL) - { - var relativePath = pathRelativeTo(file.fragments[i].info, directory) - - outputFiles[staticPath].push("i;"); - outputFiles[staticPath].push(relativePath.length+";"); - outputFiles[staticPath].push(relativePath); - } - else - { - outputFiles[staticPath].push("I;"); - outputFiles[staticPath].push(file.fragments[i].info.length+";"); - outputFiles[staticPath].push(file.fragments[i].info); - } - } - else - CPLog.info("Ignoring import fragment " + file.fragments[i].info + " in " + path); - } - else - CPLog.error("Unknown fragment type"); - } - } - // always output individual .j files - else - { - outputFiles[path] = file.contents; - } - } - else - CPLog.warn("No bundle for " + path) - } - - // phase 3.5: fix bundle plists - CPLog.error("PHASE 3.5: fix bundle plists"); - - for (var path in bundles) - { - var directory = dirname(path), - dict = bundles[path].info, - replacedFiles = [dict objectForKey:"CPBundleReplacedFiles"]; - - CPLog.info("Modifying .sj: " + path); - - if (replacedFiles) - { - var newReplacedFiles = []; - [dict setObject:newReplacedFiles forKey:"CPBundleReplacedFiles"]; - - for (var i = 0; i < replacedFiles.length; i++) - { - var replacedFilePath = directory + "/" + replacedFiles[i] - if (!requiredFiles[replacedFilePath]) - { - CPLog.info("Removing: " + replacedFiles[i]); - } - else - { - //CPLog.info("Keeping: " + replacedFiles[i]); - newReplacedFiles.push(replacedFiles[i]); - } - } - } - outputFiles[path] = CPPropertyListCreateXMLData(dict).string; - } - } - - // 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)); - - var parent = file.getParentFile(); - if (!parent.exists()) - { - CPLog.warn(parent + " doesn't exist, creating directories."); - parent.mkdirs(); - } - - 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()); -} - -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)); -} +@import + +@import "objj-analysis-tools.j" + +var defaultMain = "main.j", + defaultFrameworks = "Frameworks"; + +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"; + +function main() +{ + var rootDirectory = null, + outputDirectory = null, + mainFilename = null, + frameworksDirectory = null, + platforms = ['browser', 'objj'], + optimizePNG = false, + flatten = false, + noStrip = false, + verbose = false; + + 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); + return; + } + + rootDirectory = absolutePath(rootDirectory); + + // determine main and frameworks paths + var mainPath = rootDirectory + "/" + (mainFilename || defaultMain), + frameworksPath = rootDirectory + "/" + (frameworksDirectory || defaultFrameworks); + + CPLog.info("Application root: " + rootDirectory); + CPLog.info("Output directory: " + outputDirectory); + CPLog.info("Main file: " + mainPath) + CPLog.info("Frameworks: " + frameworksPath); + + // get a Rhino context + var cx = Packages.org.mozilla.javascript.Context.getCurrentContext(), + scope = makeObjjScope(cx); + + // set OBJJ_INCLUDE_PATHS to include the frameworks path + scope.OBJJ_INCLUDE_PATHS = [frameworksPath]; + scope.OBJJ_PLATFORMS = platforms; + + // flattening bookkeeping. keep track of the bundles and evaled code (in the correct order!) + var bundleArchives = [], + evaledFragments = []; + + scope.objj_search.prototype.didReceiveBundleResponseOriginal = scope.objj_search.prototype.didReceiveBundleResponse; + scope.objj_search.prototype.didReceiveBundleResponse = function(aResponse) { + //CPLog.trace("RESPONSE: " + aResponse); + + var __fakeResponse = { + success : aResponse.success, + filePath : pathRelativeTo(aResponse.filePath, rootDirectory) + }; + + 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)); + } + + bundleArchives.push(__fakeResponse); + + this.didReceiveBundleResponseOriginal.apply(this, arguments); + } + + // phase 1: get global defines + CPLog.error("PHASE 1: Loading application..."); + + var globals = findGlobalDefines(cx, scope, mainPath, evaledFragments); + + // coalesce the results + var dependencies = coalesceGlobalDefines(globals); + + // Log + CPLog.trace("Global defines:"); + for (var i in dependencies) + CPLog.trace(" " + i + " => " + 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) + { + // all files are required. no need for analysis + requiredFiles = scope.objj_files; + } + else + { + if (!scope.objj_files[mainPath]) + { + CPLog.error("Root file not loaded!"); + return; + } + + 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; + } + } + + requiredFiles[mainPath] = true; + traverseDependencies(context, scope.objj_files[mainPath]); + + var count = 0, + total = 0; + for (var path in scope.objj_files) + { + if (requiredFiles[path]) + { + CPLog.debug("Included: " + path); + count++; + } + else + { + CPLog.info("Excluded: " + path); + } + total++; + } + CPLog.warn("Total required files: " + count + " out of " + total); + + // FIXME: sprite images + //for (var i in context.bundleImages) + //{ + // var images = context.bundleImages[i]; + // + // CPLog.debug("Bundle images for " + i); + // for (var j in images) + // CPLog.trace(j + " = " + images[j]); + //} + } + + var outputFiles = {}; + + if (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. + var fakeDidReceiveBundleResponse = function(aResponse) + { + var bundle = new objj_bundle(); + + bundle.path = aResponse.filePath; + + if (aResponse.success) + { + var data = new objj_data(); + data.string = aResponse.text; + bundle.info = CPPropertyListCreateFrom280NorthData(data); + //bundle.info = CPPropertyListCreateFromXMLData({ string : aResponse.xml }); + } + else + bundle.info = new objj_dictionary(); + + objj_bundles[aResponse.filePath] = bundle; + } + + // 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]);") + + // 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]) + { + applicationJS.push("(function() {"); + applicationJS.push("var OBJJ_CURRENT_BUNDLE = objj_bundles['"+pathRelativeTo(evaledFragments[i].bundle.path, rootDirectory)+"'];"); + applicationJS.push(evaledFragments[i].info); + applicationJS.push("})();"); + } + else + { + CPLog.info("Stripping " + evaledFragments[i].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()});" + ); + + // comment out any OBJJ_MAIN_FILE defintions or objj_import() calls + indexHTML = indexHTML.replace(/(\bOBJJ_MAIN_FILE\s*=|\bobjj_import\s*\()/g, '//$&'); + + // add a script tag for Application.js at the very end of the block + 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; + } + else + { + // phase 3b: rebuild .sj files with correct imports, copy .j files + 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); + + if (file.path != path) + CPLog.warn("Sanity check failed (file path): " + file.path + " vs. " + path); + + if (file.bundle) + { + var bundleDirectory = dirname(file.bundle.path); + + 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"]; + 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"]; + if (!outputFiles[staticPath]) + { + outputFiles[staticPath] = []; + outputFiles[staticPath].push("@STATIC;1.0;"); + } + outputFiles[staticPath].push("p;"); + outputFiles[staticPath].push(filename.length+";"); + outputFiles[staticPath].push(filename); + + for (var i = 0; i < file.fragments.length; i++) + { + if (file.fragments[i].type & FRAGMENT_CODE) + { + outputFiles[staticPath].push("c;"); + outputFiles[staticPath].push(file.fragments[i].info.length+";"); + outputFiles[staticPath].push(file.fragments[i].info); + } + else if (file.fragments[i].type & FRAGMENT_FILE) + { + var ignoreFragment = false; + if (file.fragments[i].conditionallyIgnore) + { + var importPath = findImportInObjjFiles(scope, file.fragments[i]); + if (!importPath || !requiredFiles[importPath]) + { + ignoreFragment = true; + } + } + + if (!ignoreFragment) + { + if (file.fragments[i].type & FRAGMENT_LOCAL) + { + var relativePath = pathRelativeTo(file.fragments[i].info, directory) + + outputFiles[staticPath].push("i;"); + outputFiles[staticPath].push(relativePath.length+";"); + outputFiles[staticPath].push(relativePath); + } + else + { + outputFiles[staticPath].push("I;"); + outputFiles[staticPath].push(file.fragments[i].info.length+";"); + outputFiles[staticPath].push(file.fragments[i].info); + } + } + else + CPLog.info("Ignoring import fragment " + file.fragments[i].info + " in " + path); + } + else + CPLog.error("Unknown fragment type"); + } + } + // always output individual .j files + else + { + outputFiles[path] = file.contents; + } + } + else + CPLog.warn("No bundle for " + path) + } + + // phase 3.5: fix bundle plists + CPLog.error("PHASE 3.5: fix bundle plists"); + + for (var path in bundles) + { + var directory = dirname(path), + dict = bundles[path].info, + replacedFiles = [dict objectForKey:"CPBundleReplacedFiles"]; + + CPLog.info("Modifying .sj: " + path); + + if (replacedFiles) + { + var newReplacedFiles = []; + [dict setObject:newReplacedFiles forKey:"CPBundleReplacedFiles"]; + + for (var i = 0; i < replacedFiles.length; i++) + { + var replacedFilePath = directory + "/" + replacedFiles[i] + if (!requiredFiles[replacedFilePath]) + { + CPLog.info("Removing: " + replacedFiles[i]); + } + else + { + //CPLog.info("Keeping: " + replacedFiles[i]); + newReplacedFiles.push(replacedFiles[i]); + } + } + } + outputFiles[path] = CPPropertyListCreateXMLData(dict).string; + } + } + + // 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)); + + var parent = file.getParentFile(); + if (!parent.exists()) + { + CPLog.warn(parent + " doesn't exist, creating directories."); + parent.mkdirs(); + } + + 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()); +} + +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)); +}