Refactor StandardLinkerContext output/extra/file/jar implementation to not suck.
Patch by: spoon
Review by: me
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6497 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index aecb67b..967ee85 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -51,29 +51,22 @@
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsScope;
import com.google.gwt.dev.util.DefaultTextOutput;
-import com.google.gwt.dev.util.FileBackedObject;
+import com.google.gwt.dev.util.OutputFileSet;
import com.google.gwt.dev.util.Util;
-import com.google.gwt.util.tools.Utility;
-import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
/**
* An implementation of {@link LinkerContext} that is initialized from a
@@ -118,67 +111,6 @@
}
};
- /**
- * Returns the parent path of forward-slash based partial path. Assumes the
- * given path does not end with a trailing slash.
- */
- private static String getParentPath(String path) {
- assert !path.endsWith("/");
- int pos = path.lastIndexOf('/');
- return (pos >= 0) ? path.substring(0, pos) : null;
- }
-
- /**
- * A faster bulk version of {@link File#mkdirs()} that takes advantage of
- * cached state to avoid a lot of file system access.
- */
- private static boolean mkdirs(File dir, Set<String> createdDirs) {
- if (dir == null) {
- return true;
- }
- String path = dir.getPath();
- if (createdDirs.contains(path)) {
- return true;
- }
- if (!dir.exists()) {
- if (!mkdirs(dir.getParentFile(), createdDirs)) {
- return false;
- }
- if (!dir.mkdir()) {
- return false;
- }
- }
- createdDirs.add(path);
- return true;
- }
-
- /**
- * Creates directory entries within a zip archive. This is consistent with how
- * most tools operate.
- *
- * @param path the path of a directory within the archive to create
- * @param zipOutputStream the archive we're creating
- * @param createdDirs the set of already-created directories to avoid
- * duplication
- */
- private static void mkzipDirs(String path, ZipOutputStream zipOutputStream,
- Set<String> createdDirs) throws IOException {
- if (path == null) {
- return;
- }
- if (createdDirs.contains(path)) {
- return;
- }
- mkzipDirs(getParentPath(path), zipOutputStream, createdDirs);
- ZipEntry entry = new ZipEntry(path + '/');
- entry.setSize(0);
- entry.setCompressedSize(0);
- entry.setCrc(0);
- entry.setMethod(ZipOutputStream.STORED);
- zipOutputStream.putNextEntry(entry);
- createdDirs.add(path);
- }
-
private final ArtifactSet artifacts = new ArtifactSet();
private final SortedSet<ConfigurationProperty> configurationProperties;
@@ -294,24 +226,9 @@
configurationProperties = Collections.unmodifiableSortedSet(mutableConfigurationProperties);
}
- {
- int index = 0;
- for (Script script : module.getScripts()) {
- artifacts.add(new StandardScriptReference(script.getSrc(), index++));
- logger.log(TreeLogger.SPAM, "Added script " + script.getSrc(), null);
- }
- }
-
- {
- int index = 0;
- for (String style : module.getStyles()) {
- artifacts.add(new StandardStylesheetReference(style, index++));
- logger.log(TreeLogger.SPAM, "Added style " + style, null);
- }
- }
-
- // Generated files should be passed in via addArtifacts()
-
+ /*
+ * Add static resources in the specified module as artifacts.
+ */
for (String path : module.getAllPublicFiles()) {
String partialPath = path.replace(File.separatorChar, '/');
PublicResource resource = new StandardPublicResource(partialPath,
@@ -319,6 +236,8 @@
artifacts.add(resource);
logger.log(TreeLogger.SPAM, "Added public resource " + resource, null);
}
+
+ recordStaticReferences(logger, module);
}
/**
@@ -340,27 +259,20 @@
/**
* Gets or creates a CompilationResult for the given JavaScript program.
*/
- public StandardCompilationResult getCompilation(TreeLogger logger,
- FileBackedObject<PermutationResult> resultFile)
- throws UnableToCompleteException {
- PermutationResult permutationResult = resultFile.newInstance(logger);
-
+ public StandardCompilationResult getCompilation(
+ PermutationResult permutationResult) {
byte[][] js = permutationResult.getJs();
String strongName = Util.computeStrongName(js);
StandardCompilationResult result = resultsByStrongName.get(strongName);
if (result == null) {
result = new StandardCompilationResult(strongName, js,
permutationResult.getSerializedSymbolMap(),
- permutationResult.getStatementRanges(), permutationResult.getPermutationId());
+ permutationResult.getStatementRanges(),
+ permutationResult.getPermutationId());
resultsByStrongName.put(result.getStrongName(), result);
artifacts.add(result);
-
- // Add any other Permutations
- ArtifactSet otherArtifacts = permutationResult.getArtifacts();
- if (otherArtifacts != null) {
- artifacts.addAll(otherArtifacts);
- }
}
+ artifacts.addAll(permutationResult.getArtifacts());
return result;
}
@@ -500,138 +412,45 @@
}
/**
- * Writes artifacts into the extra directory in the standard way.
+ * Emit EmittedArtifacts artifacts onto <code>out</code>. Does not close
+ * <code>out</code>.
*
- * @param logger logs the operation
- * @param artifacts the set of artifacts to write
- * @param extraPath optional extra path for non-deployable artifacts
- * @throws UnableToCompleteException
+ * @param logger where to log progress
+ * @param artifacts the artifacts to emit
+ * @param emitPrivates whether to emit the private artifacts only, vs. the
+ * public artifacts only
+ * @param out where to emit the artifact contents
*/
- public void produceExtraDirectory(TreeLogger logger, ArtifactSet artifacts,
- File extraPath) throws UnableToCompleteException {
- extraPath = extraPath.getAbsoluteFile();
- logger = logger.branch(TreeLogger.TRACE, "Writing extras into "
- + extraPath.getPath(), null);
+ public void produceOutput(TreeLogger logger, ArtifactSet artifacts,
+ boolean emitPrivates, OutputFileSet out) throws UnableToCompleteException {
+ String publicness = emitPrivates ? "private" : "public";
+ logger = logger.branch(TreeLogger.TRACE, "Linking " + publicness
+ + " artifacts into " + out.getPathDescription(), null);
- Set<String> createdDirs = new HashSet<String>();
for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
"Emitting resource " + artifact.getPartialPath(), null);
- if (!artifact.isPrivate()) {
+ if (artifact.isPrivate() != emitPrivates) {
continue;
}
- File outFile = new File(extraPath, getExtraPathForLinker(
- artifact.getLinker(), artifact.getPartialPath()));
- writeArtifactToFile(artifactLogger, artifact, outFile, createdDirs);
- }
- }
-
- /**
- * Writes artifacts into an extra zip in the standard way.
- *
- * @param logger logs the operation
- * @param artifacts the set of artifacts to write
- * @param extraZip the output zip for deployable artifacts
- * @param pathPrefix path within the zip to write into; if non-empty must end
- * with a trailing slash
- * @throws UnableToCompleteException
- */
- public void produceExtraZip(TreeLogger logger, ArtifactSet artifacts,
- File extraZip, String pathPrefix) throws UnableToCompleteException {
- extraZip = extraZip.getAbsoluteFile();
- logger = logger.branch(TreeLogger.TRACE, "Linking compilation into "
- + extraZip.getPath(), null);
-
- try {
- Set<String> createdDirs = new HashSet<String>();
- ZipOutputStream zipOutputStream = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(extraZip)));
- for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
- TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
- "Emitting resource " + artifact.getPartialPath(), null);
-
- if (!artifact.isPrivate()) {
- continue;
- }
- String path = pathPrefix
- + getExtraPathForLinker(artifact.getLinker(),
- artifact.getPartialPath());
- writeArtifactToZip(artifactLogger, artifact, path, zipOutputStream,
- createdDirs);
- }
- Utility.close(zipOutputStream);
- } catch (FileNotFoundException e) {
- logger.log(TreeLogger.ERROR, "Unable to create extra archive "
- + extraZip.getPath(), e);
- throw new UnableToCompleteException();
- }
- }
-
- /**
- * Writes artifacts into output directory in the standard way.
- *
- * @param logger logs the operation
- * @param artifacts the set of artifacts to write
- * @param outputPath the output path for deployable artifacts
- * @throws UnableToCompleteException
- */
- public void produceOutputDirectory(TreeLogger logger, ArtifactSet artifacts,
- File outputPath) throws UnableToCompleteException {
- outputPath = outputPath.getAbsoluteFile();
- logger = logger.branch(TreeLogger.TRACE, "Linking compilation into "
- + outputPath.getPath(), null);
-
- Set<String> createdDirs = new HashSet<String>();
- for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
- TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
- "Emitting resource " + artifact.getPartialPath(), null);
-
+ String partialPath = artifact.getPartialPath();
if (artifact.isPrivate()) {
- continue;
- }
- File outFile = new File(outputPath, artifact.getPartialPath());
- writeArtifactToFile(artifactLogger, artifact, outFile, createdDirs);
- }
- }
-
- /**
- * Writes artifacts into an output zip in the standard way.
- *
- * @param logger logs the operation
- * @param artifacts the set of artifacts to write
- * @param outZip the output zip for deployable artifacts
- * @param pathPrefix path within the zip to write into; if non-empty must end
- * with a trailing slash
- * @throws UnableToCompleteException
- */
- public void produceOutputZip(TreeLogger logger, ArtifactSet artifacts,
- File outZip, String pathPrefix) throws UnableToCompleteException {
- outZip = outZip.getAbsoluteFile();
- logger = logger.branch(TreeLogger.TRACE, "Linking compilation into "
- + outZip.getPath(), null);
-
- try {
- ZipOutputStream zipOutputStream = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(outZip)));
- Set<String> createdDirs = new HashSet<String>();
- for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
- TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
- "Emitting resource " + artifact.getPartialPath(), null);
-
- if (artifact.isPrivate()) {
- continue;
+ partialPath = getExtraPathForLinker(artifact.getLinker(), partialPath);
+ if (partialPath.startsWith("/")) {
+ partialPath = partialPath.substring(1);
}
- String path = pathPrefix + artifact.getPartialPath();
- writeArtifactToZip(artifactLogger, artifact, path, zipOutputStream,
- createdDirs);
}
- Utility.close(zipOutputStream);
- } catch (FileNotFoundException e) {
- logger.log(TreeLogger.ERROR, "Unable to create output archive "
- + outZip.getPath(), e);
- throw new UnableToCompleteException();
+ try {
+ OutputStream artifactStream = out.openForWrite(partialPath,
+ artifact.getLastModified());
+ artifact.writeTo(artifactLogger, artifactStream);
+ artifactStream.close();
+ } catch (IOException e) {
+ artifactLogger.log(TreeLogger.ERROR,
+ "Fatal error emitting this artifact", e);
+ }
}
}
@@ -646,42 +465,26 @@
return linkerShortNames.get(linkerType) + '/' + partialPath;
}
- private void writeArtifactToFile(TreeLogger logger, EmittedArtifact artifact,
- File outFile, Set<String> createdDirs) throws UnableToCompleteException {
- if (!outFile.exists()
- || (outFile.lastModified() < artifact.getLastModified())) {
- if (!mkdirs(outFile.getParentFile(), createdDirs)) {
- logger.log(TreeLogger.ERROR, "Unable to create directory for file '"
- + outFile.getAbsolutePath() + "'");
- } else {
- try {
- FileOutputStream out = new FileOutputStream(outFile);
- artifact.writeTo(logger, out);
- out.close();
- } catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Unable to create file '"
- + outFile.getAbsolutePath() + "'", e);
- throw new UnableToCompleteException();
- }
- outFile.setLastModified(artifact.getLastModified());
+ /**
+ * Record script references and CSS references that are listed in the module
+ * file.
+ */
+ private void recordStaticReferences(TreeLogger logger, ModuleDef module) {
+ {
+ int index = 0;
+ for (Script script : module.getScripts()) {
+ String url = script.getSrc();
+ artifacts.add(new StandardScriptReference(url, index++));
+ logger.log(TreeLogger.SPAM, "Added script " + url, null);
}
}
- }
- private void writeArtifactToZip(TreeLogger logger, EmittedArtifact artifact,
- String path, ZipOutputStream zipOutputStream, Set<String> createdDirs)
- throws UnableToCompleteException {
- try {
- mkzipDirs(getParentPath(path), zipOutputStream, createdDirs);
- ZipEntry zipEntry = new ZipEntry(path);
- zipEntry.setTime(artifact.getLastModified());
- zipOutputStream.putNextEntry(zipEntry);
- artifact.writeTo(logger, zipOutputStream);
- zipOutputStream.closeEntry();
- } catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Unable to write out artifact '"
- + artifact.getPartialPath() + "'", e);
- throw new UnableToCompleteException();
+ {
+ int index = 0;
+ for (String style : module.getStyles()) {
+ artifacts.add(new StandardStylesheetReference(style, index++));
+ logger.log(TreeLogger.SPAM, "Added style " + style, null);
+ }
}
}
}
diff --git a/dev/core/src/com/google/gwt/dev/DevMode.java b/dev/core/src/com/google/gwt/dev/DevMode.java
index 9b41b03..6175e98 100644
--- a/dev/core/src/com/google/gwt/dev/DevMode.java
+++ b/dev/core/src/com/google/gwt/dev/DevMode.java
@@ -28,6 +28,9 @@
import com.google.gwt.dev.ui.RestartServerCallback;
import com.google.gwt.dev.ui.RestartServerEvent;
import com.google.gwt.dev.util.InstalledHelpInfo;
+import com.google.gwt.dev.util.NullOutputFileSet;
+import com.google.gwt.dev.util.OutputFileSet;
+import com.google.gwt.dev.util.OutputFileSetOnDirectory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
@@ -486,11 +489,26 @@
private void produceOutput(TreeLogger logger,
StandardLinkerContext linkerStack, ArtifactSet artifacts, ModuleDef module)
throws UnableToCompleteException {
- File moduleOutDir = new File(options.getWarDir(), module.getName());
- linkerStack.produceOutputDirectory(logger, artifacts, moduleOutDir);
- if (options.getExtraDir() != null) {
- File moduleExtraDir = new File(options.getExtraDir(), module.getName());
- linkerStack.produceExtraDirectory(logger, artifacts, moduleExtraDir);
+ TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG, "Linking module '"
+ + module.getName() + "'");
+
+ try {
+ OutputFileSetOnDirectory outFileSet = new OutputFileSetOnDirectory(
+ options.getWarDir(), module.getName() + "/");
+ OutputFileSet extraFileSet = new NullOutputFileSet();
+ if (options.getExtraDir() != null) {
+ extraFileSet = new OutputFileSetOnDirectory(options.getExtraDir(),
+ module.getName() + "/");
+ }
+
+ linkerStack.produceOutput(linkLogger, artifacts, false, outFileSet);
+ linkerStack.produceOutput(linkLogger, artifacts, true, extraFileSet);
+
+ outFileSet.close();
+ extraFileSet.close();
+ } catch (IOException e) {
+ linkLogger.log(TreeLogger.ERROR, "I/O exception", e);
+ throw new UnableToCompleteException();
}
}
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 1305ccf..733b25a 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -30,6 +30,10 @@
import com.google.gwt.dev.jjs.PermutationResult;
import com.google.gwt.dev.jjs.impl.CodeSplitter;
import com.google.gwt.dev.util.FileBackedObject;
+import com.google.gwt.dev.util.NullOutputFileSet;
+import com.google.gwt.dev.util.OutputFileSet;
+import com.google.gwt.dev.util.OutputFileSetOnDirectory;
+import com.google.gwt.dev.util.OutputFileSetOnJar;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
@@ -86,8 +90,8 @@
LinkOptions {
private File extraDir;
- private File warDir;
private File outDir;
+ private File warDir;
public LinkOptionsImpl() {
}
@@ -133,24 +137,31 @@
public static void legacyLink(TreeLogger logger, ModuleDef module,
ArtifactSet generatedArtifacts, Permutation[] permutations,
List<FileBackedObject<PermutationResult>> resultFiles, File outDir,
- JJSOptions precompileOptions) throws UnableToCompleteException {
+ JJSOptions precompileOptions) throws UnableToCompleteException,
+ IOException {
StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
module, precompileOptions);
ArtifactSet artifacts = doLink(logger, linkerContext, generatedArtifacts,
permutations, resultFiles);
- doProduceLegacyOutput(logger, artifacts, linkerContext, module, outDir);
+ OutputFileSet outFileSet = new OutputFileSetOnDirectory(outDir,
+ module.getName() + "/");
+ OutputFileSet extraFileSet = new OutputFileSetOnDirectory(outDir,
+ module.getName() + "-aux/");
+ doProduceOutput(logger, artifacts, linkerContext, outFileSet, extraFileSet);
}
public static void link(TreeLogger logger, ModuleDef module,
ArtifactSet generatedArtifacts, Permutation[] permutations,
List<FileBackedObject<PermutationResult>> resultFiles, File outDir,
File extrasDir, JJSOptions precompileOptions)
- throws UnableToCompleteException {
+ throws UnableToCompleteException, IOException {
StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
module, precompileOptions);
ArtifactSet artifacts = doLink(logger, linkerContext, generatedArtifacts,
permutations, resultFiles);
- doProduceOutput(logger, artifacts, linkerContext, module, outDir, extrasDir);
+ doProduceOutput(logger, artifacts, linkerContext, chooseOutputFileSet(
+ outDir, module.getName() + "/"), chooseOutputFileSet(extrasDir,
+ module.getName() + "/"));
}
public static void main(String[] args) {
@@ -177,6 +188,37 @@
System.exit(1);
}
+ /**
+ * Choose an output file set for the given <code>dirOrJar</code> based on
+ * its name, whether it's null, and whether it already exists as a directory.
+ */
+ private static OutputFileSet chooseOutputFileSet(File dirOrJar,
+ String pathPrefix) throws IOException {
+ return chooseOutputFileSet(dirOrJar, pathPrefix, pathPrefix);
+ }
+
+ /**
+ * A version of {@link #chooseOutputFileSet(File, String)} that allows
+ * choosing a separate path prefix depending on whether the output is a
+ * directory or a jar file.
+ */
+ private static OutputFileSet chooseOutputFileSet(File dirOrJar,
+ String jarPathPrefix, String dirPathPrefix) throws IOException {
+
+ if (dirOrJar == null) {
+ return new NullOutputFileSet();
+ }
+
+ String name = dirOrJar.getName();
+ if (!dirOrJar.isDirectory()
+ && (name.endsWith(".war") || name.endsWith(".jar") || name.endsWith(".zip"))) {
+ return new OutputFileSetOnJar(dirOrJar, jarPathPrefix);
+ } else {
+ Util.recursiveDelete(new File(dirOrJar, dirPathPrefix), true);
+ return new OutputFileSetOnDirectory(dirOrJar, dirPathPrefix);
+ }
+ }
+
private static ArtifactSet doLink(TreeLogger logger,
StandardLinkerContext linkerContext, ArtifactSet generatedArtifacts,
Permutation[] perms, List<FileBackedObject<PermutationResult>> resultFiles)
@@ -194,52 +236,26 @@
return linkerContext.invokeLink(logger);
}
- private static void doProduceLegacyOutput(TreeLogger logger,
- ArtifactSet artifacts, StandardLinkerContext linkerContext,
- ModuleDef module, File outDir) throws UnableToCompleteException {
- File moduleOutDir = new File(outDir, module.getName());
- File moduleExtraDir = new File(outDir, module.getName() + "-aux");
- Util.recursiveDelete(moduleOutDir, true);
- Util.recursiveDelete(moduleExtraDir, true);
- linkerContext.produceOutputDirectory(logger, artifacts, moduleOutDir);
- linkerContext.produceExtraDirectory(logger, artifacts, moduleExtraDir);
- logger.log(TreeLogger.INFO, "Link succeeded");
- }
-
+ /**
+ * Emit final output.
+ */
private static void doProduceOutput(TreeLogger logger, ArtifactSet artifacts,
- StandardLinkerContext linkerContext, ModuleDef module, File outDir,
- File extraDir) throws UnableToCompleteException {
- String outPath = outDir.getPath();
- if (!outDir.isDirectory()
- && (outPath.endsWith(".war") || outPath.endsWith(".jar") || outPath.endsWith(".zip"))) {
- linkerContext.produceOutputZip(logger, artifacts, outDir,
- module.getName() + '/');
- } else {
- File moduleOutDir = new File(outDir, module.getName());
- Util.recursiveDelete(moduleOutDir, true);
- linkerContext.produceOutputDirectory(logger, artifacts, moduleOutDir);
- }
+ StandardLinkerContext linkerContext, OutputFileSet outFileSet,
+ OutputFileSet extraFileSet) throws UnableToCompleteException, IOException {
+ linkerContext.produceOutput(logger, artifacts, false, outFileSet);
+ linkerContext.produceOutput(logger, artifacts, true, extraFileSet);
- if (extraDir != null) {
- String extraPath = extraDir.getPath();
- if (!extraDir.isDirectory()
- && (extraPath.endsWith(".war") || extraPath.endsWith(".jar") || extraPath.endsWith(".zip"))) {
- linkerContext.produceExtraZip(logger, artifacts, extraDir,
- module.getName() + '/');
- } else {
- File moduleExtraDir = new File(extraDir, module.getName());
- Util.recursiveDelete(moduleExtraDir, true);
- linkerContext.produceExtraDirectory(logger, artifacts, moduleExtraDir);
- }
- }
+ outFileSet.close();
+ extraFileSet.close();
+
logger.log(TreeLogger.INFO, "Link succeeded");
}
private static void finishPermuation(TreeLogger logger, Permutation perm,
FileBackedObject<PermutationResult> resultFile,
StandardLinkerContext linkerContext) throws UnableToCompleteException {
- StandardCompilationResult compilation = linkerContext.getCompilation(
- logger, resultFile);
+ PermutationResult permutationResult = resultFile.newInstance(logger);
+ StandardCompilationResult compilation = linkerContext.getCompilation(permutationResult);
StaticPropertyOracle[] propOracles = perm.getPropertyOracles();
for (StaticPropertyOracle propOracle : propOracles) {
BindingProperty[] orderedProps = propOracle.getOrderedProps();
@@ -312,6 +328,35 @@
ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
+ OutputFileSet outFileSet;
+ OutputFileSet extraFileSet;
+ try {
+ if (options.getOutDir() == null) {
+ outFileSet = chooseOutputFileSet(options.getWarDir(),
+ module.getName() + "/");
+ extraFileSet = chooseOutputFileSet(options.getExtraDir(),
+ module.getName() + "/");
+ } else {
+ outFileSet = chooseOutputFileSet(options.getOutDir(),
+ module.getName() + "/");
+ if (options.getExtraDir() != null) {
+ extraFileSet = chooseOutputFileSet(options.getExtraDir(),
+ module.getName() + "-aux/", "");
+ } else if (outFileSet instanceof OutputFileSetOnDirectory) {
+ // Automatically emit extras into the output directory, if it's in
+ // fact a directory
+ extraFileSet = chooseOutputFileSet(options.getOutDir(),
+ module.getName() + "-aux/");
+ } else {
+ extraFileSet = new NullOutputFileSet();
+ }
+ }
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR,
+ "Unexpected exception while producing output", e);
+ throw new UnableToCompleteException();
+ }
+
if (precomps.isEmpty()) {
logger.log(TreeLogger.ERROR, "No precompilation files found in '"
+ compilerWorkDir.getAbsolutePath()
@@ -359,12 +404,12 @@
ArtifactSet artifacts = doLink(branch, linkerContext, generatedArtifacts,
perms, resultFiles);
- if (options.getOutDir() == null) {
- doProduceOutput(branch, artifacts, linkerContext, module,
- options.getWarDir(), options.getExtraDir());
- } else {
- doProduceLegacyOutput(branch, artifacts, linkerContext, module,
- options.getOutDir());
+ try {
+ doProduceOutput(branch, artifacts, linkerContext, outFileSet,
+ extraFileSet);
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR,
+ "Unexpected exception while producing output", e);
}
}
return true;
diff --git a/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java b/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java
new file mode 100644
index 0000000..6fcaf5e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/NullOutputFileSet.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009 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.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An {@link OutputFileSet} that discards all data sent to it.
+ */
+public class NullOutputFileSet extends OutputFileSet {
+ private static class NullOutputStream extends OutputStream {
+ @Override
+ public void write(byte[] b) throws IOException {
+ }
+
+ @Override
+ public void write(byte[] b, int i, int j) throws IOException {
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ }
+ }
+
+ public NullOutputFileSet() {
+ super("NULL");
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public OutputStream openForWrite(String path, long lastModifiedTime) {
+ return new NullOutputStream();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
new file mode 100644
index 0000000..b29d355
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009 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.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An abstract set of files that a linker links into.
+ */
+public abstract class OutputFileSet {
+ private final String pathDescription;
+
+ protected OutputFileSet(String pathDescription) {
+ this.pathDescription = pathDescription;
+ }
+
+ public abstract void close() throws IOException;
+
+ /**
+ * Return a description of this output file set's path. The precise meaning is
+ * unspecified, except that it should be informative when used in log
+ * messages.
+ */
+ public String getPathDescription() {
+ return pathDescription;
+ }
+
+ public abstract OutputStream openForWrite(String path, long lastModifiedTime)
+ throws IOException;
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java
new file mode 100644
index 0000000..522577e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnDirectory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2009 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.util;
+
+import com.google.gwt.dev.util.collect.HashSet;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+
+/**
+ * An {@link OutputFileSet} on a directory.
+ */
+public class OutputFileSetOnDirectory extends OutputFileSet {
+ private final Set<String> createdDirs = new HashSet<String>();
+ private final File dir;
+ private final String prefix;
+
+ public OutputFileSetOnDirectory(File dir, String prefix) {
+ super(dir.getAbsolutePath());
+ this.dir = dir;
+ this.prefix = prefix;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public OutputStream openForWrite(String path, long lastModifiedTime)
+ throws IOException {
+ File file = dir;
+ for (String part : (prefix + path).split("/")) {
+ file = new File(file, part);
+ }
+ mkdirs(file.getParentFile());
+ return new FileOutputStream(file);
+ }
+
+ /**
+ * A faster bulk version of {@link File#mkdirs()} that avoids recreating the
+ * same directory multiple times.
+ */
+ private void mkdirs(File dir) {
+ if (dir == null) {
+ return;
+ }
+ String path = dir.getPath();
+ if (createdDirs.contains(path)) {
+ return;
+ }
+ createdDirs.add(path);
+ if (!dir.exists()) {
+ mkdirs(dir.getParentFile());
+ dir.mkdir();
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
new file mode 100644
index 0000000..856533a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2009 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.util;
+
+import com.google.gwt.dev.util.collect.HashSet;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * An {@link OutputFileSet} on a jar file.
+ */
+public class OutputFileSetOnJar extends OutputFileSet {
+ /**
+ * An output stream on a jar entry for <code>jar</code>. It is assumed that
+ * the entry has already been written, so this class only has to forward the
+ * writes.
+ */
+ private final class OutputStreamOnJarEntry extends OutputStream {
+ @Override
+ public void close() throws IOException {
+ jar.closeEntry();
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ jar.write(b, off, len);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ jar.write(b);
+ }
+ }
+
+ /**
+ * Returns the parent path of forward-slash based partial path. Assumes the
+ * given path does not end with a trailing slash.
+ */
+ private static String getParentPath(String path) {
+ assert !path.endsWith("/");
+ int pos = path.lastIndexOf('/');
+ return (pos >= 0) ? path.substring(0, pos) : null;
+ }
+
+ private Set<String> createdDirs = new HashSet<String>();
+
+ private final JarOutputStream jar;
+
+ private final String pathPrefix;
+
+ public OutputFileSetOnJar(File jarFile, String pathPrefix) throws IOException {
+ super(jarFile.getAbsolutePath());
+ jarFile.delete();
+ jar = new JarOutputStream(new FileOutputStream(jarFile));
+ this.pathPrefix = pathPrefix;
+ }
+
+ @Override
+ public void close() throws IOException {
+ jar.close();
+ }
+
+ @Override
+ public OutputStream openForWrite(String path, long lastModifiedTime)
+ throws IOException {
+ mkzipDirs(getParentPath(pathPrefix + path));
+
+ ZipEntry zipEntry = new ZipEntry(pathPrefix + path);
+ if (lastModifiedTime >= 0) {
+ zipEntry.setTime(lastModifiedTime);
+ }
+ jar.putNextEntry(zipEntry);
+
+ return new OutputStreamOnJarEntry();
+ }
+
+ /**
+ * Creates directory entries within a zip archive. Uses
+ * <code>createdDirs</code> to avoid creating entries for the same path twice.
+ *
+ * @param path the path of a directory within the archive to create
+ */
+ private void mkzipDirs(String path) throws IOException {
+ if (path == null) {
+ return;
+ }
+ if (createdDirs.contains(path)) {
+ return;
+ }
+ mkzipDirs(getParentPath(path));
+ ZipEntry entry = new ZipEntry(path + '/');
+ entry.setSize(0);
+ entry.setCompressedSize(0);
+ entry.setCrc(0);
+ entry.setMethod(ZipOutputStream.STORED);
+ jar.putNextEntry(entry);
+ createdDirs.add(path);
+ }
+}