Super Dev Mode: copy GWT-RPC policies to launcherDir
Also, fix a bug setting the wrong module base directory
when using bookmarklets and the code server has just
restarted.
This is so that policy files show up in the same place
as they used to in DevMode, making gwt.codeserver.port
unnecessary in DevMode -superDevMode.
Change-Id: I87659ca2605b05078a3cc4b74ce72d6fcd04a14f
Review-Link: https://gwt-review.googlesource.com/#/c/9519/
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
index c017f53..c83ce74 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
@@ -139,12 +139,14 @@
File workDir = ensureWorkDir(options);
logger.log(Type.INFO, "workDir: " + workDir);
+ LauncherDir launcherDir = LauncherDir.maybeCreate(options);
+
int nextOutboxId = 1;
OutboxTable outboxes = new OutboxTable();
for (String moduleName : options.getModuleNames()) {
AppSpace appSpace = AppSpace.create(new File(workDir, moduleName));
- Recompiler recompiler = new Recompiler(appSpace, moduleName, options);
+ Recompiler recompiler = new Recompiler(appSpace, launcherDir, moduleName, options);
// The id should be treated as an opaque string since we will change it again.
// TODO: change outbox id to include binding properties.
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompileDir.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompileDir.java
index a32b7ed..25ec834 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompileDir.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompileDir.java
@@ -18,8 +18,13 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.thirdparty.guava.common.base.Charsets;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.io.Files;
import java.io.File;
+import java.io.IOException;
+import java.util.List;
/**
* Defines a directory tree used for compiling a GWT app one time. Each time we recompile
@@ -104,6 +109,40 @@
}
/**
+ * Reads a GWT-RPC serialization policy manifest in this directory.
+ * @return a PolicyFile record for each entry in the policy file. If the policy file isn't there,
+ * returns the empty list.
+ * @throws java.io.IOException if the file exists but we can't read it.
+ */
+ List<PolicyFile> readRpcPolicyManifest(String outputModuleName) throws IOException {
+ File manifest = new File(getExtraDir(), outputModuleName + "/rpcPolicyManifest/manifest.txt");
+ if (!manifest.isFile()) {
+ return Lists.newArrayList();
+ }
+ String text = Files.toString(manifest, Charsets.UTF_8);
+
+ List<PolicyFile> result = Lists.newArrayList();
+
+ for (String line : text.split("\n")) {
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#")) {
+ continue;
+ }
+ String[] fields = line.split(", ");
+ if (fields.length < 2) {
+ continue;
+ }
+
+ String serviceName = fields[0];
+ String policyFileName = fields[1];
+ PolicyFile policy = new PolicyFile(policyFileName, serviceName, outputModuleName);
+ result.add(policy);
+ }
+
+ return result;
+ }
+
+ /**
* Creates a new compileDir directory structure. The directory must not already exist,
* but its parent should exist.
* @throws UnableToCompleteException if unable to create the directory
@@ -126,4 +165,57 @@
throw new UnableToCompleteException();
}
}
+
+ /**
+ * A record describing one GWT-RPC serialization policy file.
+ */
+ static class PolicyFile {
+ private final String name;
+ private final String serviceName;
+ private final String outputModuleName;
+
+ /**
+ * Creates a record.
+ * @param name A filename in the format "{strong name}.gwt.rpc".
+ * @param serviceName The name of a Java interface that extends RemoteService.
+ * @param outputModuleName The module that contains the service.
+ */
+ PolicyFile(String name, String serviceName, String outputModuleName) {
+ this.name = name;
+ this.serviceName = serviceName;
+ this.outputModuleName = outputModuleName;
+ }
+
+ /**
+ * Returns the name of the policy file.
+ * This looks like "{strong name}.gwt.rpc".
+ */
+ String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the URL of the policy file itself.
+ * (This is a site-relative URL like "/policies/{strong name}.gwt.rpc".)
+ */
+ String getUrl() {
+ return "/policies/" + name;
+ }
+
+ /**
+ * Returns the name of the Java interface that extends RemoteService.
+ */
+ String getServiceName() {
+ return serviceName;
+ }
+
+ /**
+ * Returns the URL to the Java source code for the Java interface.
+ * (This is a site-relative URL like "/sourcemaps/{module name}/path/to/JavaFile.java".)
+ */
+ String getServiceSourceUrl() {
+ return SourceHandler.SOURCEMAP_PATH + outputModuleName + "/" +
+ serviceName.replace('.', '/') + ".java";
+ }
+ }
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/LauncherDir.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/LauncherDir.java
new file mode 100644
index 0000000..484bf75
--- /dev/null
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/LauncherDir.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * 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 com.google.gwt.dev.codeserver;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.codeserver.CompileDir.PolicyFile;
+import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
+import com.google.gwt.thirdparty.guava.common.base.Charsets;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.guava.common.io.Files;
+import com.google.gwt.thirdparty.guava.common.io.Resources;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * An output directory where other servers pick up files from Super Dev Mode.
+ * This often points to the -war directory for a DevMode server,
+ * but it may point to a subdirectory.
+ *
+ * <p>Underneath this directory, the code server will write one subdirectory for each module.
+ * A nocache.js file, public resource files, and GWT-RPC policy files will be written there.
+ * This is usually enough to launch Super Dev Mode without running the
+ * production GWT compiler first.
+ */
+class LauncherDir {
+ private final File launcherDir;
+ private final Options options;
+
+ /**
+ * @see #maybeCreate
+ */
+ private LauncherDir(Options options) {
+ this.launcherDir = Preconditions.checkNotNull(options.getLauncherDir());
+ this.options = options;
+ }
+
+ /**
+ * Updates files after a successful compile (or on startup).
+ * @param module the module that was compiled
+ * @param compileDir the compiler's output directory
+ */
+ void update(ModuleDef module, CompileDir compileDir, TreeLogger logger)
+ throws UnableToCompleteException {
+ File moduleDir = new File(launcherDir + "/" + module.getName());
+ if (!moduleDir.isDirectory()) {
+ if (!moduleDir.mkdirs()) {
+ logger.log(Type.ERROR, "Can't create launcher dir for module: " + moduleDir);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ try {
+ String stub = generateStubNocacheJs(module.getName(), options);
+
+ 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()) {
+ logger.log(Type.ERROR, "cannot delete file: " + noCacheJsGz);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ writePublicResources(moduleDir, module, logger);
+
+ // Copy the GWT-RPC serialization policies so that the subclass of RemoteServiceServlet
+ // can pick it up. (It expects to find policy files in the module directory.)
+ // See RemoteServiceServlet.loadSerializationPolicy.
+ // (An alternate approach is to set the gwt.codeserver.port environment variable
+ // so that the other server downloads policies over HTTP.)
+ for (PolicyFile policyFile : compileDir.readRpcPolicyManifest(module.getName())) {
+ String filename = policyFile.getName();
+ File src = new File(compileDir.getWarDir(), module.getName() + "/" + filename);
+ File dest = new File(moduleDir, filename);
+ Files.copy(src, dest);
+ }
+
+ } catch (IOException e) {
+ logger.log(Type.ERROR, "Can't update launcher dir", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ /**
+ * Returns a new LauncherDir or null if not enabled.
+ */
+ static LauncherDir maybeCreate(Options options) {
+ if (options.getLauncherDir() == null) {
+ return null;
+ }
+ return new LauncherDir(options);
+ }
+
+ // TODO: figure out a better home for these static methods.
+
+ /**
+ * Returns the contents of a nocache.js file that will compile and then run a GWT application
+ * using Super Dev Mode.
+ */
+ static String generateStubNocacheJs(String outputModuleName, Options options) 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()));
+ }
+
+ 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));
+ }
+ }
+}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
index 4531395..6527445 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
@@ -19,6 +19,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.codeserver.CompileDir.PolicyFile;
import com.google.gwt.dev.codeserver.Job.Result;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
@@ -30,6 +31,7 @@
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@@ -285,12 +287,11 @@
}
/**
- * Returns a file out of the "extras" directory.
- * @param path relative path of the file, not including the module name.
- * @return The location of the file, which might not actually exist.
+ * Reads the GWT-RPC serialization policy manifest in this outbox.
+ * If it's not there, returns the empty list.
+ * @return a PolicyFile record for each entry in the policy file.
*/
- File getExtraFile(String path) {
- File prefix = new File(getOutputDir().getExtraDir(), getOutputModuleName());
- return new File(prefix, path);
+ List<PolicyFile> readRpcPolicyManifest() throws IOException {
+ return getOutputDir().readRpcPolicyManifest(getOutputModuleName());
}
}
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 8115bf8..7093603 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -51,7 +51,6 @@
import java.io.File;
import java.io.IOException;
-import java.net.URL;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -63,6 +62,7 @@
class Recompiler {
private final AppSpace appSpace;
+ private final LauncherDir launcherDir;
private final String inputModuleName;
private String serverPrefix;
@@ -84,8 +84,9 @@
private CompilerContext compilerContext;
private Options options;
- Recompiler(AppSpace appSpace, String inputModuleName, Options options) {
+ Recompiler(AppSpace appSpace, LauncherDir launcherDir, String inputModuleName, Options options) {
this.appSpace = appSpace;
+ this.launcherDir = launcherDir;
this.inputModuleName = inputModuleName;
this.options = options;
this.serverPrefix = options.getPreferredHost() + ":" + options.getPort();
@@ -197,7 +198,9 @@
module.getCompilationState(compileLogger, compilerContext);
setUpCompileDir(compileDir, module, compileLogger);
- writeLauncherFiles(module, compileLogger);
+ if (launcherDir != null) {
+ launcherDir.update(module, compileDir, compileLogger);
+ }
outputModuleName.set(module.getName());
lastBuild.set(compileDir);
@@ -225,10 +228,10 @@
compileLogger.log(Type.WARN, "cannot create directory: " + outputDir);
}
}
- writePublicResources(outputDir, module, compileLogger);
+ LauncherDir.writePublicResources(outputDir, module, compileLogger);
// write no cache that will inject recompile.nocache.js
- String stub = generateStubNocacheJs(module.getName());
+ String stub = LauncherDir.generateStubNocacheJs(module.getName(), options);
File noCacheJs = new File(outputDir.getCanonicalPath(), module.getName() + ".nocache.js");
Files.write(stub, noCacheJs, Charsets.UTF_8);
@@ -244,21 +247,6 @@
}
}
- 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.
*/
@@ -341,7 +329,9 @@
String moduleName = outputModuleName.get();
writeRecompileNoCacheJs(new File(publishedCompileDir.getWarDir(), moduleName), moduleName,
recompileJs, compileLogger);
- writeLauncherFiles(module, compileLogger);
+ if (launcherDir != null) {
+ launcherDir.update(module, compileDir, compileLogger);
+ }
} else {
// always recompile after an error
lastBuildInput = null;
@@ -364,61 +354,6 @@
}
/**
- * 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() {
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
index f0a359c..2248ea4 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
@@ -19,10 +19,9 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.codeserver.CompileDir.PolicyFile;
import com.google.gwt.dev.codeserver.Pages.ErrorPage;
import com.google.gwt.dev.json.JsonObject;
-import com.google.gwt.thirdparty.guava.common.base.Charsets;
-import com.google.gwt.thirdparty.guava.common.io.Files;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
@@ -42,6 +41,7 @@
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -387,37 +387,29 @@
out.startTag("h1").text("Policy Files").endTag("h1").nl();
for (Outbox box : outboxes.getOutboxes()) {
- File manifest = box.getExtraFile("rpcPolicyManifest/manifest.txt");
- if (manifest.isFile()) {
+ List<PolicyFile> policies = box.readRpcPolicyManifest();
+ if (!policies.isEmpty()) {
out.startTag("h2").text(box.getOutputModuleName()).endTag("h2").nl();
out.startTag("table").nl();
- String text = Files.toString(manifest, Charsets.UTF_8);
- for (String line : text.split("\n")) {
- line = line.trim();
- if (line.isEmpty() || line.startsWith("#")) {
- continue;
- }
- String[] fields = line.split(", ");
- if (fields.length < 2) {
- continue;
- }
-
- String serviceName = fields[0];
- String policyFileName = fields[1];
-
- String serviceUrl = SourceHandler.SOURCEMAP_PATH + box.getOutputModuleName() + "/" +
- serviceName.replace('.', '/') + ".java";
- String policyUrl = "/policies/" + policyFileName;
+ for (PolicyFile policy : policies) {
out.startTag("tr");
out.startTag("td");
- out.startTag("a", "href=", serviceUrl).text(serviceName).endTag("a");
+
+ out.startTag("a", "href=", policy.getServiceSourceUrl());
+ out.text(policy.getServiceName());
+ out.endTag("a");
+
out.endTag("td");
out.startTag("td");
- out.startTag("a", "href=", policyUrl).text(policyFileName).endTag("a");
+
+ out.startTag("a", "href=", policy.getUrl());
+ out.text(policy.getName());
+ out.endTag("a");
+
out.endTag("td");
out.endTag("tr").nl();
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/recompile_main.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/recompile_main.js
index d8d661f..bd3a7de 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/recompile_main.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/recompile_main.js
@@ -44,8 +44,12 @@
Main.prototype.compile = function() {
var that = this;
// Export the module base of the server running the backend. (Returned by GWT.getModuleBaseURL.)
+ // (If we are using the bookmarklets with an external server, the dev mode hook has already set
+ // this up to point somewhere different, so don't override it.)
var moduleBaseKey = '__gwtDevModeHook:' + this.__moduleName + ':moduleBase';
- $wnd[moduleBaseKey] = this.__baseUrlProvider.getBaseUrl();
+ if (!$wnd[moduleBaseKey]) {
+ $wnd[moduleBaseKey] = this.__baseUrlProvider.getBaseUrl();
+ }
this.__dialog.clear();
this.__dialog.add(this.__dialog.createTextElement("div", "12pt", "Compiling " + this.__moduleName));
diff --git a/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java b/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
index 0bac061..2e6aba9 100644
--- a/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
+++ b/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
@@ -113,8 +113,8 @@
fooResource);
writeResourcesTo(originalResources, sourcePath);
- Recompiler recompiler =
- new Recompiler(AppSpace.create(Files.createTempDir()), "com.foo.SimpleModule", options);
+ Recompiler recompiler = new Recompiler(AppSpace.create(Files.createTempDir()), null,
+ "com.foo.SimpleModule", options);
Outbox outbox = new Outbox("Transactional Cache", recompiler, options, logger);
OutboxTable outboxes = new OutboxTable();
outboxes.addOutbox(outbox);