| /* |
| * 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()); |
| } |
| } |