blob: 2640e1820a0ac91caab2dfb59a22ca3033dfc649 [file] [log] [blame]
/*
* Copyright 2011 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.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;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
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;
/**
* Holds the compiler output for one module.
* TODO(skybrian) there will later be a separate Outbox for each set of binding properties.
*/
class Outbox {
/**
* The suffix that the GWT compiler uses when writing a sourcemap file.
*/
static final String SOURCEMAP_FILE_SUFFIX = "_sourceMap0.json";
private final String id;
private final Recompiler recompiler;
private final Options options;
private final AtomicReference<Result> published = new AtomicReference<Result>();
private Job publishedJob; // may be null if the Result wasn't created by a Job.
Outbox(String id, Recompiler recompiler, Options options, TreeLogger logger)
throws UnableToCompleteException {
Preconditions.checkArgument(isValidOutboxId(id));
this.id = id;
this.recompiler = recompiler;
this.options = options;
maybePrecompile(logger);
}
private boolean isValidOutboxId(String id) {
return ModuleDef.isValidModuleName(id);
}
/**
* Forces the next recompile even if no input files have changed.
*/
void forceNextRecompile() {
recompiler.forceNextRecompile();
}
/**
* A unique id for this outbox. (This should be treated as an opaque string.)
*/
String getId() {
return id;
}
/**
* Loads the module and maybe compiles it. Sets up the output directory.
* Throws an exception if unable. (In this case, Super Dev Mode fails to start.)
*/
void maybePrecompile(TreeLogger logger) throws UnableToCompleteException {
if (options.getNoPrecompile()) {
publish(recompiler.initWithoutPrecompile(logger), null);
return;
}
// TODO: each box will have its own binding properties
Map<String, String> defaultProps = new HashMap<String, String>();
defaultProps.put("user.agent", "safari");
defaultProps.put("locale", "en");
// Create a dummy job for the first compile.
// Its progress is not visible externally but will still be logged.
JobEventTable dummy = new JobEventTable();
Job job = makeJob(defaultProps, logger);
job.onSubmitted(dummy);
publish(recompiler.precompile(job), job);
if (options.isCompileTest()) {
// Listener errors are fatal in compile tests
Throwable error = job.getListenerFailure();
if (error != null) {
UnableToCompleteException e = new UnableToCompleteException();
e.initCause(error);
throw e;
}
}
}
/**
* Creates a Job whose output will be saved in this outbox.
*/
Job makeJob(Map<String, String> bindingProperties, TreeLogger parentLogger) {
return new Job(this, bindingProperties, parentLogger, options);
}
/**
* Compiles the module again, possibly changing the output directory.
* After returning, the result of the compile can be found via {@link Job#waitForResult}
*/
void recompile(Job job) {
if (!job.wasSubmitted() || job.isDone()) {
throw new IllegalStateException(
"tried to recompile using a job in the wrong state:" + job.getId());
}
Result result = recompiler.recompile(job);
if (result.isOk()) {
publish(result, job);
} else {
job.getLogger().log(TreeLogger.Type.WARN, "continuing to serve previous version");
}
}
/**
* Makes the result of a compile downloadable via HTTP.
* @param job the job that created this result, or null if none.
*/
private synchronized void publish(Result result, Job job) {
if (publishedJob != null) {
publishedJob.onGone();
}
publishedJob = job;
published.set(result);
}
private CompileDir getOutputDir() {
return published.get().outputDir;
}
/**
* Returns true if we haven't done a real compile yet, so the Outbox contains
* a stub that will automatically start a compile.
*/
synchronized boolean containsStubCompile() {
return publishedJob == null;
}
/**
* Returns the module name that will be sent to the compiler (before renaming).
*/
String getInputModuleName() {
return recompiler.getInputModuleName();
}
/**
* Returns the module name last received from the compiler (after renaming).
*/
String getOutputModuleName() {
return recompiler.getOutputModuleName();
}
/**
* Returns the source map file from the most recent recompile,
* assuming there is one permutation.
*
* @throws RuntimeException if unable
*/
File findSourceMapForOnePermutation() {
String moduleName = recompiler.getOutputModuleName();
List<File> sourceMapFiles = getOutputDir().findSourceMapFiles(moduleName);
if (sourceMapFiles == null) {
throw new RuntimeException("Can't find sourcemap files.");
}
if (sourceMapFiles.size() > 1) {
throw new RuntimeException("Multiple fragment 0 sourcemaps found. Too many permutations.");
}
if (sourceMapFiles.isEmpty()) {
throw new RuntimeException("No sourcemaps found. Not enabled?");
}
return sourceMapFiles.get(0);
}
/**
* Returns the source map file given a strong name.
*
* @throws RuntimeException if unable
*/
File findSourceMap(String strongName) {
File dir = findSymbolMapDir();
File file = new File(dir, strongName + SOURCEMAP_FILE_SUFFIX);
if (!file.isFile()) {
throw new RuntimeException("Sourcemap file doesn't exist for " + strongName);
}
return file;
}
/**
* Returns the symbol map file given a strong name.
*
* @throws RuntimeException if unable
*/
File findSymbolMap(String strongName) {
File dir = findSymbolMapDir();
File file = new File(dir, strongName + ".symbolMap");
if (!file.isFile()) {
throw new RuntimeException("Symbolmap file doesn't exist for " + strongName);
}
return file;
}
/**
* Returns the symbols map folder for this modulename.
* @throws RuntimeException if unable
*/
private File findSymbolMapDir() {
String moduleName = recompiler.getOutputModuleName();
File symbolMapsDir = getOutputDir().findSymbolMapDir(moduleName);
if (symbolMapsDir == null) {
throw new RuntimeException("Can't find symbol map directory for " + moduleName);
}
return symbolMapsDir;
}
/**
* Finds a source file (or other resource) that's either in this module's source path, or
* is a generated file.
* @param path location of the file relative to its directory in the classpath, or (if
* it starts with "gen/"), a generated file.
* @return bytes in the file, or null if there's no such source file.
*/
InputStream openSourceFile(String path) throws IOException {
if (path.startsWith("gen/")) {
// generated file?
String rest = path.substring("gen/".length());
File fileInGenDir = new File(getGenDir(), rest);
if (!fileInGenDir.isFile()) {
return null;
}
return new BufferedInputStream(new FileInputStream(fileInGenDir));
} else {
// regular source file?
URL resource = recompiler.getResourceLoader().getResource(path);
if (resource == null) {
return null;
}
return resource.openStream();
}
}
/**
* Returns the location of a file in the compiler's output directory from the
* last time this module was recompiled. The location will change after a successful
* recompile.
* @param urlPath The path to the file. This should be a relative path beginning
* with the module name (after renaming).
* @return The location of the file, which might not actually exist.
*/
File getOutputFile(String urlPath) {
return new File(getOutputDir().getWarDir(), urlPath);
}
/**
* Returns the log file from the last time this module was recompiled. This changes
* after each compile.
*/
File getCompileLog() {
return recompiler.getLastLog();
}
File getGenDir() {
return getOutputDir().getGenDir();
}
File getWarDir() {
return getOutputDir().getWarDir();
}
/**
* Return fresh Js that knows how to request the specific permutation recompile.
*/
public String getRecompileJs(TreeLogger logger) throws UnableToCompleteException {
return recompiler.getRecompileJs(logger);
}
/**
* 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.
*/
List<PolicyFile> readRpcPolicyManifest() throws IOException {
return getOutputDir().readRpcPolicyManifest(getOutputModuleName());
}
}