Fix python_library so it caches correctly.

Summary:
Previously, if you had a python_binary rule that depended on a
python_library rule, and both rules were cached in cassandra,
there would be an issue executing the python_binary rule
(presumably as part of a genrule) because the files from the
python_library rule would not be in buck-out/bin/ where the
python_binary rule expected them to be.

This changes things so that the python_library writes its files
to buck-out/gen/, and those files are archived with the python_library
rule such that when it is pulled down from cassandra, it is
unpacked in the way that python_binary needs.

Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/python/PythonBinaryRule.java b/src/com/facebook/buck/python/PythonBinaryRule.java
index 57a19cc..a69972e 100644
--- a/src/com/facebook/buck/python/PythonBinaryRule.java
+++ b/src/com/facebook/buck/python/PythonBinaryRule.java
@@ -20,9 +20,6 @@
 
 import com.facebook.buck.rules.AbstractBuildRuleBuilder;
 import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
-import com.facebook.buck.rules.Buildable;
-import com.facebook.buck.rules.BuildableContext;
-import com.facebook.buck.rules.DoNotUseAbstractBuildable;
 import com.facebook.buck.rules.AbstractDependencyVisitor;
 import com.facebook.buck.rules.BinaryBuildRule;
 import com.facebook.buck.rules.BuildContext;
@@ -30,19 +27,21 @@
 import com.facebook.buck.rules.BuildRuleParams;
 import com.facebook.buck.rules.BuildRuleResolver;
 import com.facebook.buck.rules.BuildRuleType;
+import com.facebook.buck.rules.Buildable;
+import com.facebook.buck.rules.BuildableContext;
 import com.facebook.buck.rules.BuildableProperties;
+import com.facebook.buck.rules.DoNotUseAbstractBuildable;
 import com.facebook.buck.rules.RuleKey;
 import com.facebook.buck.step.Step;
 import com.facebook.buck.util.ProjectFilesystem;
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 
 public class PythonBinaryRule extends DoNotUseAbstractBuildable implements BinaryBuildRule {
@@ -73,16 +72,17 @@
 
   @Override
   public String getExecutableCommand(ProjectFilesystem projectFilesystem) {
-    Function<String, String> pathRelativizer = projectFilesystem.getPathRelativizer();
-    String pythonPath = Joiner.on(':').join(Iterables.transform(getPythonPathEntries(),
-        pathRelativizer));
+    String pythonPath = Joiner.on(':').join(
+        Iterables.transform(
+            getPythonPathEntries(),
+            projectFilesystem.getAbsolutifier()));
     return String.format("PYTHONPATH=%s python %s",
         pythonPath,
-        pathRelativizer.apply(main));
+        projectFilesystem.getPathRelativizer().apply(main));
   }
 
