| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.royale.compiler.internal.codegen.mxml.royale; |
| |
| import com.google.common.io.Files; |
| import com.google.javascript.jscomp.SourceFile; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.io.FilenameUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.io.filefilter.DirectoryFileFilter; |
| import org.apache.commons.io.filefilter.FileFileFilter; |
| import org.apache.commons.io.filefilter.FileFilterUtils; |
| import org.apache.commons.io.filefilter.IOFileFilter; |
| import org.apache.royale.compiler.clients.problems.ProblemQuery; |
| import org.apache.royale.compiler.codegen.js.goog.IJSGoogPublisher; |
| import org.apache.royale.compiler.config.Configuration; |
| import org.apache.royale.compiler.css.ICSSPropertyValue; |
| import org.apache.royale.compiler.definitions.IClassDefinition; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.metadata.IMetaTag; |
| import org.apache.royale.compiler.filespecs.IFileSpecification; |
| import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogPublisher; |
| import org.apache.royale.compiler.internal.codegen.js.goog.JarSourceFile; |
| import org.apache.royale.compiler.internal.css.CSSArrayPropertyValue; |
| import org.apache.royale.compiler.internal.css.CSSFontFace; |
| import org.apache.royale.compiler.internal.css.CSSFunctionCallPropertyValue; |
| import org.apache.royale.compiler.internal.driver.js.royale.JSCSSCompilationSession; |
| import org.apache.royale.compiler.internal.driver.js.goog.JSGoogConfiguration; |
| import org.apache.royale.compiler.internal.graph.GoogDepsWriter; |
| import org.apache.royale.compiler.internal.projects.RoyaleJSProject; |
| import org.apache.royale.compiler.internal.scopes.ASProjectScope.DefinitionPromise; |
| import org.apache.royale.compiler.internal.targets.ITargetAttributes; |
| import org.apache.royale.compiler.utils.JSClosureCompilerWrapper; |
| import org.apache.royale.swc.ISWC; |
| import org.apache.royale.swc.ISWCFileEntry; |
| import org.apache.royale.swc.ISWCManager; |
| |
| import java.io.*; |
| import java.net.URL; |
| import java.net.URLDecoder; |
| import java.nio.charset.Charset; |
| import java.util.*; |
| import java.util.regex.Matcher; |
| |
| public class MXMLRoyalePublisher extends JSGoogPublisher implements IJSGoogPublisher |
| { |
| |
| public static final String ROYALE_OUTPUT_DIR_NAME = "bin"; |
| public static final String ROYALE_INTERMEDIATE_DIR_NAME = "js-debug"; |
| public static final String ROYALE_RELEASE_DIR_NAME = "js-release"; |
| |
| private static final String ROYALE_EXTERNS = "externs"; |
| private static final String ROYALE_THEME_ASSETS = "assets/"; |
| |
| class DependencyRecord |
| { |
| String path; |
| String deps; |
| String line; |
| int lineNumber; |
| } |
| |
| class DependencyLineComparator implements Comparator<DependencyRecord> |
| { |
| @Override |
| public int compare(DependencyRecord o1, DependencyRecord o2) |
| { |
| return new Integer(o1.lineNumber).compareTo(o2.lineNumber); |
| } |
| } |
| |
| public MXMLRoyalePublisher(RoyaleJSProject project, Configuration config) |
| { |
| super(project, config); |
| this.isMarmotinniRun = googConfiguration.getMarmotinni() != null; |
| this.outputPathParameter = configuration.getOutput(); |
| this.moduleOutput = googConfiguration.getModuleOutput(); |
| this.useStrictPublishing = googConfiguration.getStrictPublish(); |
| |
| this.project = project; |
| } |
| |
| protected RoyaleJSProject project; |
| |
| private boolean isMarmotinniRun; |
| private String outputPathParameter; |
| private String moduleOutput; |
| private boolean useStrictPublishing; |
| private List<String> additionalHTML = new ArrayList<String>(); |
| private Set<String> closurePropertyNamesToKeep; |
| private Set<String> closureSymbolNamesToExport; |
| |
| private GoogDepsWriter getGoogDepsWriter(File intermediateDir, |
| String mainClassQName, |
| JSGoogConfiguration googConfiguration, |
| List<ISWC> swcs) |
| { |
| return new GoogDepsWriter(intermediateDir, mainClassQName, googConfiguration, swcs); |
| } |
| |
| @Override |
| public File getOutputFolder() |
| { |
| // Marmotinni is our test-framework. In case of a Marmotinni build |
| // we need to output the code to a different location. |
| // FIXME: I think this is a bad idea ... we should remove this. |
| // (erikdebruin) - If there is a -marmotinni switch, we want |
| // the output redirected to the directory it specifies. |
| // - If there is an -output switch, use that path as the |
| // output parent folder. |
| if (isMarmotinniRun) |
| { |
| outputParentFolder = new File(googConfiguration.getMarmotinni()); |
| } |
| // If the output path is specified using the config-xml or the commandline. |
| else if (outputPathParameter != null) |
| { |
| // FB usually specified -output <project-path>/bin-release/app.swf |
| if (outputPathParameter.contains(".swf")) { |
| if (outputParentFolder == null) |
| outputParentFolder = new File(outputPathParameter); |
| if (moduleOutput != null && outputPathParameter.contains(moduleOutput)) |
| { |
| String rootFolder = outputPathParameter.substring(0, outputPathParameter.indexOf(moduleOutput)); |
| if (rootFolder.endsWith("src")) |
| outputParentFolder = new File(rootFolder).getParentFile(); |
| else if (rootFolder.endsWith("src/main/royale") || rootFolder.endsWith("src\\main\\royale")) |
| outputParentFolder = new File(rootFolder).getParentFile().getParentFile().getParentFile(); |
| else |
| outputParentFolder = new File(rootFolder).getParentFile(); |
| } |
| else |
| outputParentFolder = outputParentFolder.getParentFile().getParentFile(); |
| } else { |
| outputParentFolder = new File(outputPathParameter); |
| } |
| } |
| else |
| { |
| String mainClassFolder = configuration.getTargetFileDirectory(); |
| if (mainClassFolder.endsWith("src")) |
| outputParentFolder = new File(configuration.getTargetFileDirectory()).getParentFile(); |
| else if (mainClassFolder.endsWith("src/main/royale") || mainClassFolder.endsWith("src\\main\\royale")) |
| outputParentFolder = new File(configuration.getTargetFileDirectory()).getParentFile().getParentFile().getParentFile(); |
| else if (moduleOutput != null && mainClassFolder.endsWith(moduleOutput)) |
| { |
| String rootFolder = mainClassFolder.replace(mainClassFolder, ""); |
| if (rootFolder.endsWith("src")) |
| outputParentFolder = new File(rootFolder).getParentFile(); |
| else if (rootFolder.endsWith("src/main/royale") || rootFolder.endsWith("src\\main\\royale")) |
| outputParentFolder = new File(rootFolder).getParentFile().getParentFile().getParentFile(); |
| } |
| else |
| outputParentFolder = new File(configuration.getTargetFileDirectory()); |
| } |
| |
| outputParentFolder = new File(outputParentFolder, ROYALE_OUTPUT_DIR_NAME); |
| |
| outputFolder = new File(outputParentFolder, File.separator + ROYALE_INTERMEDIATE_DIR_NAME); |
| if (moduleOutput != null) |
| outputFolder = new File(outputFolder, File.separator + moduleOutput); |
| |
| // (erikdebruin) Marmotinni handles file management, so we |
| // bypass the setup. |
| if (!isMarmotinniRun && !googConfiguration.getSkipTranspile()) { |
| setupOutputFolder(); |
| } |
| |
| return outputFolder; |
| } |
| |
| public void setClosurePropertyNamesToKeep(Set<String> propertyNames) |
| { |
| closurePropertyNamesToKeep = propertyNames; |
| } |
| |
| public void setClosureSymbolNamesToExport(Set<String> symbolNames) |
| { |
| closureSymbolNamesToExport = symbolNames; |
| } |
| |
| @Override |
| public boolean publish(ProblemQuery problems) throws IOException |
| { |
| // The "intermediate" is the "js-debug" output. |
| final File intermediateDir = outputFolder; |
| |
| // The source directory is the source path entry containing the Main class. |
| List<File> sourcePaths = project.getSourcePath(); |
| String targetFile = configuration.getTargetFile().toLowerCase(); |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("find project folder for " + targetFile); |
| } |
| File imageSrcDir = null; |
| for (File sp : sourcePaths) |
| { |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("checking source path " + sp.getAbsolutePath()); |
| } |
| String lowercasePath = sp.getAbsolutePath().toLowerCase(); |
| if (targetFile.startsWith(lowercasePath)) |
| imageSrcDir = sp; |
| } |
| if (imageSrcDir == null) |
| { |
| imageSrcDir = new File(configuration.getTargetFile()).getAbsoluteFile().getParentFile(); |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("not found on source path, using parent file " + imageSrcDir.getAbsolutePath()); |
| } |
| } |
| final String projectName = FilenameUtils.getBaseName(configuration.getTargetFile()); |
| String qName = null; |
| try { |
| qName = project.mainCU.getQualifiedNames().get(0); |
| } catch (InterruptedException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| String mainClassQName = qName; |
| DefinitionPromise cpromise = (DefinitionPromise)project.mainCU.getDefinitionPromises().get(0); |
| IDefinition actualDef = cpromise.getActualDefinition(); |
| IClassDefinition baseDef = null; |
| if(actualDef instanceof IClassDefinition) |
| { |
| IClassDefinition cdef = (IClassDefinition) cpromise.getActualDefinition(); |
| baseDef = (IClassDefinition) project.resolveQNameToDefinition(cdef.getBaseClassAsDisplayString()); |
| } |
| if (baseDef != null) |
| { |
| String factoryClassName = null; |
| IClassDefinition.IClassIterator classIterator = baseDef.classIterator(project, true); |
| while (classIterator.hasNext()) |
| { |
| baseDef = classIterator.next(); |
| if (baseDef.hasMetaTagByName("Frame")) { |
| factoryClassName = getFactoryClass(baseDef.getMetaTagByName("Frame")); |
| break; |
| } |
| } |
| if (factoryClassName != null) |
| { |
| mainClassQName = generateFactoryClass(factoryClassName, projectName, mainClassQName, intermediateDir); |
| } |
| } |
| final String outputFileName = projectName + "." + project.getBackend().getOutputExtension(); |
| |
| // The "release" is the "js-release" directory. |
| File releaseDir = new File(outputParentFolder, ROYALE_RELEASE_DIR_NAME); |
| if (moduleOutput != null) |
| releaseDir = new File(releaseDir, File.separator + moduleOutput); |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Copy static resources to the intermediate (and release) directory. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| IOFileFilter pngSuffixFilter = FileFilterUtils.and(FileFileFilter.FILE, |
| FileFilterUtils.suffixFileFilter(".png")); |
| IOFileFilter gifSuffixFilter = FileFilterUtils.and(FileFileFilter.FILE, |
| FileFilterUtils.suffixFileFilter(".gif")); |
| IOFileFilter jpgSuffixFilter = FileFilterUtils.and(FileFileFilter.FILE, |
| FileFilterUtils.suffixFileFilter(".jpg")); |
| IOFileFilter jpegSuffixFilter = FileFilterUtils.and(FileFileFilter.FILE, |
| FileFilterUtils.suffixFileFilter(".jpeg")); |
| IOFileFilter svgSuffixFilter = FileFilterUtils.and(FileFileFilter.FILE, |
| FileFilterUtils.suffixFileFilter(".svg")); |
| IOFileFilter jsonSuffixFilter = FileFilterUtils.and(FileFileFilter.FILE, |
| FileFilterUtils.suffixFileFilter(".json")); |
| IOFileFilter assetFiles = FileFilterUtils.or(pngSuffixFilter, jpgSuffixFilter, jpegSuffixFilter, svgSuffixFilter, gifSuffixFilter, |
| jsonSuffixFilter); |
| IOFileFilter resourceFilter = FileFilterUtils.or(DirectoryFileFilter.DIRECTORY, assetFiles); |
| // FIXME: All images need to be located relative to the Main class ... for Maven this is a problem. |
| FileUtils.copyDirectory(imageSrcDir, intermediateDir, resourceFilter); |
| // Iterate over all themes SWCs and add the contents of any included files in |
| // an assets folder to an assets folder in the destination folder. |
| final ISWCManager swcManager = project.getWorkspace().getSWCManager(); |
| List<ISWC> themeSWCs = new ArrayList<ISWC>(); |
| List<IFileSpecification> themes = project.getThemeFiles(); |
| for (final IFileSpecification themeFile : themes) |
| { |
| final String extension = FilenameUtils.getExtension(themeFile.getPath()); |
| if ("swc".equalsIgnoreCase(extension)) |
| { |
| final ISWC swc = swcManager.get(new File(themeFile.getPath())); |
| themeSWCs.add(swc); |
| Map<String, ISWCFileEntry> files = swc.getFiles(); |
| for (String key : files.keySet()) |
| { |
| if (key.startsWith(ROYALE_THEME_ASSETS)) |
| { |
| ISWCFileEntry fileEntry = swc.getFile(key); |
| if (fileEntry != null) |
| { |
| InputStream is = fileEntry.createInputStream(); |
| int n = is.available(); |
| int total = 0; |
| byte[] data = new byte[n]; |
| while (total < n) |
| { |
| total += is.read(data, total, n - total); |
| } |
| FileUtils.writeByteArrayToFile(new File(intermediateDir, key), data); |
| if (configuration.release()) |
| { |
| FileUtils.writeByteArrayToFile(new File(releaseDir, key), data); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // If we are doing a release build, we need to copy them to the release dir too. |
| if (configuration.release()) { |
| FileUtils.copyDirectory(imageSrcDir, releaseDir, resourceFilter); |
| // The copy-directory contains a lot of empty directories ... clean them up. |
| clearEmptyDirectoryTrees(releaseDir); |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Copy / Dump the closure files into the intermediate directory. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| // List of source files we need to pass into the closure compiler. As we have to |
| // read the content in order to dump it to the intermediate, we can just keep it |
| // and eventually use it in case of a release build. |
| List<SourceFile> closureSourceFiles = null; |
| |
| // If the closure lib dir is explicitly set, use that directory. If it |
| // is not set, check if its content is available in the classpath. If |
| // it is found in the classpath, use that as closure lib dir. |
| if (!googConfiguration.isClosureLibSet()) |
| { |
| // Check if the "goog/deps.js" is available in the classpath. |
| File closureLibraryJar = getJarThatContainsClasspathResources("goog/deps.js"); |
| if (closureLibraryJar != null) |
| { |
| // We don't want to add all files to the classpath, so we only output the |
| // resources contained in 'closure-whitelist.properites' to the output. |
| Properties whiteList = new Properties(); |
| whiteList.load(Thread.currentThread().getContextClassLoader().getResourceAsStream( |
| "royale/closure-whitelist.properites")); |
| |
| // Add the closure files from classpath. |
| closureSourceFiles = getClasspathResources(closureLibraryJar, whiteList); |
| } |
| } |
| if (closureSourceFiles == null) |
| { |
| File closureLibDir = new File(googConfiguration.getClosureLib()); |
| if (!closureLibDir.exists() || !closureLibDir.isDirectory()) |
| { |
| //only throw this error if closure-lib is set because it |
| //wouldn't make sense with the default fallback path |
| if (googConfiguration.isClosureLibSet()) |
| { |
| throw new RuntimeException("Parameter 'closure-lib' doesn't point to a valid directory."); |
| } |
| } |
| else |
| { |
| closureSourceFiles = getDirectoryResources(new File(closureLibDir, "closure")); |
| } |
| } |
| if (closureSourceFiles == null || closureSourceFiles.size() == 0) |
| { |
| throw new RuntimeException( |
| "Parameter 'closure-lib' not specified and closure resources not available in classpath."); |
| } |
| // Dump a copy of the closure lib files to the intermediate directory. Without this |
| // the application will not be able to run. |
| for(SourceFile closureSourceFile : closureSourceFiles) { |
| FileUtils.write(new File(new File(intermediateDir, "library/closure"), |
| closureSourceFile.getName()), closureSourceFile.getCode(), Charset.forName("utf8")); |
| } |
| closureSourceFiles = closureFilesInOrder(intermediateDir + "/library/closure/", closureSourceFiles, "goog.events.EventTarget"); |
| |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Prepare the closure compilation. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| JSClosureCompilerWrapper compilerWrapper = null; |
| if (configuration.release()) |
| { |
| compilerWrapper = new JSClosureCompilerWrapper(googConfiguration.getJSCompilerOptions()); |
| compilerWrapper.setPropertyNamesToKeep(closurePropertyNamesToKeep); |
| if (closureSymbolNamesToExport == null) { |
| closureSymbolNamesToExport = new HashSet<String>(); |
| } |
| //the HTML template always needs this name to be exported, even if |
| //other class names are not exported |
| closureSymbolNamesToExport.add(mainClassQName); |
| compilerWrapper.setExtraSymbolNamesToExport(closureSymbolNamesToExport); |
| } |
| |
| if (compilerWrapper != null) |
| { |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Add all the closure lib files to the compilation unit. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| for (SourceFile closureSourceFile : closureSourceFiles) { |
| compilerWrapper.addJSSourceFile(closureSourceFile); |
| } |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Add all the externs to the compilation |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| // Iterate over all swc dependencies and add all the externs they contain. |
| // (Externs are located in a "externs" directory in the root of the SWC) |
| Set<ISWC> swcExterns = project.swcExterns; |
| List<ISWC> swcs = project.getLibraries(); |
| List<ISWC> allswcs = new ArrayList<ISWC>(); |
| allswcs.addAll(swcs); |
| allswcs.addAll(themeSWCs); |
| for (ISWC swc : allswcs) |
| { |
| Map<String, ISWCFileEntry> files = swc.getFiles(); |
| for (String key : files.keySet()) |
| { |
| if (key.startsWith(ROYALE_EXTERNS)) |
| { |
| ISWCFileEntry fileEntry = swc.getFile(key); |
| if (fileEntry != null) |
| { |
| InputStream is = fileEntry.createInputStream(); |
| String code = IOUtils.toString(is, "UTF-8"); |
| is.close(); |
| |
| if (compilerWrapper != null) |
| { |
| JarSourceFile externFile = new JarSourceFile(key, code,true); |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("using extern: " + key); |
| } |
| compilerWrapper.addJSExternsFile(externFile); |
| } |
| |
| if (swcExterns.contains(swc)) |
| { |
| List<String> lines = IOUtils.readLines(new StringReader(code)); |
| collectAdditionalHTML(lines, swc.getSWCFile().getAbsolutePath() + ":" + key); |
| } |
| } |
| } |
| } |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Add all files generated by the compiler to the compilation unit. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| GoogDepsWriter gdw = getGoogDepsWriter(intermediateDir, mainClassQName, googConfiguration, allswcs); |
| // This list contains all files generated by the compiler, this is both the |
| // compiled js files created by the sources of the current project plus the |
| // js files of used dependencies. |
| ArrayList<String> sourceExternFiles = new ArrayList<String>(); |
| ArrayList<String> fileList = gdw.getListOfFiles(project, sourceExternFiles, problems); |
| if (fileList == null) |
| return false; // some error occurred |
| |
| for (String sourceExtern : project.sourceExterns) |
| { |
| String sourceExternFileName = sourceExtern.replace(".", "/") + ".js"; |
| File sourceExternFile = new File(intermediateDir, sourceExternFileName); |
| if (sourceExternFile.exists()) |
| { |
| String sourceExternPath = sourceExternFile.getAbsolutePath(); |
| if (!sourceExternFiles.contains(sourceExternPath)) |
| sourceExternFiles.add(sourceExternPath); |
| } |
| } |
| if (compilerWrapper != null) |
| { |
| for (String file : fileList) |
| { |
| compilerWrapper.addJSSourceFile(file); |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("using source file: " + file); |
| } |
| } |
| } |
| for (String file : sourceExternFiles) |
| { |
| if (compilerWrapper != null) |
| { |
| compilerWrapper.addJSExternsFile(file); |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("using extern file: " + file); |
| } |
| } |
| collectFileAdditionalHTML(file); |
| } |
| additionalHTML.addAll(gdw.additionalHTML); |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Generate the index.html for loading the application. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| // The application needs to import all dependencies the application needs, this |
| // is generated here so it can be used for outputting the html templates. |
| String depsFileData = gdw.generateDeps(project, problems); |
| |
| // FOR MODULES: this generates inject_script lines for js to be added to __deps.js |
| String additionalScript = ""; |
| |
| if (project.isModule(mainClassQName)) |
| { |
| for (String s : additionalHTML) |
| { |
| additionalScript += s.trim() + System.lineSeparator(); |
| } |
| |
| // need better test someday |
| depsFileData += "\ngoog.require('" + mainClassQName + "');\n"; |
| writeFile(new File(intermediateDir, projectName + "__deps.js"), depsFileData + additionalScript + "\n", false); |
| gdw.needCSS = true; |
| if (configuration.release()) { |
| writeFile(new File(releaseDir, projectName + ".js"), additionalScript, false); |
| } |
| } |
| else |
| { |
| File template = ((JSGoogConfiguration)configuration).getHtmlTemplate(); |
| List<String> wrappedScript = new ArrayList<String>(); |
| wrappedScript.add("<script type=\"text/javascript\">"); |
| wrappedScript.addAll(additionalHTML); |
| wrappedScript.add("</script>"); |
| // Create the index.html for the debug-js version. |
| if (!((JSGoogConfiguration)configuration).getSkipTranspile()) { |
| if (template != null) { |
| writeTemplate(template, "intermediate", projectName, mainClassQName, intermediateDir, depsFileData, wrappedScript); |
| } else { |
| writeHTML("intermediate", projectName, mainClassQName, intermediateDir, depsFileData, wrappedScript); |
| } |
| } |
| // Create the index.html for the release-js version. |
| if (configuration.release()) { |
| if (template != null) { |
| writeTemplate(template, "release", projectName, mainClassQName, releaseDir, depsFileData, wrappedScript); |
| } else { |
| writeHTML("release", projectName, mainClassQName, releaseDir, null, wrappedScript); |
| } |
| } |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // Generate or copy the main CSS resources. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| project.needCSS = gdw.needCSS; |
| if (project.needCSS || googConfiguration.getSkipTranspile()) { |
| if (!googConfiguration.getSkipTranspile()) { |
| writeCSS(projectName, intermediateDir, false); |
| } |
| if (project.needCSS && configuration.release()) { |
| // if release version minify css string |
| writeCSS(projectName, releaseDir, true); |
| } |
| } |
| |
| |
| ///////////////////////////////////////////////////////////////////////////////// |
| // If we are doing a release build, let the closure compiler do it's job. |
| ///////////////////////////////////////////////////////////////////////////////// |
| |
| if (compilerWrapper != null) { |
| boolean ok = true; |
| final File projectReleaseMainFile = new File(releaseDir, outputFileName); |
| compilerWrapper.setOptions(projectReleaseMainFile.getCanonicalPath(), useStrictPublishing, !googConfiguration.getRemoveCirculars(), projectName); |
| compilerWrapper.targetFilePath = projectReleaseMainFile.getCanonicalPath(); |
| compilerWrapper.setSourceMap(googConfiguration.getSourceMap()); |
| compilerWrapper.setVerbose(googConfiguration.isVerbose()); |
| compilerWrapper.setPreventRenameMxmlSymbolReferences(googConfiguration.getPreventRenameMxmlSymbolReferences()); |
| |
| ok = compilerWrapper.compile(); |
| |
| // FOR MODULES: add additionalScript to main js release file too |
| if (project.isModule(mainClassQName)) |
| { |
| StringBuilder appendString = new StringBuilder(); |
| appendString.append(additionalScript); |
| writeFile(projectReleaseMainFile, appendString.toString(), true); |
| } |
| |
| appendSourceMapLocation(projectReleaseMainFile, projectName); |
| |
| if (ok) |
| System.out.println("The project '" + projectName + "' has been successfully compiled and optimized."); |
| } |
| else |
| System.out.println("The project '" + projectName + "' has been successfully compiled."); |
| |
| return true; |
| } |
| |
| protected List<SourceFile> closureFilesInOrder(String path, List<SourceFile> files, String entryPoint) |
| { |
| ArrayList<String> sortedFiles = new ArrayList<String>(); |
| HashMap<String, SourceFile> fileMap = new HashMap<String, SourceFile>(); |
| SourceFile depsFile = null; |
| |
| for (SourceFile sourceFile : files) |
| { |
| if ((sourceFile.getOriginalPath().endsWith("goog/deps.js") || sourceFile.getOriginalPath().endsWith("goog\\deps.js")) && |
| !(sourceFile.getOriginalPath().endsWith("third_party/goog/deps.js") || sourceFile.getOriginalPath().endsWith("third_party\\goog\\deps.js"))) |
| depsFile = sourceFile; |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("originalPath: " + sourceFile.getOriginalPath()); |
| } |
| fileMap.put(sourceFile.getOriginalPath(), sourceFile); |
| } |
| |
| ArrayList<String> deps = new ArrayList<String>(); |
| try |
| { |
| BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(path + depsFile.getOriginalPath()), "UTF8")); |
| |
| while (true) |
| { |
| String line = in.readLine(); |
| if (line == null) |
| break; |
| if (line.startsWith("//") || line.trim().length() == 0) |
| continue; |
| deps.add(line); |
| } |
| in.close(); |
| } |
| catch (Exception e) |
| { |
| // nothing to see, move along... |
| } |
| |
| sortClosureFile(deps, entryPoint, sortedFiles); |
| |
| ArrayList<SourceFile> list = new ArrayList<SourceFile>(); |
| ArrayList<String> seen = new ArrayList<String>(); |
| sortedFiles.add("deps.js"); |
| sortedFiles.add("base.js"); |
| // in dual branch, add this to node publisher |
| sortedFiles.add("bootstrap/nodejs.js"); |
| int n = sortedFiles.size(); |
| for (int i = n - 1; i >= 0; i--) |
| { |
| String fileName = sortedFiles.get(i); |
| if (googConfiguration.isVerbose()) |
| { |
| System.out.println("sorted filename: " + fileName); |
| } |
| if (seen.contains(fileName)) |
| continue; |
| seen.add(fileName); |
| |
| SourceFile sf = fileMap.get("goog/" + fileName); |
| if (sf == null) |
| System.out.println("got null for " + fileName); |
| list.add(sf); |
| } |
| return list; |
| } |
| |
| private void collectFileAdditionalHTML(String filePath) |
| { |
| List<String> fileLines; |
| try |
| { |
| fileLines = Files.readLines(new File(filePath), Charset.forName("utf8")); |
| } |
| catch(IOException e) |
| { |
| return; |
| } |
| collectAdditionalHTML(fileLines, filePath); |
| } |
| |
| private void collectAdditionalHTML(List<String> lines, String key) |
| { |
| boolean inDocComment = false; |
| boolean inConstructor = false; |
| boolean inInjectScript = false; |
| for (int i = 0; i < lines.size(); i++) |
| { |
| String line = lines.get(i); |
| if (inDocComment) |
| { |
| if (inInjectScript) |
| { |
| if (line.indexOf("</inject_script>") > -1) |
| { |
| inInjectScript = false; |
| continue; |
| } |
| line = line.trim(); |
| if (line.startsWith("*")) |
| line = line.substring(1); |
| additionalHTML.add(line); |
| continue; |
| } |
| int c = line.indexOf("<inject_script>"); |
| if (c != -1) |
| { |
| inInjectScript = true; |
| continue; |
| } |
| if (!inConstructor) |
| { |
| c = line.indexOf("@constructor"); |
| if(c != -1) |
| { |
| inConstructor = true; |
| continue; |
| } |
| } |
| c = line.indexOf("*/"); |
| if(c != -1) |
| { |
| if(inConstructor) |
| { |
| //we're done |
| break; |
| } |
| inInjectScript = false; |
| inDocComment = false; |
| inConstructor = false; |
| } |
| |
| } |
| else |
| { |
| int c = line.indexOf("/**"); |
| if(c != -1) |
| { |
| inDocComment = true; |
| continue; |
| } |
| } |
| } |
| } |
| |
| private void sortClosureFile(List<String> deps, String entryPoint, List<String> sortedFiles) |
| { |
| String provided = getProvidedFile(deps, entryPoint); |
| sortedFiles.add(provided); |
| List<String> reqs = getRequires(deps, entryPoint); |
| if (reqs == null) return; |
| for (String req : reqs) |
| { |
| sortClosureFile(deps, req, sortedFiles); |
| } |
| } |
| |
| private String getProvidedFile(List<String> deps, String name) |
| { |
| for (String dep : deps) |
| { |
| int open = dep.indexOf("["); |
| int close = dep.indexOf("]", open + 1); |
| String list = dep.substring(open + 1, close); |
| String[] parts = list.split(","); |
| for (String part : parts) |
| { |
| part = part.trim(); |
| if (part.startsWith("'")) |
| { |
| part = part.substring(1, part.length() - 1); |
| } |
| if(part.equals(name)) |
| { |
| open = dep.indexOf("'"); |
| close = dep.indexOf("'", open + 1); |
| return dep.substring(open + 1, close); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private List<String> getRequires(List<String> deps, String name) |
| { |
| for (String dep : deps) |
| { |
| int open = dep.indexOf("["); |
| int close = dep.indexOf("]", open + 1); |
| String list = dep.substring(open + 1, close); |
| String[] parts = list.split(","); |
| for (String part : parts) |
| { |
| part = part.trim(); |
| if (part.startsWith("'")) |
| { |
| part = part.substring(1, part.length() - 1); |
| } |
| |
| if(part.equals(name)) |
| { |
| open = dep.indexOf("[", close + 1); |
| close = dep.indexOf("]", open + 1); |
| if (open + 1 == close) |
| { |
| return null; |
| } |
| String list2 = dep.substring(open + 1, close); |
| String[] parts2 = list2.split(","); |
| ArrayList<String> reqs = new ArrayList<String>(); |
| for (String part2 : parts2) |
| { |
| part2 = part2.trim(); |
| if (part2.startsWith("'")) |
| { |
| part2 = part2.substring(1, part2.length() - 1); |
| } |
| reqs.add(part2); |
| } |
| return reqs; |
| } |
| } |
| } |
| return null; |
| } |
| |
| protected String readCode(File file) |
| { |
| String code = ""; |
| try |
| { |
| BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF8")); |
| |
| String line = in.readLine(); |
| |
| while (line != null) |
| { |
| code += line + "\n"; |
| line = in.readLine(); |
| } |
| code = code.substring(0, code.length() - 1); |
| |
| in.close(); |
| } |
| catch (Exception e) |
| { |
| // nothing to see, move along... |
| } |
| |
| return code; |
| } |
| |
| private String safeReplacement(String source) { |
| return source!=null ? Matcher.quoteReplacement(source) : ""; |
| } |
| |
| protected void writeTemplate(File template, String type, String projectName, String mainClassQName, File targetDir, String deps, List<String> additionalHTML) |
| throws IOException |
| { |
| // Check if the template exists. |
| if(!template.exists()) { |
| throw new IOException("Template specified by 'html-template' does not exist: " + template.getPath()); |
| } |
| |
| String input = readCode(template); |
| ITargetAttributes ta = project.computeTargetAttributes(); |
| Float width = null; |
| Float height = null; |
| String bgcolor = null; |
| String pageTitle = null; |
| if(ta != null) |
| { |
| width = ta.getWidth(); |
| height = ta.getHeight(); |
| bgcolor = ta.getBackgroundColor(); |
| pageTitle = ta.getPageTitle(); |
| } |
| |
| String result = null; |
| if (type.equals("release")) { |
| result = input.replaceAll("\\$\\{application\\}", safeReplacement(projectName + ".min")); |
| } else { |
| result = input.replaceAll("\\$\\{application\\}", safeReplacement(projectName)); |
| } |
| if (bgcolor != null) |
| result = result.replaceAll("\\$\\{bgcolor\\}", safeReplacement(bgcolor)); |
| //result = result.replaceAll("\\$\\{expressInstallSwf\\}", expressInstallSwf); |
| if (height != null) |
| result = result.replaceAll("\\$\\{height\\}", safeReplacement(height.toString())); |
| if (pageTitle != null) |
| result = result.replaceAll("\\$\\{title\\}", safeReplacement(pageTitle)); |
| //result = result.replaceAll("\\$\\{version_major\\}", versionMajor); |
| //result = result.replaceAll("\\$\\{version_minor\\}", versionMinor); |
| //result = result.replaceAll("\\$\\{version_revision\\}", versionRevision); |
| if (width != null) |
| result = result.replaceAll("\\$\\{width\\}", safeReplacement(width.toString())); |
| //result = result.replaceAll("\\$\\{useBrowserHistory\\}", useBrowserHistory); |
| |
| StringBuilder addHTML = new StringBuilder(); |
| addHTML.append(getTemplateAdditionalHTML(additionalHTML)); |
| addHTML.append(getTemplateDependencies(type, projectName, mainClassQName, deps)); |
| result = result.replaceAll("\\$\\{head\\}", safeReplacement(addHTML.toString())); |
| |
| String templateBody = getTemplateBody("release".equals(type) ? projectName : mainClassQName); |
| result = result.replaceAll("\\$\\{body\\}", safeReplacement(templateBody)); |
| |
| writeFile(new File(targetDir, googConfiguration.getHtmlOutputFileName()), result, false); |
| } |
| |
| protected String getTemplateAdditionalHTML(List<String> additionalHTML) |
| { |
| StringBuilder htmlFile = new StringBuilder(); |
| for (String s : additionalHTML) |
| { |
| htmlFile.append(s).append("\n"); |
| } |
| return htmlFile.toString(); |
| } |
| |
| protected String getTemplateDependencies(String type, String projectName, String mainClassQName, String deps) |
| { |
| StringBuilder depsHTML = new StringBuilder(); |
| if ("intermediate".equals(type)) |
| { |
| depsHTML.append("\t<script type=\"text/javascript\" src=\"./library/closure/goog/base.js\"></script>\n"); |
| depsHTML.append("\t<script type=\"text/javascript\">\n"); |
| depsHTML.append(deps); |
| depsHTML.append("\t\tgoog.require(\""); |
| depsHTML.append(mainClassQName); |
| depsHTML.append("\");\n"); |
| depsHTML.append("\t</script>\n"); |
| } |
| else |
| { |
| depsHTML.append("\t<script type=\"text/javascript\" src=\"./"); |
| depsHTML.append(projectName); |
| depsHTML.append(".js\"></script>\n"); |
| } |
| return depsHTML.toString(); |
| } |
| |
| protected String getTemplateBody(String mainClassQName) |
| { |
| StringBuilder bodyHTML = new StringBuilder(); |
| bodyHTML.append("\t<script type=\"text/javascript\">\n"); |
| bodyHTML.append("\t\tnew "); |
| bodyHTML.append(mainClassQName); |
| bodyHTML.append("()"); |
| bodyHTML.append(".start();\n"); |
| bodyHTML.append("\t</script>\n"); |
| return bodyHTML.toString(); |
| } |
| |
| protected void writeHTML(String type, String projectName, String mainClassQName, File targetDir, String deps, List<String> additionalHTML) |
| throws IOException |
| { |
| StringBuilder htmlFile = new StringBuilder(); |
| htmlFile.append("<!DOCTYPE html>\n"); |
| htmlFile.append("<html>\n"); |
| htmlFile.append("<head>\n"); |
| htmlFile.append("\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n"); |
| htmlFile.append("\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); |
| |
| // if release version want to call minified css file, while in debug the non minified one |
| if (type.equals("release")) { |
| htmlFile.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"").append(projectName).append(".min.css\">\n"); |
| } else { |
| htmlFile.append("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"").append(projectName).append(".css\">\n"); |
| } |
| |
| htmlFile.append(getTemplateAdditionalHTML(additionalHTML)); |
| htmlFile.append(getTemplateDependencies(type, projectName, mainClassQName, deps)); |
| |
| htmlFile.append("</head>\n"); |
| htmlFile.append("<body>\n"); |
| |
| htmlFile.append(getTemplateBody(mainClassQName)); |
| |
| htmlFile.append("</body>\n"); |
| htmlFile.append("</html>"); |
| |
| writeFile(new File(targetDir, googConfiguration.getHtmlOutputFileName()), htmlFile.toString(), false); |
| } |
| |
| private void writeCSS(String projectName, File targetDir, Boolean minify) throws IOException |
| { |
| JSCSSCompilationSession cssSession = (JSCSSCompilationSession) project.getCSSCompilationSession(); |
| String cssString = cssSession.emitCSS(); |
| |
| if (minify) |
| { |
| //minify CSS for release |
| writeFile(new File(targetDir, projectName + ".min.css"), JSCSSCompilationSession.minifyCSSString(cssString), false); |
| } else { |
| writeFile(new File(targetDir, projectName + ".css"), cssString, false); |
| } |
| |
| for (CSSFontFace fontFace : cssSession.fontFaces) |
| { |
| // check frameworks/fonts folder |
| String configdir = configuration.getLoadConfig(); |
| File dir = new File(configdir); |
| dir = dir.getParentFile(); |
| for (ICSSPropertyValue prop : fontFace.getSources()) |
| { |
| if (prop instanceof CSSArrayPropertyValue) |
| { |
| for (ICSSPropertyValue value : ((CSSArrayPropertyValue)prop).getElements()) |
| { |
| copyFontFile((CSSFunctionCallPropertyValue) value, dir, targetDir); |
| } |
| } |
| else |
| { |
| if (prop instanceof CSSFunctionCallPropertyValue) |
| { |
| copyFontFile((CSSFunctionCallPropertyValue) prop, dir, targetDir); |
| } |
| } |
| } |
| } |
| } |
| |
| protected void copyFontFile(CSSFunctionCallPropertyValue fn, File sourceDir, File targetDir) throws IOException { |
| String fontPath = fn.rawArguments; |
| if (fontPath.startsWith("'")) { |
| fontPath = fontPath.substring(1, fontPath.length() - 1); |
| } |
| if (fontPath.startsWith("\"")) { |
| fontPath = fontPath.substring(1, fontPath.length() - 1); |
| } |
| int c = fontPath.indexOf("?"); |
| if (c != -1) { |
| fontPath = fontPath.substring(0, c); |
| } |
| File fontFile = new File(sourceDir, fontPath); |
| File destFile = new File(targetDir, fontPath); |
| if (fontFile.exists()) |
| { |
| if (!destFile.exists()) { |
| FileUtils.copyFile(fontFile, destFile); |
| } |
| } |
| } |
| |
| protected File getJarThatContainsClasspathResources(String resourcePath) { |
| URL resource = Thread.currentThread().getContextClassLoader().getResource(resourcePath); |
| if (resource != null) { |
| // Strip the url of the parts we don't need. |
| // Unless we are not using some insanely complex setup |
| // the resource will always be on the same machine. |
| String resourceJarPath = resource.getFile(); |
| try { |
| resourceJarPath = URLDecoder.decode(resourceJarPath, "UTF-8"); |
| if (resourceJarPath.contains(":")) { |
| resourceJarPath = resourceJarPath.substring(resourceJarPath.indexOf(":") + 1); |
| } |
| if (resourceJarPath.contains("!")) { |
| resourceJarPath = resourceJarPath.substring(0, resourceJarPath.indexOf("!")); |
| } |
| return new File(resourceJarPath); |
| } catch (UnsupportedEncodingException e) { |
| e.printStackTrace(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * In case of release builds, we also need the 'js-release' directory created. |
| */ |
| @Override |
| protected void setupOutputFolder() { |
| super.setupOutputFolder(); |
| |
| // Only create a release directory for release builds. |
| if (configuration.release()) { |
| File releaseDir = new File(outputParentFolder, ROYALE_RELEASE_DIR_NAME); |
| if (!releaseDir.exists() && !releaseDir.mkdirs()) { |
| throw new RuntimeException("Unable to create release directory at " + releaseDir.getAbsolutePath()); |
| } |
| } |
| } |
| |
| protected void clearEmptyDirectoryTrees(File baseDirectory) { |
| File[] files = baseDirectory.listFiles(); |
| if(files != null) { |
| for (File file : files) { |
| if (file.isDirectory()) { |
| clearEmptyDirectoryTrees(file); |
| if (isEmptyDirectory(file)) { |
| file.delete(); |
| } |
| } |
| } |
| } |
| } |
| |
| protected boolean isEmptyDirectory(File directory) { |
| File[] files = directory.listFiles(); |
| if(files != null) { |
| for (File file : files) { |
| if (file.isFile()) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private String getFactoryClass(IMetaTag node) |
| { |
| if (node == null) return null; |
| |
| return(node.getAttribute("factoryClass").getValue()); |
| } |
| |
| protected String generateFactoryClass(String factoryClassName, String projectName, String mainClassQName, File targetDir) |
| throws IOException |
| { |
| String generatedName = projectName + "." + factoryClassName; |
| generatedName = generatedName.replace(".", "_"); |
| StringBuilder factoryClass = new StringBuilder(); |
| factoryClass.append("/**\n"); |
| factoryClass.append(" * Generated by Apache Royale Compiler\n"); |
| factoryClass.append(" * " + generatedName + "\n"); |
| factoryClass.append(" *\n"); |
| factoryClass.append(" * @fileoverview\n"); |
| factoryClass.append(" *\n"); |
| factoryClass.append(" * @suppress {checkTypes|accessControls}\n"); |
| factoryClass.append(" */\n"); |
| factoryClass.append("\n"); |
| factoryClass.append("goog.provide('" + generatedName + "');\n"); |
| factoryClass.append("\n"); |
| factoryClass.append("goog.require('" + factoryClassName + "');\n"); |
| factoryClass.append("goog.require('" + mainClassQName + "');\n"); |
| factoryClass.append("\n"); |
| factoryClass.append("\n"); |
| factoryClass.append("\n"); |
| factoryClass.append("/**\n"); |
| factoryClass.append(" * @constructor\n"); |
| factoryClass.append(" * @extends {" + factoryClassName + "}\n"); |
| factoryClass.append(" */\n"); |
| factoryClass.append(generatedName + " = function() {\n"); |
| factoryClass.append(" " + generatedName + ".base(this, 'constructor');\n"); |
| factoryClass.append(" this.mainClassName = " + mainClassQName + ";\n"); |
| factoryClass.append("};\n"); |
| factoryClass.append("goog.inherits(" + generatedName + ", " + factoryClassName + ");\n"); |
| factoryClass.append("goog.exportSymbol('" + generatedName + "', " + generatedName + ");\n"); |
| factoryClass.append("/**\n"); |
| factoryClass.append(" * @type {Object.<string, Array.<Object>>}\n"); |
| factoryClass.append(" */\n"); |
| factoryClass.append(generatedName + ".prototype.ROYALE_CLASS_INFO = { names: [{ name: '" + generatedName + "', qName: '" + generatedName + "', kind: 'class' }]};\n"); |
| |
| writeFile(new File(targetDir, generatedName + ".js"), factoryClass.toString(), false); |
| return generatedName; |
| } |
| } |