Adds a new artifact visibility "Deploy" (in addition to existing
"Public" and "Private"), for artifacts that should be deployed to
the server but not ever served to a client.

Patch by: jat
Review by: unnurg

Review at http://gwt-code-reviews.appspot.com/1055802


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9162 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
index 4bb578a..067c238 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
@@ -37,17 +37,98 @@
  */
 public abstract class EmittedArtifact extends Artifact<EmittedArtifact> {
 
+  /**
+   * Describes the visibility of an artifact.
+   */
+  public enum Visibility {
+
+    /**
+     * A public artifact is something that may be served to clients.
+     */
+    Public,
+    
+    /**
+     * A private artifact is something that is only used during the build
+     * process.
+     */
+    Private {
+      @Override
+      public boolean matches(Visibility visibility) {
+        switch (visibility) {
+          case LegacyDeploy:
+          case Private:
+            return true;
+          default:
+            return false;
+        }
+      }
+    },
+    
+    /**
+     * A deploy artifact is deployed to the server but is never served to the
+     * client.
+     */
+    Deploy {
+      @Override
+      public boolean matches(Visibility visibility) {
+        switch (visibility) {
+          case Deploy:
+          case LegacyDeploy:
+            return true;
+          default:
+            return false;
+        }
+      }
+    },
+    
+    /**
+     * For legacy use only - used for artifacts that were previously marked as
+     * private because they should not be delivered to the client, but actually
+     * should be visible to the server.  These artifacts will now be treated as
+     * both Private and Deploy, so that existing build tools that expect to find
+     * them in the output directory for Private artifacts will find them.
+     * 
+     * New code should use Deploy instead.
+     */
+    LegacyDeploy {
+      @Override
+      public boolean matches(Visibility visibility) {
+        switch (visibility) {
+          case Deploy:
+          case LegacyDeploy:
+          case Private:
+            return true;
+          default:
+            return false;
+        }
+      }
+    };
+    
+    /**
+     * Returns true if this visibility matches the requested visibility level,
+     * dealing with the fact that {@link #LegacyDeploy} matches both
+     * {@link #Private} and {@link #Deploy}.
+     * 
+     * @param visibility
+     * @return true if this visibility matches the requested level
+     */
+    public boolean matches(Visibility visibility) {
+      return this == visibility;
+    }
+  }
+
   private final String partialPath;
 
   /**
    * This is mutable because it has no effect on identity.
    */
-  private boolean isPrivate;
+  private Visibility visibility;
 
   protected EmittedArtifact(Class<? extends Linker> linker, String partialPath) {
     super(linker);
     assert partialPath != null;
     this.partialPath = partialPath;
+    visibility = Visibility.Public;
   }
 
   /**
@@ -78,6 +159,13 @@
     return partialPath;
   }
 
+  /**
+   * @return the visibility
+   */
+  public Visibility getVisibility() {
+    return visibility;
+  }
+
   @Override
   public final int hashCode() {
     return getPartialPath().hashCode();
@@ -96,16 +184,31 @@
    * Private EmittedArtifacts are intended for resources that generally should
    * not be deployed to the server in the same location as the module
    * compilation artifacts.
+   * 
+   * @deprecated use {@link #getVisibility()} instead
    */
+  @Deprecated
   public boolean isPrivate() {
-    return isPrivate;
+    return visibility == Visibility.Private;
   }
 
   /**
    * Sets the private attribute of the EmittedResource.
+   * 
+   * @param isPrivate true if this artifact is private
+   *
+   * @deprecated use {@link #setVisibility(Visibility)} instead
    */
+  @Deprecated
   public void setPrivate(boolean isPrivate) {
-    this.isPrivate = isPrivate;
+    this.visibility = isPrivate ? Visibility.Private : Visibility.Public;
+  }
+
+  /**
+   * @param visibility the visibility to set
+   */
+  public void setVisibility(Visibility visibility) {
+    this.visibility = visibility;
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java
index b3c868b..787c11d 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/BinaryOnlyArtifactWrapper.java
@@ -31,7 +31,7 @@
 
   public BinaryOnlyArtifactWrapper(String path, EmittedArtifact artifact) {
     super(path);
-    setPrivate(artifact.isPrivate());
+    setVisibility(artifact.getVisibility());
     this.underlyingArtifact = artifact;
   }
 
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 e638de0..220456a 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
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.PublicResource;
 import com.google.gwt.core.ext.linker.SelectionProperty;
@@ -467,26 +468,26 @@
    * 
    * @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 visibility the level of visibility of artifacts to output
    * @param out where to emit the artifact contents
    */
   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
+      Visibility visibility, OutputFileSet out) throws UnableToCompleteException {
+    logger = logger.branch(TreeLogger.TRACE, "Linking " + visibility
         + " artifacts into " + out.getPathDescription(), null);
 
     for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
       TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
           "Emitting resource " + artifact.getPartialPath(), null);
 
-      if (artifact.isPrivate() != emitPrivates) {
+      if (!artifact.getVisibility().matches(visibility)) {
         continue;
       }
 
       String partialPath = artifact.getPartialPath();
-      if (artifact.isPrivate()) {
+      if (artifact.getVisibility() != Visibility.Public) {
+        // Any non-public linker will have their artifacts stored in a directory
+        // named after the linker.
         partialPath = getExtraPathForLinker(artifact.getLinker(), partialPath);
         if (partialPath.startsWith("/")) {
           partialPath = partialPath.substring(1);
diff --git a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
index 2c00afa..1b84ea8 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.linker.Shardable;
@@ -237,7 +238,8 @@
     try {
       serializedMap = emitString(logger, mappingArtifact.getSerialized(),
           "compilation-mappings.txt");
-      serializedMap.setPrivate(false);
+      // TODO(unnurg): make this Deploy
+      serializedMap.setVisibility(Visibility.Public);
       toReturn.add(serializedMap);
     } catch (UnableToCompleteException e) {
       e.printStackTrace();
diff --git a/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java b/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
index bdc8c40..eadeb71 100644
--- a/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.core.ext.linker.ModuleMetricsArtifact;
@@ -172,7 +173,8 @@
    */
   private void buildCompilerMetricsXml(ArtifactSet artifacts) {
     ModuleMetricsArtifact moduleMetrics = null;
-    Set<ModuleMetricsArtifact> moduleMetricsSet = artifacts.find(ModuleMetricsArtifact.class);
+    Set<ModuleMetricsArtifact> moduleMetricsSet = artifacts.find(
+        ModuleMetricsArtifact.class);
     if (!moduleMetricsSet.isEmpty()) {
       for (ModuleMetricsArtifact metrics : moduleMetricsSet) {
         moduleMetrics = metrics;
@@ -186,10 +188,11 @@
       return;
     }
 
-    byte[] xmlResult = CompilerMetricsXmlFormatter.writeMetricsAsXml(artifacts, moduleMetrics);
-    EmittedArtifact metricsArtifact = new SyntheticArtifact(SoycReportLinker.class, 
-       "compilerMetrics.xml", xmlResult);
-    metricsArtifact.setPrivate(true);
+    byte[] xmlResult = CompilerMetricsXmlFormatter.writeMetricsAsXml(
+        artifacts, moduleMetrics);
+    EmittedArtifact metricsArtifact = new SyntheticArtifact(
+        SoycReportLinker.class, "compilerMetrics.xml", xmlResult);
+    metricsArtifact.setVisibility(Visibility.Private);
     artifacts.add(metricsArtifact);
   }
 
@@ -197,7 +200,8 @@
       ArtifactSet artifacts) {
     ArtifactsOutputDirectory out = new ArtifactsOutputDirectory();
     try {
-      new SoycDashboard(out).generateCrossPermutationFiles(extractPermutationDescriptions(artifacts));
+      new SoycDashboard(out).generateCrossPermutationFiles(
+          extractPermutationDescriptions(artifacts));
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR,
           "Error while generating a Story of Your Compile", e);
@@ -221,9 +225,12 @@
 
   private Map<String, List<String>> extractPermutationDescriptions(
       ArtifactSet artifacts) {
-    Map<String, List<String>> permDescriptions = new TreeMap<String, List<String>>();
-    for (PermDescriptionArtifact art : artifacts.find(PermDescriptionArtifact.class)) {
-      permDescriptions.put(Integer.toString(art.getPermId()), art.getPermDesc());
+    Map<String, List<String>> permDescriptions = new TreeMap<String,
+        List<String>>();
+    for (PermDescriptionArtifact art : artifacts.find(
+        PermDescriptionArtifact.class)) {
+      permDescriptions.put(Integer.toString(art.getPermId()),
+          art.getPermDesc());
     }
     return permDescriptions;
   }
diff --git a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
index 321c29e..5dc937e 100644
--- a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.Shardable;
@@ -120,7 +121,8 @@
       throws UnableToCompleteException {
     EmittedArtifact symbolMapArtifact = emitBytes(logger, out.toByteArray(),
         result.getStrongName() + STRONG_NAME_SUFFIX);
-    symbolMapArtifact.setPrivate(true);
+    // TODO: change to Deploy when possible
+    symbolMapArtifact.setVisibility(Visibility.LegacyDeploy);
     artifacts.add(symbolMapArtifact);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
index 82cd22d..3eb5ce2 100644
--- a/dev/core/src/com/google/gwt/dev/Compiler.java
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -30,6 +30,7 @@
 import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.Memory;
 import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerDeployDir;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
 import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
@@ -59,6 +60,7 @@
       registerHandler(new ArgHandlerWorkDirOptional(options));
 
       registerHandler(new ArgHandlerWarDir(options));
+      registerHandler(new ArgHandlerDeployDir(options));
       registerHandler(new ArgHandlerExtraDir(options));
     }
 
@@ -87,6 +89,10 @@
       localWorkers = other.getLocalWorkers();
     }
 
+    public File getDeployDir() {
+      return linkOptions.getDeployDir();
+    }
+
     public File getExtraDir() {
       return linkOptions.getExtraDir();
     }
@@ -104,6 +110,10 @@
       return linkOptions.getWarDir();
     }
 
+    public void setDeployDir(File extraDir) {
+      linkOptions.setDeployDir(extraDir);
+    }
+
     public void setExtraDir(File extraDir) {
       linkOptions.setExtraDir(extraDir);
     }
@@ -234,7 +244,7 @@
           }
           Link.link(logger.branch(TreeLogger.TRACE, logMessage), module,
               generatedArtifacts, allPerms, resultFiles, options.getWarDir(),
-              options.getExtraDir(), precompileOptions);
+              options.getDeployDir(), options.getExtraDir(), precompileOptions);
 
           linkEvent.end();
           long compileDone = System.currentTimeMillis();
diff --git a/dev/core/src/com/google/gwt/dev/DevMode.java b/dev/core/src/com/google/gwt/dev/DevMode.java
index 32aaca4..8bcdcca 100644
--- a/dev/core/src/com/google/gwt/dev/DevMode.java
+++ b/dev/core/src/com/google/gwt/dev/DevMode.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.shell.jetty.JettyLauncher;
@@ -30,6 +31,7 @@
 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.ArgHandlerDeployDir;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
 import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
@@ -167,6 +169,7 @@
       registerHandler(new ArgHandlerServer(options));
       registerHandler(new ArgHandlerStartupURLs(options));
       registerHandler(new ArgHandlerWarDir(options));
+      registerHandler(new ArgHandlerDeployDir(options));
       registerHandler(new ArgHandlerExtraDir(options));
       registerHandler(new ArgHandlerWorkDirOptional(options));
       registerHandler(new ArgHandlerModuleName(options) {
@@ -207,6 +210,15 @@
     private ServletContainerLauncher scl;
     private String sclArgs;
     private File warDir;
+    private File deployDir;
+
+    /**
+     * @return the deploy directory.
+     */
+    public File getDeployDir() {
+      return (deployDir == null) ? new File(warDir, "WEB-INF/deploy")
+          : deployDir;
+    }
 
     public File getExtraDir() {
       return extraDir;
@@ -233,6 +245,15 @@
       return warDir;
     }
 
+    /**
+     * Set the deploy directory.
+     * 
+     * @param deployDir the deployDir to set
+     */
+    public void setDeployDir(File deployDir) {
+      this.deployDir = deployDir;
+    }
+
     public void setExtraDir(File extraDir) {
       this.extraDir = extraDir;
     }
@@ -491,16 +512,23 @@
 
     OutputFileSetOnDirectory outFileSet = new OutputFileSetOnDirectory(
         options.getWarDir(), module.getName() + "/");
+    OutputFileSetOnDirectory deployFileSet = new OutputFileSetOnDirectory(
+        options.getDeployDir(), 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);
+    linkerStack.produceOutput(linkLogger, artifacts, Visibility.Public,
+        outFileSet);
+    linkerStack.produceOutput(linkLogger, artifacts, Visibility.Deploy,
+        deployFileSet);
+    linkerStack.produceOutput(linkLogger, artifacts, Visibility.Private,
+        extraFileSet);
 
     outFileSet.close();
+    deployFileSet.close();
     try {
       extraFileSet.close();
     } catch (IOException e) {
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index 601e63d..98cd955 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.shell.WorkDirs;
@@ -224,7 +225,8 @@
       outputDir.mkdirs();
       OutputFileSetOnDirectory outFileSet = new OutputFileSetOnDirectory(
           outputDir, "");
-      linkerStack.produceOutput(logger, artifacts, false, outFileSet);
+      linkerStack.produceOutput(logger, artifacts, Visibility.Public,
+          outFileSet);
       outFileSet.close();
     }
   }
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 5a38bfe..9c46fc3 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.impl.BinaryOnlyArtifactWrapper;
 import com.google.gwt.core.ext.linker.impl.JarEntryEmittedArtifact;
@@ -42,8 +43,10 @@
 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.ArgHandlerDeployDir;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
+import com.google.gwt.dev.util.arg.OptionDeployDir;
 import com.google.gwt.dev.util.arg.OptionExtraDir;
 import com.google.gwt.dev.util.arg.OptionOutDir;
 import com.google.gwt.dev.util.arg.OptionWarDir;
@@ -80,7 +83,7 @@
    * Options for Link.
    */
   public interface LinkOptions extends CompileTaskOptions, OptionExtraDir,
-      OptionWarDir, LegacyLinkOptions {
+      OptionWarDir, OptionDeployDir, LegacyLinkOptions {
   }
 
   static class ArgProcessor extends CompileArgProcessor {
@@ -89,6 +92,7 @@
       super(options);
       registerHandler(new ArgHandlerExtraDir(options));
       registerHandler(new ArgHandlerWarDir(options));
+      registerHandler(new ArgHandlerDeployDir(options));
       registerHandler(new ArgHandlerOutDirDeprecated(options));
     }
 
@@ -104,6 +108,7 @@
   static class LinkOptionsImpl extends CompileTaskOptionsImpl implements
       LinkOptions {
 
+    private File deployDir;
     private File extraDir;
     private File outDir;
     private File warDir;
@@ -117,11 +122,17 @@
 
     public void copyFrom(LinkOptions other) {
       super.copyFrom(other);
+      setDeployDir(other.getDeployDir());
       setExtraDir(other.getExtraDir());
       setWarDir(other.getWarDir());
       setOutDir(other.getOutDir());
     }
 
+    public File getDeployDir() {
+      return (deployDir == null) ? new File(warDir, "WEB-INF/deploy")
+          : deployDir;
+    }
+
     public File getExtraDir() {
       return extraDir;
     }
@@ -135,6 +146,10 @@
       return warDir;
     }
 
+    public void setDeployDir(File dir) {
+      deployDir = dir;
+    }
+
     public void setExtraDir(File extraDir) {
       this.extraDir = extraDir;
     }
@@ -160,23 +175,35 @@
         linkerContext, generatedArtifacts, permutations, resultFiles);
     OutputFileSet outFileSet = chooseOutputFileSet(outDir, module.getName()
         + "/");
+    OutputFileSet deployFileSet = chooseOutputFileSet(outDir, module.getName()
+        + "-deploy/");
     OutputFileSet extraFileSet = chooseOutputFileSet(outDir, module.getName()
         + "-aux/");
-    doProduceOutput(logger, artifacts, linkerContext, outFileSet, extraFileSet);
+    doProduceOutput(logger, artifacts, linkerContext, outFileSet, deployFileSet,
+        extraFileSet);
   }
 
   public static void link(TreeLogger logger, ModuleDef module,
       ArtifactSet generatedArtifacts, Permutation[] permutations,
       List<FileBackedObject<PermutationResult>> resultFiles, File outDir,
-      File extrasDir, JJSOptions precompileOptions)
+      File deployDir, File extrasDir, JJSOptions precompileOptions)
       throws UnableToCompleteException, IOException {
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, precompileOptions);
     ArtifactSet artifacts = doSimulatedShardingLink(logger, module,
         linkerContext, generatedArtifacts, permutations, resultFiles);
+    OutputFileSet extrasFileSet = chooseOutputFileSet(extrasDir,
+        module.getName() + "/");
+    // allow -deploy and -extra to point to the same directory/jar
+    OutputFileSet deployFileSet;
+    if (deployDir.equals(extrasDir)) {
+      deployFileSet = extrasFileSet;
+    } else {
+      deployFileSet = chooseOutputFileSet(deployDir,
+          module.getName() + "/");
+    }
     doProduceOutput(logger, artifacts, linkerContext, chooseOutputFileSet(
-        outDir, module.getName() + "/"), chooseOutputFileSet(extrasDir,
-        module.getName() + "/"));
+        outDir, module.getName() + "/"), deployFileSet, extrasFileSet);
   }
 
   /**
@@ -215,17 +242,12 @@
 
       // Write the data of emitted artifacts
       for (EmittedArtifact art : linkedArtifacts.find(EmittedArtifact.class)) {
-        String jarEntryPath;
-        if (art.isPrivate()) {
-          String pathWithLinkerName = linkerContext.getExtraPathForLinker(
-              art.getLinker(), art.getPartialPath());
-          if (pathWithLinkerName.startsWith("/")) {
-            // This happens if the linker has no extra path
-            pathWithLinkerName = pathWithLinkerName.substring(1);
-          }
-          jarEntryPath = "aux/" + pathWithLinkerName;
+        Visibility visibility = art.getVisibility();
+        String jarEntryPath = visibility.name() + "/";
+        if (visibility == Visibility.Public) {
+          jarEntryPath += art.getPartialPath();
         } else {
-          jarEntryPath = "target/" + art.getPartialPath();
+          jarEntryPath += prefixArtifactPath(art, linkerContext);
         }
         ZipEntry ze = new ZipEntry(jarEntryPath);
         ze.setTime(art.getLastModified());
@@ -331,8 +353,8 @@
     }
 
     String name = dirOrJar.getName();
-    if (!dirOrJar.isDirectory()
-        && (name.endsWith(".war") || name.endsWith(".jar") || name.endsWith(".zip"))) {
+    if (!dirOrJar.isDirectory() && (name.endsWith(".war")
+        || name.endsWith(".jar") || name.endsWith(".zip"))) {
       return new OutputFileSetOnJar(dirOrJar, pathPrefix);
     } else {
       Util.recursiveDelete(new File(dirOrJar, pathPrefix), true);
@@ -389,11 +411,18 @@
    */
   private static void doProduceOutput(TreeLogger logger, ArtifactSet artifacts,
       StandardLinkerContext linkerContext, OutputFileSet outFileSet,
-      OutputFileSet extraFileSet) throws UnableToCompleteException, IOException {
-    linkerContext.produceOutput(logger, artifacts, false, outFileSet);
-    linkerContext.produceOutput(logger, artifacts, true, extraFileSet);
+      OutputFileSet deployFileSet, OutputFileSet extraFileSet)
+      throws UnableToCompleteException, IOException {
+
+    linkerContext.produceOutput(logger, artifacts, Visibility.Public,
+        outFileSet);
+    linkerContext.produceOutput(logger, artifacts, Visibility.Deploy,
+        deployFileSet);
+    linkerContext.produceOutput(logger, artifacts, Visibility.Private,
+        extraFileSet);
 
     outFileSet.close();
+    deployFileSet.close();
     extraFileSet.close();
 
     logger.log(TreeLogger.INFO, "Link succeeded");
@@ -456,12 +485,8 @@
   private static String getFullArtifactPath(EmittedArtifact emittedArtifact,
       StandardLinkerContext context) {
     String path = emittedArtifact.getPartialPath();
-    if (emittedArtifact.isPrivate()) {
-      path = context.getExtraPathForLinker(emittedArtifact.getLinker(), path);
-      if (path.startsWith("/")) {
-        // This happens if the linker has no extra path
-        path = path.substring(1);
-      }
+    if (emittedArtifact.getVisibility() != Visibility.Public) {
+      path = prefixArtifactPath(emittedArtifact, context);
     }
     return path;
   }
@@ -490,6 +515,25 @@
         + javaScript[0].length() + " and total script size of " + totalSize);
   }
 
+  /**
+   * Prefix an artifact's partial path with the linker name and make sure it is
+   * a relative pathname.
+   * 
+   * @param art
+   * @param linkerContext
+   * @return prefixed path
+   */
+  private static String prefixArtifactPath(
+      EmittedArtifact art, StandardLinkerContext linkerContext) {
+    String pathWithLinkerName = linkerContext.getExtraPathForLinker(
+        art.getLinker(), art.getPartialPath());
+    if (pathWithLinkerName.startsWith("/")) {
+      // This happens if the linker has no extra path
+      pathWithLinkerName = pathWithLinkerName.substring(1);
+    }
+    return pathWithLinkerName;
+  }
+
   private static ArtifactSet scanCompilePermResults(TreeLogger logger,
       List<File> resultFiles) throws IOException, UnableToCompleteException {
     final ArtifactSet artifacts = new ArtifactSet();
@@ -504,18 +548,10 @@
         }
 
         String path;
-        Artifact<?> artForEntry;
+        Artifact<?> artForEntry = null;
 
-        if (entry.getName().startsWith("target/")) {
-          path = entry.getName().substring("target/".length());
-          artForEntry = new JarEntryEmittedArtifact(path, resultFile, entry);
-        } else if (entry.getName().startsWith("aux/")) {
-          path = entry.getName().substring("aux/".length());
-          JarEntryEmittedArtifact jarArtifact = new JarEntryEmittedArtifact(
-              path, resultFile, entry);
-          jarArtifact.setPrivate(true);
-          artForEntry = jarArtifact;
-        } else if (entry.getName().startsWith("arts/")) {
+        String entryName = entry.getName();
+        if (entryName.startsWith("arts/")) {
           try {
             artForEntry = Util.readStreamAsObject(new BufferedInputStream(
                 jarFile.getInputStream(entry)), Artifact.class);
@@ -526,7 +562,21 @@
             throw new UnableToCompleteException();
           }
         } else {
-          continue;
+          int slash = entryName.indexOf('/');
+          if (slash >= 0) {
+            try {
+              Visibility visibility = Visibility.valueOf(entryName.substring(0,
+                  slash));
+              path = entryName.substring(slash + 1);
+              JarEntryEmittedArtifact jarArtifact = new JarEntryEmittedArtifact(
+                  path, resultFile, entry);
+              jarArtifact.setVisibility(visibility);
+              artForEntry = jarArtifact;
+            } catch (IllegalArgumentException e) {
+              // silently ignore paths with invalid visibilities
+              continue;
+            }
+          }
         }
 
         artifacts.add(artForEntry);
@@ -596,8 +646,8 @@
 
         try {
           link(branch, module, precomp.getGeneratedArtifacts(), perms,
-              resultFiles, options.getWarDir(), options.getExtraDir(),
-              precomp.getUnifiedAst().getOptions());
+              resultFiles, options.getWarDir(), options.getDeployDir(),
+              options.getExtraDir(), precomp.getUnifiedAst().getOptions());
         } catch (IOException e) {
           logger.log(TreeLogger.ERROR,
               "Unexpected exception while producing output", e);
@@ -638,13 +688,21 @@
           module.getName() + "/");
       OutputFileSet extraFileSet = chooseOutputFileSet(options.getExtraDir(),
           module.getName() + "/");
+      // allow -deploy and -extra to point to the same directory/jar
+      OutputFileSet deployFileSet;
+      if (options.getDeployDir().equals(options.getExtraDir())) {
+        deployFileSet = extraFileSet;
+      } else {
+        deployFileSet = chooseOutputFileSet(options.getDeployDir(),
+            module.getName() + "/");
+      }
 
       ArtifactSet artifacts = scanCompilePermResults(logger, resultFiles);
       artifacts.addAll(linkerContext.getArtifactsForPublicResources(logger,
           module));
       artifacts = linkerContext.invokeFinalLink(logger, artifacts);
       doProduceOutput(logger, artifacts, linkerContext, outFileSet,
-          extraFileSet);
+          deployFileSet, extraFileSet);
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Exception during final linking", e);
       throw new UnableToCompleteException();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 722431b..53f4d53 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -25,6 +25,7 @@
 import com.google.gwt.core.ext.linker.CompilationMetricsArtifact;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.StatementRanges;
 import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.SyntheticArtifact;
@@ -1184,7 +1185,7 @@
 
     // Set all of the main SOYC artifacts private.
     for (SyntheticArtifact soycArtifact : soycArtifacts) {
-      soycArtifact.setPrivate(true);
+      soycArtifact.setVisibility(Visibility.Private);
     }
 
     if (sizeBreakdowns != null) {
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
index 04bff20..d0de937 100644
--- a/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSet.java
@@ -35,6 +35,12 @@
     return pathsSeen.contains(path);
   }
 
+  /**
+   * No more output will be sent to this OutputFileSet.  It is safe to call
+   * {@link #close()} on an already-closed instance.
+   * 
+   * @throws IOException
+   */
   public abstract void close() throws IOException;
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
index 132c5bb..5d8eaad 100644
--- a/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
+++ b/dev/core/src/com/google/gwt/dev/util/OutputFileSetOnJar.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.util;
 
+import com.google.gwt.dev.util.NullOutputFileSet.NullOutputStream;
 import com.google.gwt.dev.util.collect.HashSet;
 
 import java.io.File;
@@ -67,6 +68,8 @@
   private final JarOutputStream jar;
 
   private final String pathPrefix;
+  
+  private final Set<String> seenEntries = new HashSet<String>();
 
   public OutputFileSetOnJar(File jarFile, String pathPrefix) throws IOException {
     super(jarFile.getAbsolutePath());
@@ -83,9 +86,14 @@
   @Override
   public OutputStream createNewOutputStream(String path, long lastModifiedTime)
       throws IOException {
-    mkzipDirs(getParentPath(pathPrefix + path));
+    String fullPath = pathPrefix + path;
+    if (seenEntries.contains(fullPath)) {
+      return new NullOutputStream();
+    }
+    seenEntries.add(fullPath);
+    mkzipDirs(getParentPath(fullPath));
 
-    ZipEntry zipEntry = new ZipEntry(pathPrefix + path);
+    ZipEntry zipEntry = new ZipEntry(fullPath);
     if (lastModifiedTime >= 0) {
       zipEntry.setTime(lastModifiedTime);
     }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDeployDir.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDeployDir.java
new file mode 100644
index 0000000..60079c6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDeployDir.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 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.arg;
+
+import com.google.gwt.util.tools.ArgHandlerDir;
+
+import java.io.File;
+
+/**
+ * Argument handler for processing the deploy directory flag.
+ */
+public class ArgHandlerDeployDir extends ArgHandlerDir {
+  // The default argument is handled in LinkOptionsImpl since it is relative
+  // to the war directory.
+
+  private final OptionDeployDir option;
+
+  public ArgHandlerDeployDir(OptionDeployDir option) {
+    this.option = option;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "The directory into which deployable but not servable output files"
+        + " will be written (defaults to 'WEB-INF/deploy' under the -war "
+        + "directory/jar, and may be the same as the -extra directory/jar)";
+  }
+
+  @Override
+  public String getTag() {
+    return "-deploy";
+  }
+
+  @Override
+  public void setDir(File dir) {
+    option.setDeployDir(dir);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionDeployDir.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionDeployDir.java
new file mode 100644
index 0000000..376a588
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionDeployDir.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 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.arg;
+
+import java.io.File;
+
+/**
+ * Option to set the base directory for artifacts of visibility "Deploy".
+ */
+public interface OptionDeployDir {
+
+  /**
+   * Returns the deploy directory.
+   */
+  File getDeployDir();
+
+  /**
+   * Sets the deploy directory.
+   */
+  void setDeployDir(File dir);
+}
diff --git a/dev/core/src/com/google/gwt/soyc/io/ArtifactsOutputDirectory.java b/dev/core/src/com/google/gwt/soyc/io/ArtifactsOutputDirectory.java
index dac00a5..9cf9dea 100644
--- a/dev/core/src/com/google/gwt/soyc/io/ArtifactsOutputDirectory.java
+++ b/dev/core/src/com/google/gwt/soyc/io/ArtifactsOutputDirectory.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.soyc.io;
 
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.SyntheticArtifact;
 import com.google.gwt.core.linker.SoycReportLinker;
 
@@ -51,7 +52,7 @@
         SyntheticArtifact newArtifact = new SyntheticArtifact(
             SoycReportLinker.class, COMPILE_REPORT_DIRECTORY + "/" + path,
             baos.toByteArray());
-        newArtifact.setPrivate(true);
+        newArtifact.setVisibility(Visibility.Private);
         artifacts.add(newArtifact);
         baos = null;
       }
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index 920d2b3..f4053d5 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
@@ -226,7 +227,8 @@
               msgWriter.write(branch, locale.toString(), resourceList, out,
                   targetClass);
               out.flush();
-              context.commitResource(logger, outStr).setPrivate(true);
+              context.commitResource(logger, outStr).setVisibility(
+                  Visibility.Private);
             } catch (UnableToCompleteException e) {
               // msgWriter should have already logged an error message.
               // Keep going for now so we can find other errors.
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 122e0ef..05ec38f 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -31,6 +31,7 @@
 import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.shell.CheckForUpdates;
 import com.google.gwt.dev.shell.jetty.JettyLauncher;
+import com.google.gwt.dev.util.arg.ArgHandlerDeployDir;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableCastChecking;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableClassMetadata;
@@ -168,6 +169,7 @@
           return super.getDefaultArgs();
         }
       });
+      registerHandler(new ArgHandlerDeployDir(options));
       registerHandler(new ArgHandlerExtraDir(options));
       registerHandler(new ArgHandlerWorkDirOptional(options));
       // DISABLE: ArgHandlerModuleName
diff --git a/user/src/com/google/gwt/precompress/linker/PrecompressLinker.java b/user/src/com/google/gwt/precompress/linker/PrecompressLinker.java
index e219763..7803152 100644
--- a/user/src/com/google/gwt/precompress/linker/PrecompressLinker.java
+++ b/user/src/com/google/gwt/precompress/linker/PrecompressLinker.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
@@ -130,7 +131,8 @@
 
       ArtifactSet updated = new ArtifactSet(artifacts);
       for (EmittedArtifact art : artifacts.find(EmittedArtifact.class)) {
-        if (art.isPrivate()) {
+        if (art.getVisibility() != Visibility.Public) {
+          // only compress things that will be served to the client
           continue;
         }
         if (art.getPartialPath().endsWith(".gz")) {
diff --git a/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java b/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java
index 58c70e7..91c58d5 100644
--- a/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java
+++ b/user/src/com/google/gwt/user/linker/rpc/RpcLogLinker.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
@@ -59,7 +60,7 @@
           EmittedArtifact art = emitInputStream(logger,
               logArt.getContents(logger), logArt.getQualifiedSourceName() + "-"
                   + policyStrongName + ".rpc.log");
-          art.setPrivate(true);
+          art.setVisibility(Visibility.Private);
           toReturn.add(art);
         }
       }
diff --git a/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java b/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java
index 8adb7f4..8c84dd3 100644
--- a/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java
+++ b/user/src/com/google/gwt/user/linker/rpc/RpcPolicyManifestLinker.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.linker.AbstractLinker;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.SyntheticArtifact;
@@ -72,7 +73,7 @@
       ArtifactSet toReturn = new ArtifactSet(artifacts);
       SyntheticArtifact manifestArt = emitString(logger,
           generateManifest(context), MANIFEST_TXT);
-      manifestArt.setPrivate(true);
+      manifestArt.setVisibility(Visibility.LegacyDeploy);
       toReturn.add(manifestArt);
       return toReturn;
     }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
index 94c083d..3da97e0 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -23,6 +23,7 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.GeneratedResource;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JField;
@@ -762,7 +763,8 @@
       os.write(manifestBytes);
 
       GeneratedResource resource = context.commitResource(logger, os);
-      resource.setPrivate(true);
+      // TODO: change to Deploy when possible
+      resource.setVisibility(Visibility.LegacyDeploy);
     } catch (UnsupportedEncodingException e) {
       logger.log(TreeLogger.ERROR,
           SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
diff --git a/user/test/com/google/gwt/module/NoDeployTest.gwt.xml b/user/test/com/google/gwt/module/NoDeployTest.gwt.xml
index a6af83f..8b37ca4 100644
--- a/user/test/com/google/gwt/module/NoDeployTest.gwt.xml
+++ b/user/test/com/google/gwt/module/NoDeployTest.gwt.xml
@@ -18,4 +18,7 @@
   <generate-with class="com.google.gwt.module.rebind.NoDeployGenerator" >
     <when-type-assignable class="com.google.gwt.module.client.NoDeployTest.NoDeploy" />
   </generate-with>
+
+  <!-- servlet for test for deployed but not servable files -->
+  <servlet path='/deploy' class='com.google.gwt.module.server.DeployServlet'/>
 </module>
diff --git a/user/test/com/google/gwt/module/client/NoDeployTest.java b/user/test/com/google/gwt/module/client/NoDeployTest.java
index d8c9d7f..75f2e4a 100644
--- a/user/test/com/google/gwt/module/client/NoDeployTest.java
+++ b/user/test/com/google/gwt/module/client/NoDeployTest.java
@@ -24,10 +24,8 @@
 import com.google.gwt.junit.client.GWTTestCase;
 
 /**
- * Ensure that generated resources in the no-deploy directory aren't in the
- * output. This validates that the
- * {@link com.google.gwt.dev.linker.NoDeployResourcesShim} is being loaded and
- * operating correctly.
+ * Ensure that generated resources are deployed properly according to their
+ * visibility.
  */
 public class NoDeployTest extends GWTTestCase {
 
@@ -37,24 +35,69 @@
   private static class NoDeploy {
   }
 
-  public static final String TEST_TEXT = "Hello world!";
-
   /**
    * The maximum amount of time to wait for an RPC response in milliseconds. 
    */
-  private static final int RESPONSE_DELAY = 5000; 
+  private static final int RESPONSE_DELAY = 5000;
+  private static final String DEPLOY_PREFIX = "deploy?com.google.gwt.module.NoDeployTest.JUnit/"; 
 
   @Override
   public String getModuleName() {
     return "com.google.gwt.module.NoDeployTest";
   }
 
-  public void testDeploy() throws RequestException {
+  /**
+   * Verify that a no-deploy directory in the public path will be deployed.
+   */
+  public void testPublicNoDeployPath() throws RequestException {
+    assertFileIsPublic("no-deploy/", "inPublic.txt");
+  }
+
+  public void testVisibilityDeployHttp() throws RequestException {
+    assertFileIsNotPublic("", "deployFile.txt");
+  }
+
+  public void testVisibilityDeployServer() throws RequestException {
+    assertFileIsDeployed("deployFile.txt");
+  }
+
+  public void testVisibilityLegacyDeployHttp() throws RequestException {
+    assertFileIsNotPublic("", "legacyFile.txt");
+  }
+
+  public void testVisibilityLegacyDeployServer() throws RequestException {
+    assertFileIsDeployed("legacyFile.txt");
+  }
+
+  public void testVisibilityPrivateHttp() throws RequestException {
+      assertFileIsNotPublic("", "privateFile.txt");
+  }
+
+  public void testVisibilityPrivateServer() throws RequestException {
+    assertFileIsNotDeployed("privateFile.txt");
+  }
+
+  public void testVisibilityPublicHttp() throws RequestException {
+    assertFileIsPublic("", "publicFile.txt");
+  }    
+
+  public void testVisibilityPublicServer() throws RequestException {
+    assertFileIsNotDeployed("publicFile.txt");
+  }    
+
+  /**
+   * Fetch a file from a servlet to make sure it is deployed.
+   * @param path
+   *
+   * @throws RequestException
+   */
+  private void assertFileIsDeployed(final String path) 
+      throws RequestException {
     GWT.create(NoDeploy.class);
 
-    // Try fetching a file that should exist
+    // Try fetching a file that should be publicly accessible
     RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
-        GWT.getHostPageBaseURL() + "publicFile.txt");
+        GWT.getHostPageBaseURL() + DEPLOY_PREFIX + path);
     delayTestFinish(RESPONSE_DELAY);
     builder.sendRequest("", new RequestCallback() {
 
@@ -63,28 +106,31 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
-        assertEquals(TEST_TEXT, response.getText());
+        assertEquals(200, response.getStatusCode());
+        assertEquals(path, response.getText());
         finishTest();
       }
     });
   }
 
-  public void testNoDeploy() throws RequestException {
-    if (!GWT.isScript()) {
-      // Linkers aren't used in hosted-mode
-      return;
-    }
-
+  /**
+   * Fetch a file from a servlet to make sure it is not deployed.
+   * @param path
+   *
+   * @throws RequestException
+   */
+  private void assertFileIsNotDeployed(final String path) 
+      throws RequestException {
     GWT.create(NoDeploy.class);
 
-    // Try fetching a file that shouldn't exist
+    // Try fetching a file that should be publicly accessible
     RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
-        GWT.getHostPageBaseURL() + "privateFile.txt");
+        GWT.getHostPageBaseURL() + DEPLOY_PREFIX + path);
     delayTestFinish(RESPONSE_DELAY);
     builder.sendRequest("", new RequestCallback() {
 
       public void onError(Request request, Throwable exception) {
-        fail();
+        throw new RuntimeException(exception);
       }
 
       public void onResponseReceived(Request request, Response response) {
@@ -95,14 +141,47 @@
   }
 
   /**
-   * Verify that a no-deploy directory in the public path will be deployed.
+   * Fetch a file from the HTTP server that should not be publicly accessible.
+   *
+   * @param prefix
+   * @param path
+   * @throws RequestException
    */
-  public void testNoDeployInPublic() throws RequestException {
+  private void assertFileIsNotPublic(final String prefix, final String path) 
+      throws RequestException {
     GWT.create(NoDeploy.class);
 
-    // Try fetching a file that shouldn't exist
+    // Try fetching a file that should be publicly accessible
     RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
-        GWT.getHostPageBaseURL() + "no-deploy/inPublic.txt");
+        GWT.getHostPageBaseURL() + prefix + path);
+    delayTestFinish(RESPONSE_DELAY);
+    builder.sendRequest("", new RequestCallback() {
+
+      public void onError(Request request, Throwable exception) {
+        throw new RuntimeException(exception);
+      }
+
+      public void onResponseReceived(Request request, Response response) {
+        assertEquals(404, response.getStatusCode());
+        finishTest();
+      }
+    });
+  }
+
+  /**
+   * Fetch a file from the HTTP server that should be publicly accessible.
+   *
+   * @param prefix
+   * @param path
+   * @throws RequestException
+   */
+  private void assertFileIsPublic(String prefix, final String path) 
+      throws RequestException {
+    GWT.create(NoDeploy.class);
+
+    // Try fetching a file that should be publicly accessible
+    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
+        GWT.getHostPageBaseURL() + prefix + path);
     delayTestFinish(RESPONSE_DELAY);
     builder.sendRequest("", new RequestCallback() {
 
@@ -111,7 +190,8 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
-        assertEquals(TEST_TEXT, response.getText());
+        assertEquals(200, response.getStatusCode());
+        assertEquals(path, response.getText().trim());
         finishTest();
       }
     });
diff --git a/user/test/com/google/gwt/module/public/no-deploy/inPublic.txt b/user/test/com/google/gwt/module/public/no-deploy/inPublic.txt
index 6769dd6..a529bb3 100644
--- a/user/test/com/google/gwt/module/public/no-deploy/inPublic.txt
+++ b/user/test/com/google/gwt/module/public/no-deploy/inPublic.txt
@@ -1 +1 @@
-Hello world!
\ No newline at end of file
+inPublic.txt
diff --git a/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java b/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
index d5b86f1..22850ee 100644
--- a/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
+++ b/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
@@ -19,14 +19,15 @@
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.dev.util.Util;
-import com.google.gwt.module.client.NoDeployTest;
 
 import java.io.IOException;
 import java.io.OutputStream;
 
 /**
- * Creates two files in the generated output directory.
+ * Creates three files in the generated output directory with different
+ * visibility.
  */
 public class NoDeployGenerator extends Generator {
 
@@ -35,8 +36,10 @@
       String typeName) throws UnableToCompleteException {
 
     try {
-      createFile(logger, context, "publicFile.txt", false);
-      createFile(logger, context, "privateFile.txt", true);
+      createFile(logger, context, "publicFile.txt", Visibility.Public);
+      createFile(logger, context, "deployFile.txt", Visibility.Deploy);
+      createFile(logger, context, "privateFile.txt", Visibility.Private);
+      createFile(logger, context, "legacyFile.txt", Visibility.LegacyDeploy);
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to create test file", e);
       throw new UnableToCompleteException();
@@ -46,7 +49,7 @@
   }
 
   private void createFile(TreeLogger logger, GeneratorContext context,
-      String path, boolean isPrivate) throws UnableToCompleteException,
+      String path, Visibility visibility) throws UnableToCompleteException,
       IOException {
 
     OutputStream out = context.tryCreateResource(logger, path);
@@ -54,7 +57,7 @@
       return;
     }
 
-    out.write(Util.getBytes(NoDeployTest.TEST_TEXT));
-    context.commitResource(logger, out).setPrivate(isPrivate);
+    out.write(Util.getBytes(path));
+    context.commitResource(logger, out).setVisibility(visibility);
   }
 }