-  private ImmutableSet<String> getPythonPathEntries() {
-    final ImmutableSet.Builder<String> entries = ImmutableSet.builder();
+  private ImmutableSet<Path> getPythonPathEntries() {
+    final ImmutableSet.Builder<Path> entries = ImmutableSet.builder();
 
     final PythonBinaryRule pythonBinaryRule = this;
     new AbstractDependencyVisitor(this) {
@@ -93,10 +93,8 @@
         if (buildable instanceof PythonLibrary) {
           PythonLibrary pythonLibrary = (PythonLibrary) buildable;
 
-          Optional<String> pythonPathEntry = pythonLibrary.getPythonPathDirectory();
-          if (pythonPathEntry.isPresent()) {
-            entries.add(pythonPathEntry.get());
-          }
+          Path pythonPathEntry = pythonLibrary.getPythonPathDirectory();
+          entries.add(pythonPathEntry);
           return true;
         }
 
diff --git a/src/com/facebook/buck/python/PythonLibrary.java b/src/com/facebook/buck/python/PythonLibrary.java
index 889d109..ae0ee09 100644
--- a/src/com/facebook/buck/python/PythonLibrary.java
+++ b/src/com/facebook/buck/python/PythonLibrary.java
@@ -21,24 +21,26 @@
 import com.facebook.buck.model.BuildTarget;
 import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
 import com.facebook.buck.rules.AbstractBuildable;
-import com.facebook.buck.rules.BuildableContext;
 import com.facebook.buck.rules.BuildContext;
 import com.facebook.buck.rules.BuildRuleParams;
 import com.facebook.buck.rules.BuildRuleResolver;
 import com.facebook.buck.rules.BuildRuleType;
+import com.facebook.buck.rules.BuildableContext;
 import com.facebook.buck.rules.BuildableProperties;
 import com.facebook.buck.rules.RuleKey;
 import com.facebook.buck.rules.SrcsAttributeBuilder;
 import com.facebook.buck.step.Step;
+import com.facebook.buck.step.fs.CopyStep;
 import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
-import com.facebook.buck.step.fs.SymlinkFileStep;
 import com.facebook.buck.util.BuckConstant;
-import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSortedSet;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 
 import javax.annotation.Nullable;
@@ -48,18 +50,18 @@
   private final static BuildableProperties OUTPUT_TYPE = new BuildableProperties(LIBRARY);
   private final BuildTarget buildTarget;
   private final ImmutableSortedSet<String> srcs;
-  private final Optional<String> pythonPathDirectory;
+  private final Path pythonPathDirectory;
 
   protected PythonLibrary(BuildRuleParams buildRuleParams,
                           ImmutableSortedSet<String> srcs) {
+    Preconditions.checkArgument(!srcs.isEmpty(),
+        "Must specify srcs for %s.",
+        buildRuleParams.getBuildTarget());
+
     this.buildTarget = buildRuleParams.getBuildTarget();
     this.srcs = ImmutableSortedSet.copyOf(srcs);
 
-    if (srcs.isEmpty()) {
-      this.pythonPathDirectory = Optional.absent();
-    } else {
-      this.pythonPathDirectory = Optional.of(getPathToPythonPathDirectory());
-    }
+    this.pythonPathDirectory = getPathToPythonPathDirectory();
   }
 
   @Nullable
@@ -70,16 +72,18 @@
 
   @Override
   public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) throws IOException {
-    return builder
-        .set("srcs", srcs)
-        .set("pythonPathDirectory", pythonPathDirectory);
+    return builder.set("srcs", srcs);
   }
 
-  private String getPathToPythonPathDirectory() {
-    return String.format("%s/%s/__pylib_%s/",
-        BuckConstant.BIN_DIR,
+  private Path getPathToPythonPathDirectory() {
+    return Paths.get(
+        BuckConstant.GEN_DIR,
         buildTarget.getBasePath(),
-        buildTarget.getShortName());
+        getPathUnderGenDirectory());
+  }
+
+  private String getPathUnderGenDirectory() {
+    return "__pylib_" + buildTarget.getShortName();
   }
 
   public ImmutableSortedSet<String> getPythonSrcs() {
@@ -91,25 +95,28 @@
     return srcs;
   }
 
-  public Optional<String> getPythonPathDirectory() {
+  public Path getPythonPathDirectory() {
     return pythonPathDirectory;
   }
 
   @Override
-  public List<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext) throws IOException {
+  public List<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext)
+      throws IOException {
     ImmutableList.Builder<Step> commands = ImmutableList.builder();
 
-    // Symlink all of the sources to a generated directory so that the generated directory can be
-    // included as a $PYTHONPATH element.
+    // Copy all of the sources to a generated directory so that the generated directory can be
+    // included as a $PYTHONPATH element. Symlinks would be more efficient, but we need to include
+    // this structure in the artifact, which is not guaranteed to be zip-friendly.
     // TODO(mbolin): Do not flatten the directory structure when creating the symlinks, as directory
     // structure is significant in Python when __init__.py is used.
-    if (!srcs.isEmpty()) {
-      commands.add(new MakeCleanDirectoryStep(getPathToPythonPathDirectory()));
-      File pythonPathDirectory = new File(getPathToPythonPathDirectory());
-      for (String src : srcs) {
-        File target = new File(pythonPathDirectory, new File(src).getName());
-        commands.add(new SymlinkFileStep(src, target.getPath()));
-      }
+    commands.add(new MakeCleanDirectoryStep(pythonPathDirectory));
+    for (String src : srcs) {
+      String srcName = new File(src).getName();
+      Path target = pythonPathDirectory.resolve(srcName);
+      commands.add(new CopyStep(src, target.toString()));
+
+      Path pathToArtifact = Paths.get(getPathUnderGenDirectory(), srcName);
+      buildableContext.recordArtifact(pathToArtifact.toString());
     }
 
     return commands.build();
diff --git a/src/com/facebook/buck/rules/RuleKey.java b/src/com/facebook/buck/rules/RuleKey.java
index c3b1aaf..e646900 100644
--- a/src/com/facebook/buck/rules/RuleKey.java
+++ b/src/com/facebook/buck/rules/RuleKey.java
@@ -36,6 +36,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -278,6 +279,10 @@
       return setKey(key).setVal(val);
     }
 
+    public Builder set(String key, @Nullable Path path) throws IOException {
+      return set(key, path == null ? null : path.toFile());
+    }
+
     public Builder set(String key, @Nullable String val) {
       return setKey(key).setVal(val);
     }
diff --git a/src/com/facebook/buck/step/fs/RmStep.java b/src/com/facebook/buck/step/fs/RmStep.java
index 4194c09..c41d34f 100644
--- a/src/com/facebook/buck/step/fs/RmStep.java
+++ b/src/com/facebook/buck/step/fs/RmStep.java
@@ -91,7 +91,7 @@
       }
     } else {
       // Delete a single file
-      File file = projectFilesystem.resolve(patternToDelete).toAbsolutePath().toFile();
+      File file = projectFilesystem.resolve(patternToDelete).toFile();
       if (!file.delete() && !shouldForceDeletion) {
         return 1;
       }
diff --git a/src/com/facebook/buck/util/ProjectFilesystem.java b/src/com/facebook/buck/util/ProjectFilesystem.java
index a5d5e8d..61f7ae1 100644
--- a/src/com/facebook/buck/util/ProjectFilesystem.java
+++ b/src/com/facebook/buck/util/ProjectFilesystem.java
@@ -51,7 +51,13 @@
 
   private final File projectRoot;
   private final Path pathToRoot;
+
+  // Over time, we hope to replace our uses of String with Path, as appropriate. When that happens,
+  // the Function<String, String> can go away, as Function<Path, Path> should be used exclusively.
+
+  private final Function<Path, Path> pathAbsolutifier;
   private final Function<String, String> pathRelativizer;
+
   private final ImmutableSet<String> ignorePaths;
 
   /**
@@ -66,10 +72,16 @@
     this.projectRoot = Preconditions.checkNotNull(projectRoot);
     this.pathToRoot = projectRoot.toPath();
     Preconditions.checkArgument(projectRoot.isDirectory());
+    this.pathAbsolutifier = new Function<Path, Path>() {
+      @Override
+      public Path apply(Path path) {
+        return resolve(path);
+      }
+    };
     this.pathRelativizer = new Function<String, String>() {
       @Override
       public String apply(String relativePath) {
-        return ProjectFilesystem.this.getFileForRelativePath(relativePath).getAbsolutePath();
+        return getFileForRelativePath(relativePath).getAbsolutePath();
       }
     };
     this.ignorePaths = Preconditions.checkNotNull(ignorePaths);
@@ -83,9 +95,18 @@
     return pathToRoot;
   }
 
-  /** @return the specified {@code path} resolved against {@link #getRootPath()}. */
+  /**
+   * @return the specified {@code path} resolved against {@link #getRootPath()} to an absolute path.
+   */
   public Path resolve(Path path) {
-    return pathToRoot.resolve(path);
+    return pathToRoot.resolve(path).toAbsolutePath();
+  }
+
+  /**
+   * @return A {@link Function} that applies {@link #resolve(Path)} to its parameter.
+   */
+  public Function<Path, Path> getAbsolutifier() {
+    return pathAbsolutifier;
   }
 
   public File getProjectRoot() {
diff --git a/test/com/facebook/buck/android/ApkGenruleTest.java b/test/com/facebook/buck/android/ApkGenruleTest.java
index b450ea2..8362f74 100644
--- a/test/com/facebook/buck/android/ApkGenruleTest.java
+++ b/test/com/facebook/buck/android/ApkGenruleTest.java
@@ -61,6 +61,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Map;
@@ -169,7 +170,7 @@
         ImmutableList.of(
             "rm",
             "-f",
-            "./" + apkGenrule.getPathToOutputFile()),
+            apkGenrule.getPathToOutputFile()),
         rmCommand.getShellCommand(executionContext));
 
     Step secondStep = steps.get(1);
@@ -177,7 +178,7 @@
     MkdirStep mkdirCommand = (MkdirStep) secondStep;
     assertEquals(
         "Second command should make sure the output directory exists.",
-        Paths.get("./" + GEN_DIR + "/src/com/facebook/"),
+        Paths.get(GEN_DIR + "/src/com/facebook/"),
         mkdirCommand.getPath(executionContext));
 
     Step thirdStep = steps.get(2);
@@ -235,6 +236,11 @@
           public Function<String, String> getPathRelativizer() {
             return Functions.identity();
           }
+
+          @Override
+          public Path resolve(Path path) {
+            return path;
+          }
         })
         .setEventBus(BuckEventBusFactory.newInstance())
         .setPlatform(Platform.LINUX) // Fix platform to Linux to use bash in genrule.
