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() {