Super Dev Mode: add -launcherDir flag This provides a way to start a GWT application using Super Dev Mode without having done a full compile of the GWT application ahead of time. The -launcherDir flag is optional and points to an output directory. The code server will create a subdirectory for each module. On startup and after each recompile, it will write out a nocache.js file and any public resources in the module. (Note that we don't delete files except for a nocache.js.gz file if it exists.) For example, -launcherDir can be pointed to the expanded war directory of a Java frontend server. An upcoming patch will switch devmode's -superDevMode flag to use this mechanism instead. Change-Id: Iced1fb38d85d225c92e2d02b30e53675e1a18bfb Review-Link: https://gwt-review.googlesource.com/#/c/9455/
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java index dc03310..882c179 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
@@ -60,6 +60,7 @@ private boolean noPrecompile = false; private boolean isCompileTest = false; private File workDir; + private File launcherDir; private final List<String> moduleNames = new ArrayList<String>(); private boolean allowMissingSourceDir = false; private final List<File> sourcePath = new ArrayList<File>(); @@ -189,6 +190,15 @@ } /** + * A directory where each module's files for launching Super Dev Mode should be written, + * or null if not supplied. + * (For example, nocache.js and public resource files will go here.) + */ + File getLauncherDir() { + return launcherDir; + } + + /** * The names of the module that will be compiled (along with all its dependencies). */ List<String> getModuleNames() { @@ -300,6 +310,7 @@ registerHandler(new SourceFlag()); registerHandler(new StrictResourcesFlag()); registerHandler(new WorkDirFlag()); + registerHandler(new LauncherDir()); registerHandler(new ArgHandlerIncrementalCompile(new OptionIncrementalCompile() { @Override public boolean isIncrementalCompileEnabled() { @@ -625,6 +636,48 @@ } } + private class LauncherDir extends ArgHandler { + + @Override + public String getTag() { + return "-launcherDir"; + } + + @Override + public String[] getTags() { + // add an alias since in DevMode this was "-war" + return new String[] {getTag(), "-war"}; + } + + @Override + public String[] getTagArgs() { + return new String[0]; + } + + @Override + public String getPurpose() { + return "An output directory where files for launching Super Dev Mode will be written. " + + "(Optional.)"; + } + + @Override + public int handle(String[] args, int startIndex) { + if (startIndex + 1 >= args.length) { + System.err.println(getTag() + " should be followed by the name of a directory"); + return -1; + } + + File candidate = new File(args[startIndex + 1]); + if (!candidate.isDirectory()) { + System.err.println("not a directory: " + candidate); + return -1; + } + + launcherDir = candidate; + return 1; + } + } + private class ModuleNameArgument extends ArgHandlerExtra { @Override
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java index 53f9c30..331b9a8 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -51,6 +51,7 @@ import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -196,11 +197,11 @@ module.getCompilationState(compileLogger, compilerContext); setUpCompileDir(compileDir, module, compileLogger); + writeLauncherFiles(module, compileLogger); outputModuleName.set(module.getName()); lastBuild.set(compileDir); - long elapsedTime = System.currentTimeMillis() - startTime; compileLogger.log(TreeLogger.Type.INFO, "Module setup completed in " + elapsedTime + " ms"); @@ -224,18 +225,7 @@ compileLogger.log(Type.WARN, "cannot create directory: " + outputDir); } } - - // Copy the public resources to the output. - ResourceOracleImpl publicResources = module.getPublicResourceOracle(); - for (String pathName : publicResources.getPathNames()) { - File file = new File(outputDir, pathName); - File parent = file.getParentFile(); - if (!parent.isDirectory() && !parent.mkdirs()) { - compileLogger.log(Type.ERROR, "cannot create directory: " + parent); - throw new UnableToCompleteException(); - } - Files.asByteSink(file).writeFrom(publicResources.getResourceAsStream(pathName)); - } + writePublicResources(outputDir, module, compileLogger); // Create a "module_name.nocache.js" that calculates the permutation and forces a recompile. String nocacheJs = generateModuleRecompileJs(module, compileLogger); @@ -251,6 +241,21 @@ } } + private static void writePublicResources(File moduleOutputDir, ModuleDef module, + TreeLogger compileLogger) throws UnableToCompleteException, IOException { + // Copy the public resources to the output. + ResourceOracleImpl publicResources = module.getPublicResourceOracle(); + for (String pathName : publicResources.getPathNames()) { + File file = new File(moduleOutputDir, pathName); + File parent = file.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + compileLogger.log(Type.ERROR, "cannot create directory: " + parent); + throw new UnableToCompleteException(); + } + Files.asByteSink(file).writeFrom(publicResources.getResourceAsStream(pathName)); + } + } + /** * Generates the nocache.js file to use when precompile is not on. */ @@ -333,6 +338,7 @@ String moduleName = outputModuleName.get(); writeRecompileNoCacheJs(new File(publishedCompileDir.getWarDir(), moduleName), moduleName, recompileJs, compileLogger); + writeLauncherFiles(module, compileLogger); } else { // always recompile after an error lastBuildInput = null; @@ -355,6 +361,61 @@ } /** + * Updates files for launching Super Dev Mode. + */ + private void writeLauncherFiles(ModuleDef module, TreeLogger compileLogger) + throws UnableToCompleteException { + + File launcherDir = options.getLauncherDir(); + if (launcherDir == null) { + return; // not turned on + } + + File moduleDir = new File(launcherDir + "/" + module.getName()); + if (!moduleDir.isDirectory()) { + if (!moduleDir.mkdirs()) { + compileLogger.log(Type.ERROR, "Can't create launcher dir for module: " + moduleDir); + throw new UnableToCompleteException(); + } + } + + try { + String stub = generateStubNocacheJs(module.getName()); + + final File noCacheJs = new File(moduleDir, module.getName() + ".nocache.js"); + Files.write(stub, noCacheJs, Charsets.UTF_8); + + // Remove gz file so it doesn't get used instead. + // (We may be writing to an existing war directory.) + final File noCacheJsGz = new File(noCacheJs.getPath() + ".gz"); + if (noCacheJsGz.exists()) { + if (!noCacheJsGz.delete()) { + compileLogger.log(Type.ERROR, "cannot delete file: " + noCacheJsGz); + throw new UnableToCompleteException(); + } + } + + writePublicResources(moduleDir, module, compileLogger); + + } catch (IOException e) { + compileLogger.log(Type.ERROR, "Can't update launcher dir", e); + throw new UnableToCompleteException(); + } + } + + /** + * Returns the contents of a nocache.js file that will compile and then run a GWT application + * using Super Dev Mode. + */ + private String generateStubNocacheJs(String outputModuleName) throws IOException { + URL url = Resources.getResource(Recompiler.class, "stub.nocache.js"); + final String template = Resources.toString(url, Charsets.UTF_8); + return template + .replace("__MODULE_NAME__", outputModuleName) + .replace("__SUPERDEV_PORT__", String.valueOf(options.getPort())); + } + + /** * Returns the log from the last compile. (It may be a failed build.) */ File getLastLog() {