diff --git a/test/com/facebook/buck/android/GenAidlTest.java b/test/com/facebook/buck/android/GenAidlTest.java
index 6351d60..978e3c5 100644
--- a/test/com/facebook/buck/android/GenAidlTest.java
+++ b/test/com/facebook/buck/android/GenAidlTest.java
@@ -88,7 +88,12 @@
     ExecutionContext executionContext = createMock(ExecutionContext.class);
     expect(executionContext.getAndroidPlatformTarget()).andReturn(androidPlatformTarget);
     expect(executionContext.getProjectFilesystem()).andReturn(
-        new ProjectFilesystem(new File(".")))
+        new ProjectFilesystem(new File(".")) {
+          @Override
+          public Path resolve(Path path) {
+            return path;
+          }
+        })
         .times(2);
     replay(androidPlatformTarget,
         aidlExecutable,
@@ -96,7 +101,6 @@
         executionContext);
 
     Path outputDirectory = Paths.get(
-        ".",
         BuckConstant.BIN_DIR,
         "/java/com/example/base/.IWhateverService.aidl");
     MkdirStep mkdirStep = (MkdirStep) steps.get(0);
diff --git a/test/com/facebook/buck/shell/GenruleTest.java b/test/com/facebook/buck/shell/GenruleTest.java
index c5c6b6e..1dfab56 100644
--- a/test/com/facebook/buck/shell/GenruleTest.java
+++ b/test/com/facebook/buck/shell/GenruleTest.java
@@ -70,6 +70,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Map;
@@ -175,7 +176,7 @@
         ImmutableList.of(
             "rm",
             "-f",
-            "./" + GEN_DIR + "/src/com/facebook/katana/AndroidManifest.xml"),
+            GEN_DIR + "/src/com/facebook/katana/AndroidManifest.xml"),
         rmCommand.getShellCommand(executionContext));
 
     Step secondStep = steps.get(1);
@@ -183,7 +184,7 @@
     MkdirStep mkdirCommand = (MkdirStep) secondStep;
     assertEquals(
         "Second command should make sure the output directory exists.",
-        Paths.get("./" + GEN_DIR + "/src/com/facebook/katana"),
+        Paths.get(GEN_DIR + "/src/com/facebook/katana"),
         mkdirCommand.getPath(executionContext));
 
     Step mkTmpDir = steps.get(2);
@@ -245,6 +246,11 @@
           public Function<String, String> getPathRelativizer() {
             return Functions.identity();
           }
+
+          @Override
+          public Path resolve(Path path) {
+            return path;
+          }
         })
         .setEventBus(BuckEventBusFactory.newInstance())
         .setPlatform(platform)