Repository: bazelbuild/bazel
Issue: Allow options to use ~ or $HOME
Labels: type: feature request
P3
category: misc > misc

It would be nice for users to be able to specify user-specific paths.  We'd probably have to add support for both client & server path parsing.

See https://github.com/bazelbuild/bazel/issues/1752#issuecomment-258642717 for context.


Issue comments:
this (in combination with #1752) sounds valuable since it lowers the entry costs of an organization. People can check out and best practices are laid out in the shared bazelrc 
+1

Pull request review comments:
Please add an `OS.getCurrent()` check around this `if`, and only replace "~" on Linux/macOS.
here too
(`OS` in the same Java package)
AFAIK, `user.home` should be resolved just fine on Windows and recent JDK versions: [1]. On Windows, on Java 8, we have:

```java
public class Test {
  public static void main(String[] args) {
    String path = "~/foo/";
    path = path.replace("~", System.getProperty("user.home"));
    System.out.println(path);
  }
}
```

Running it produces:

```shell
C:\>java Test
C:\Users\davido/foo/
```

FTR: I backported this solution from that Buck feature: [Add support for user home in cache directory name](https://github.com/facebook/buck/commit/a1ba00118666c39f87354e3c0f2226e6f87c6970) that I added to Buck 4 years ago. And of course, Buck is platform independent.

[1] https://bugs.java.com/view_bug.do?bug_id=4787931
[2] https://stackoverflow.com/questions/2134338/java-user-home-is-being-set-to-userprofile-and-not-being-resolved

Right, but `~abc` is a valid directory name on Windows. So I'm reluctant to expand "~" to user.home on Windows too.
We need a working and platform independent solution to configure local action path per default and commit it in (D)VCS. I am open to any suggestions, but we just can't solve the problem at hand for Linux/macOS, and ignore Windows users.

Should we compare for "~/" instead on all platforms, a lá:

```java
 if (path.startsWith("~/")) {
 [...]
```
?
Yes please, that'd be better.
Done.

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java b/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
index c94863ed86d01a..029d08c8c10ba9 100644
--- a/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.util;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.common.options.Converter;
@@ -103,7 +104,11 @@ public static class PathFragmentConverter
 
     @Override
     public PathFragment convert(String input) {
-      return PathFragment.create(input);
+      String path = Preconditions.checkNotNull(input);
+      if (!path.isEmpty() && path.startsWith("~/")) {
+        path = path.replace("~", System.getProperty("user.home"));
+      }
+      return PathFragment.create(path);
     }
 
     @Override
@@ -123,6 +128,9 @@ public List<PathFragment> convert(String input) {
       List<PathFragment> list = new ArrayList<>();
       for (String piece : input.split(":")) {
         if (!piece.isEmpty()) {
+          if (piece.startsWith("~/")) {
+            piece = piece.replace("~", System.getProperty("user.home"));
+          }
           list.add(PathFragment.create(piece));
         }
       }


Test patch:
diff --git a/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java b/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java
index ddf8dadda93f9e..2d7d2f75f93ff8 100644
--- a/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java
@@ -17,6 +17,7 @@
 import static org.junit.Assert.fail;
 
 import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.util.OptionsUtils.PathFragmentConverter;
 import com.google.devtools.build.lib.util.OptionsUtils.PathFragmentListConverter;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.common.options.Option;
@@ -166,6 +167,10 @@ private List<PathFragment> convert(String input) throws Exception {
     return new PathFragmentListConverter().convert(input);
   }
 
+  private PathFragment convertOne(String input) throws Exception {
+    return new PathFragmentConverter().convert(input);
+  }
+
   @Test
   public void emptyStringYieldsEmptyList() throws Exception {
     assertThat(convert("")).isEqualTo(list());
@@ -183,12 +188,23 @@ public void converterSkipsEmptyStrings() throws Exception {
 
   @Test
   public void multiplePaths() throws Exception {
-    assertThat(convert("foo:/bar/baz:.:/tmp/bang"))
+    assertThat(convert("~/foo:foo:/bar/baz:.:/tmp/bang"))
         .containsExactly(
-            fragment("foo"), fragment("/bar/baz"), fragment("."), fragment("/tmp/bang"))
+            fragment(System.getProperty("user.home") + "/foo"),
+            fragment("foo"),
+            fragment("/bar/baz"),
+            fragment("."),
+            fragment("/tmp/bang"))
         .inOrder();
   }
 
+  @Test
+  public void singlePath() throws Exception {
+    assertThat(convertOne("foo")).isEqualTo(fragment("foo"));
+    assertThat(convertOne("foo/bar/baz")).isEqualTo(fragment("foo/bar/baz"));
+    assertThat(convertOne("~/foo")).isEqualTo(fragment(System.getProperty("user.home") + "/foo"));
+  }
+
   @Test
   public void valueisUnmodifiable() throws Exception {
     try {