diff --git a/user/test/com/google/gwt/module/server/DeployServlet.java b/user/test/com/google/gwt/module/server/DeployServlet.java
new file mode 100644
index 0000000..853beb9
--- /dev/null
+++ b/user/test/com/google/gwt/module/server/DeployServlet.java
@@ -0,0 +1,69 @@
+/*
+ * 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.module.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A servlet that returns files in the WEB-INF/deploy directory.
+ */
+public class DeployServlet extends HttpServlet {
+
+  @Override
+  public void init() throws ServletException {
+    getServletContext().log("DeployServlet initialized");
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse res)
+      throws IOException {
+    String path = req.getQueryString();
+    ServletContext context = getServletContext();
+    context.log("DeployServlet get: " + path);
+    InputStream istr = null;
+    ServletOutputStream ostr = null;
+    try {
+      istr = context.getResourceAsStream("/WEB-INF/deploy/" + path);
+      if (istr == null) {
+        res.sendError(HttpServletResponse.SC_NOT_FOUND);
+        return;
+      }
+      res.setStatus(HttpServletResponse.SC_OK);
+      res.setContentType("text/plain");
+      ostr = res.getOutputStream();
+      byte[] buf = new byte[4096];
+      int n;
+      while ((n = istr.read(buf)) >= 0) {
+        ostr.write(buf, 0, n);
+      }
+    } finally {
+      if (istr != null) {
+        istr.close();
+      }
+      if (ostr != null) {
+        ostr.flush();
+      }
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/precompress/linker/PrecompressLinkerTest.java b/user/test/com/google/gwt/precompress/linker/PrecompressLinkerTest.java
index ddd9a53..6d58363 100644
--- a/user/test/com/google/gwt/precompress/linker/PrecompressLinkerTest.java
+++ b/user/test/com/google/gwt/precompress/linker/PrecompressLinkerTest.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.SyntheticArtifact;
 
@@ -180,7 +181,7 @@
 
   private static SyntheticArtifact emitPrivate(String string, String contents) {
     SyntheticArtifact art = emit(string, contents);
-    art.setPrivate(true);
+    art.setVisibility(Visibility.Private);
     return art;
   }