/* * Copyright 2011 Chad Retz * * Licensed 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.gwtnode.dev; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.security.Permission; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Compiler * * @author Chad Retz */ public class Compiler { private static LogLevel logLevel = LogLevel.ERROR; private static void printUsage() { StringBuilder usage = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader( Compiler.class.getResourceAsStream("USAGE"))); try { usage.append(reader.readLine()).append("\n"); } catch (IOException e) { throw new RuntimeException("Unable to read USAGE"); } finally { try { reader.close(); } catch (Exception ignore) { } } } public static void main(String[] args) { //TODO: modularize try { List<String> argList = new ArrayList<String>(Arrays.asList(args)); //validate options... assertValidOptions(argList); //grab the log level logLevel = getLogLevel(argList); //grab the module name List<String> simpleNames = getSimpleModuleNames(argList); //get the out directory File outDir = getOutputDirectory(argList); //create temp dirs (0 - work, 1 - war, 2 - deploy, 3 - extra) File[] tempDirs = createTempDirectories(argList); //insert params argList.addAll(0, Arrays.asList( "-workDir", tempDirs[0].getAbsolutePath(), "-war", tempDirs[1].getAbsolutePath(), "-deploy", tempDirs[2].getAbsolutePath(), "-extra", tempDirs[3].getAbsolutePath())); //run run(argList.toArray(new String[0])); //copy everything in the war folder to the out folder if (simpleNames.size() == 1) { //if there is one, just copy it copyRecursively(new File(tempDirs[1], simpleNames.get(0)), outDir); } else { //if there isn't, put the whole war dir structure there... copyRecursively(tempDirs[1], outDir); } //XXX: should I be deleting the temp directories? if (tempDirs[0].getParentFile().getName().startsWith("gwt-node-")) { //delete the parent if the parent starts with "gwt-node-" deleteRecursively(tempDirs[0].getParentFile()); } else { //nope, just the kids deleteRecursively(tempDirs[0]); deleteRecursively(tempDirs[1]); deleteRecursively(tempDirs[2]); deleteRecursively(tempDirs[3]); } } catch (Exception e) { System.out.println("Error - " + e.getMessage()); printUsage(); if (logLevel.isEnabled(LogLevel.INFO)) { e.printStackTrace(); } System.exit(1); } } private static int run(String[] array) { //preventing System.exit() NoExitSecurityManager.install(); try { //I have to set this system property because the persistent unit cache // (com.google.gwt.dev.javac.PersistentUnitCache) reads from the temp // files in another thread while I'm trying to delete them later... //This property is referenced in com.google.gwt.dev.javac.UnitCacheFactory System.setProperty("gwt.persistentunitcache", "false"); com.google.gwt.dev.Compiler.main(array); } catch (ExitInvokedException e) { return e.status; } finally { NoExitSecurityManager.uninstall(); } throw new IllegalStateException("Didn't get System.exit() as expected"); } private static File getOutputDirectory(List<String> argList) throws FriendlyException { File ret; try { if (argList.contains("-out")) { int index = argList.indexOf("-out"); ret = new File(argList.get(index + 1)); argList.remove(index); argList.remove(index); } else { ret = new File("."); } } catch (Exception e) { throw new FriendlyException("Can't get output directory", e); } if (!ret.exists()) { throw new FriendlyException("Output directory " + ret + " does not exist"); } return ret; } private static void deleteRecursively(File file) throws IOException { if (file.isDirectory()) { for (File child : file.listFiles()) { deleteRecursively(child); } } if (!file.delete()) { if (logLevel.isEnabled(LogLevel.DEBUG)) { System.out.println("Unable to delete file " + file + ", deleting on exit"); } file.deleteOnExit(); } } private static void copyRecursively(File source, File target) throws IOException { if (source.isDirectory()) { if (!target.exists()) { target.mkdir(); } for (String child : source.list()) { copyRecursively(new File(source, child), new File(target, child)); } } else { InputStream in = null; OutputStream out = null; try { in = new FileInputStream(source); out = new FileOutputStream(target); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } finally { try { in.close(); } catch (Exception ignore) { } try { out.close(); } catch (Exception ignore) { } } } } private static File[] createTempDirectories(List<String> argList) throws FriendlyException { try { File workDir; if (argList.contains("-workDir")) { int index = argList.indexOf("-workDir"); workDir = new File(argList.get(index + 1)); //remove it from the list argList.remove(index); argList.remove(index); } else { workDir = new File(System.getProperty("java.io.tmpdir")); } File tempDir; do { tempDir = new File(workDir, "gwt-node-" + Long.toHexString(System.currentTimeMillis())); Thread.sleep(10); } while (tempDir.exists() || !tempDir.mkdir()); if (logLevel.isEnabled(LogLevel.DEBUG)) { System.out.println("Using temporary directory: " + tempDir); } File[] ret = new File[] { new File(tempDir, "work"), new File(tempDir, "war"), new File(tempDir, "deploy"), new File(tempDir, "extra") }; ret[0].mkdir(); ret[1].mkdir(); ret[2].mkdir(); ret[3].mkdir(); return ret; } catch (Exception e) { throw new FriendlyException("Unable to create temporary directories", e); } } private static List<String> getSimpleModuleNames(List<String> argList) { //work backwards and consider each argument that doesn't have a // [-arg arg] or [-arg] style a module name List<String> moduleNames = new ArrayList<String>(); for (int i = argList.size() - 1; i >= 0; i--) { String arg = argList.get(i); if (!arg.startsWith("-") && (i == 0 || !argList.get(i - 1).startsWith("-"))) { moduleNames.add(arg.substring(arg.lastIndexOf('.') + 1)); } } return moduleNames; } private static LogLevel getLogLevel(List<String> argList) throws FriendlyException { try { if (argList.contains("-logLevel")) { return LogLevel.valueOf(argList.get(argList.indexOf("-logLevel") + 1)); } else { return LogLevel.ERROR; } } catch (Exception e) { throw new FriendlyException("Invalid log level", e); } } private static void assertValidOptions(List<String> argList) throws FriendlyException { List<String> errors = new ArrayList<String>(5); if (argList.contains("-gen")) { //not needed during standard compilation errors.add("-gen is not allowed"); } if (argList.contains("-localWorkers")) { //we only have one permutation errors.add("-localWorkers is not allowed"); } if (argList.contains("-war")) { //we create this directory errors.add("-war is not allowed"); } if (argList.contains("-deploy")) { //we create this directory errors.add("-deploy is not allowed"); } if (argList.contains("-extra")) { //we create this directory errors.add("-extra is not allowed"); } if (!errors.isEmpty()) { throw new FriendlyException("Invalid options: " + errors); } } private static enum LogLevel { ERROR, WARN, INFO, TRACE, DEBUG, SPAM, ALL; private boolean isEnabled(LogLevel requested) { return ordinal() >= requested.ordinal(); } } @SuppressWarnings("serial") private static class FriendlyException extends Exception { public FriendlyException(String message) { super(message); } public FriendlyException(String message, Throwable cause) { super(message, cause); } } private static class NoExitSecurityManager extends SecurityManager { public static void install() { System.setSecurityManager(new NoExitSecurityManager(System.getSecurityManager())); } public static void uninstall() { if (System.getSecurityManager() instanceof NoExitSecurityManager) { System.setSecurityManager(((NoExitSecurityManager) System.getSecurityManager()).previous); } } private final SecurityManager previous; private NoExitSecurityManager(SecurityManager previous) { this.previous = previous; } @Override public void checkPermission(Permission perm) { //meh } @Override public void checkPermission(Permission perm, Object context) { //meh } @Override public void checkExit(int status) { super.checkExit(status); throw new ExitInvokedException(status); } } @SuppressWarnings("serial") private static class ExitInvokedException extends RuntimeException { private final int status; private ExitInvokedException(int status) { this.status = status; } } }