Make labels in .bzl files in remote repos resolve relative to their repo

For example, if you have a BUILD file that does:

load('@foo//bar:baz.bzl', 'my_rule')
my_rule(...)

If baz.bzl uses Label('//whatever'), this change makes //whatever resolve to
@foo//whatever. Previous to this change, it would be resolved to the repository
the BUILD file using my_rule was in.

RELNOTES[INC]: Labels in .bzl files in remote repositories will be resolved
relative to their repository (instead of the repository the Skylark rule is
used in).

--
MOS_MIGRATED_REVID=117720181
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
index 798b14d..efe4573 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -518,7 +518,7 @@
       EventHandler eventHandler,
       String astFileContentHashCode,
       Map<String, Extension> importMap) {
-    Environment env = Environment.builder(mutability)
+    return Environment.builder(mutability)
         .setSkylark()
         .setGlobals(globals)
         .setEventHandler(eventHandler)
@@ -526,17 +526,17 @@
         .setImportedExtensions(importMap)
         .setLoadingPhase()
         .build();
-    return env;
   }
 
   @Override
   public Environment createSkylarkRuleClassEnvironment(
-      Mutability mutability,
+      Label extensionLabel, Mutability mutability,
       EventHandler eventHandler,
       String astFileContentHashCode,
       Map<String, Extension> importMap) {
     return createSkylarkRuleClassEnvironment(
-        mutability, globals, eventHandler, astFileContentHashCode, importMap);
+        mutability, globals.setLabel(extensionLabel),
+        eventHandler, astFileContentHashCode, importMap);
   }
 
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
index f6529a6..2f0988f 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
@@ -49,12 +49,15 @@
    * Returns a new Skylark Environment instance for rule creation.
    * Implementations need to be thread safe.
    * Be sure to close() the mutability before you return the results of said evaluation.
+   *
+   * @param label the location of the rule.
    * @param mutability the Mutability for the current evaluation context
    * @param eventHandler the EventHandler for warnings, errors, etc.
    * @param astFileContentHashCode the hash code identifying this environment.
    * @return an Environment, in which to evaluate load time skylark forms.
    */
   Environment createSkylarkRuleClassEnvironment(
+      Label label,
       Mutability mutability,
       EventHandler eventHandler,
       @Nullable String astFileContentHashCode,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index 8ca4b64..a671496 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -42,6 +42,7 @@
 import com.google.devtools.build.lib.analysis.config.RunUnder;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.cmdline.LabelValidator;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
@@ -163,7 +164,7 @@
   // rules to skylark extensions. Using the same instance would require a large refactoring.
   // If we don't want to support old built-in rules and Skylark simultaneously
   // (except for transition phase) it's probably OK.
-  private static LoadingCache<String, Label> labelCache =
+  private static final LoadingCache<String, Label> labelCache =
       CacheBuilder.newBuilder().build(new CacheLoader<String, Label>() {
     @Override
     public Label load(String from) throws Exception {
@@ -613,16 +614,36 @@
       + "Example: <br><pre class=language-python>Label(\"//tools:default\")</pre>",
       returnType = Label.class,
       mandatoryPositionals = {@Param(name = "label_string", type = String.class,
-            doc = "the label string")},
-      useLocation = true)
+          doc = "the label string")},
+      optionalNamedOnly = {@Param(
+          name = "relative_to_caller_repository",
+          type = Boolean.class,
+          defaultValue = "False",
+          doc = "whether the label should be resolved relative to the label of the file this "
+              + "function is called from.")},
+      useLocation = true,
+      useEnvironment = true)
   private static final BuiltinFunction label = new BuiltinFunction("Label") {
-      public Label invoke(String labelString,
-          Location loc) throws EvalException, ConversionException {
-          try {
-            return labelCache.get(labelString);
-          } catch (ExecutionException e) {
-            throw new EvalException(loc, "Illegal absolute label syntax: " + labelString);
+      @SuppressWarnings({"unchecked", "unused"})
+      public Label invoke(
+          String labelString, Boolean relativeToCallerRepository, Location loc, Environment env)
+          throws EvalException {
+        Label parentLabel = null;
+        if (relativeToCallerRepository) {
+          parentLabel = env.getCallerLabel();
+        } else {
+          parentLabel = env.getGlobals().label();
+        }
+        try {
+          if (parentLabel != null) {
+            LabelValidator.parseAbsoluteLabel(labelString);
+            labelString = parentLabel.getRelative(labelString)
+                .getUnambiguousCanonicalForm();
           }
+          return labelCache.get(labelString);
+        } catch (LabelValidator.BadLabelException | LabelSyntaxException | ExecutionException e) {
+          throw new EvalException(loc, "Illegal absolute label syntax: " + labelString);
+        }
       }
     };
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index a264278..db0f970 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -69,6 +69,7 @@
       SkylarkRuleContext skylarkRuleContext = new SkylarkRuleContext(ruleContext, Kind.RULE);
       Environment env = Environment.builder(mutability)
           .setSkylark()
+          .setCallerLabel(ruleContext.getLabel())
           .setGlobals(
               ruleContext.getRule().getRuleClassObject().getRuleDefinitionEnvironment().getGlobals())
           .setEventHandler(ruleContext.getAnalysisEnvironment().getEventHandler())
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java
index c030ccc..e0bafdf 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ASTFileLookupFunction.java
@@ -105,6 +105,7 @@
             ast = BuildFileAST.parseSkylarkFile(path, astFileSize, env.getListener(),
                 new ValidationEnvironment(
                     ruleClassProvider.createSkylarkRuleClassEnvironment(
+                        fileLabel,
                         mutability,
                         env.getListener(),
                         // the two below don't matter for extracting the ValidationEnvironment:
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
index 47ee3ef..c4889fa 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
@@ -360,7 +360,7 @@
       com.google.devtools.build.lib.syntax.Environment extensionEnv =
           ruleClassProvider
               .createSkylarkRuleClassEnvironment(
-                  mutability, eventHandler, ast.getContentHashCode(), importMap)
+                  extensionLabel, mutability, eventHandler, ast.getContentHashCode(), importMap)
               .setupOverride("native", packageFactory.getNativeModule(inWorkspace));
       ast.exec(extensionEnv, eventHandler);
       try {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
index f25aed6..2338b31 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.events.EventKind;
@@ -98,10 +99,14 @@
     private final Mutability mutability;
     final Frame parent;
     final Map<String, Object> bindings = new HashMap<>();
+    // The label for the target this frame is defined in (e.g., //foo:bar.bzl).
+    @Nullable
+    private Label label;
 
-    Frame(Mutability mutability, Frame parent) {
+    private Frame(Mutability mutability, Frame parent) {
       this.mutability = mutability;
       this.parent = parent;
+      this.label = parent == null ? null : parent.label;
     }
 
     @Override
@@ -110,6 +115,25 @@
     }
 
     /**
+     * Attaches a label to an existing frame. This is used to get the repository a Skylark
+     * extension is actually defined in.
+     * @param label the label to attach.
+     * @return a new Frame with the existing frame's properties plus the label.
+     */
+    public Frame setLabel(Label label) {
+      this.label = label;
+      return this;
+    }
+
+    /**
+     * Returns the label for this frame.
+     */
+    @Nullable
+    public final Label label() {
+      return label;
+    }
+
+    /**
      * Gets a binding from the current frame or if not found its parent.
      * @param varname the name of the variable to be bound
      * @return the value bound to variable
@@ -325,6 +349,13 @@
   @Nullable private Continuation continuation;
 
   /**
+   * Gets the label of the BUILD file that is using this environment. For example, if a target
+   * //foo has a dependency on //bar which is a Skylark rule defined in //rules:my_rule.bzl being
+   * evaluated in this environment, then this would return //foo.
+   */
+  @Nullable private final Label callerLabel;
+
+  /**
    * Enters a scope by saving state to a new Continuation
    * @param function the function whose scope to enter
    * @param caller the source AST node for the caller
@@ -394,7 +425,7 @@
   /**
    * @return the current Frame, in which variable side-effects happen.
    */
-  private Frame currentFrame() {
+  public Frame currentFrame() {
     return isGlobal() ? globalFrame : lexicalFrame;
   }
 
@@ -450,6 +481,7 @@
    * @param isSkylark true if in Skylark context
    * @param fileContentHashCode a hash for the source file being evaluated, if any
    * @param isLoadingPhase true if in loading phase
+   * @param callerLabel the label this environment came from
    */
   private Environment(
       Frame globalFrame,
@@ -458,7 +490,8 @@
       Map<String, Extension> importedExtensions,
       boolean isSkylark,
       @Nullable String fileContentHashCode,
-      boolean isLoadingPhase) {
+      boolean isLoadingPhase,
+      @Nullable Label callerLabel) {
     this.globalFrame = Preconditions.checkNotNull(globalFrame);
     this.dynamicFrame = Preconditions.checkNotNull(dynamicFrame);
     Preconditions.checkArgument(globalFrame.mutability().isMutable());
@@ -468,6 +501,7 @@
     this.isSkylark = isSkylark;
     this.fileContentHashCode = fileContentHashCode;
     this.isLoadingPhase = isLoadingPhase;
+    this.callerLabel = callerLabel;
   }
 
   /**
@@ -481,6 +515,7 @@
     @Nullable private EventHandler eventHandler;
     @Nullable private Map<String, Extension> importedExtensions;
     @Nullable private String fileContentHashCode;
+    private Label label;
 
     Builder(Mutability mutability) {
       this.mutability = mutability;
@@ -545,7 +580,13 @@
           importedExtensions,
           isSkylark,
           fileContentHashCode,
-          isLoadingPhase);
+          isLoadingPhase,
+          label);
+    }
+
+    public Builder setCallerLabel(Label label) {
+      this.label = label;
+      return this;
     }
   }
 
@@ -554,6 +595,13 @@
   }
 
   /**
+   * Returns the caller's label.
+   */
+  public Label getCallerLabel() {
+    return callerLabel;
+  }
+
+  /**
    * Sets a binding for a special dynamic variable in this Environment.
    * This is not for end-users, and will throw an AssertionError in case of conflict.
    * @param varname the name of the dynamic variable to be bound
@@ -856,7 +904,7 @@
 
   /**
    * Parses some String input without a supporting file, returning statements and comments.
-   * @param input a list of lines of code
+   * @param inputLines a list of lines of code
    */
   @VisibleForTesting
   Parser.ParseResult parseFileWithComments(String... inputLines) {
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
index 113ba74..61a6ba8 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
@@ -789,7 +789,6 @@
 
   @Test
   public void testRelativeLabelInExternalRepository() throws Exception {
-    scratch.file("BUILD");
     scratch.file("external_rule.bzl",
         "def _impl(ctx):",
         "  return",
@@ -800,6 +799,37 @@
         "  }",
         ")");
 
+    scratch.file("BUILD",
+        "filegroup(name='dep')");
+
+    scratch.file("/r/a/BUILD",
+        "load('/external_rule', 'external_rule')",
+        "external_rule(name='r')");
+
+    scratch.overwriteFile("WORKSPACE",
+        "local_repository(name='r', path='/r')");
+
+    invalidatePackages();
+    SkylarkRuleContext context = createRuleContext("@r//a:r");
+    Label depLabel = (Label) evalRuleContextCode(context, "ruleContext.attr.internal_dep.label");
+    assertThat(depLabel).isEqualTo(Label.parseAbsolute("//:dep"));
+  }
+
+  @Test
+  public void testCallerRelativeLabelInExternalRepository() throws Exception {
+    scratch.file("BUILD");
+    scratch.file("external_rule.bzl",
+        "def _impl(ctx):",
+        "  return",
+        "external_rule = rule(",
+        "  implementation = _impl,",
+        "  attrs = {",
+        "    'internal_dep': attr.label(",
+        "        default = Label('//:dep', relative_to_caller_repository = True)",
+        "    )",
+        "  }",
+        ")");
+
     scratch.file("/r/BUILD",
         "filegroup(name='dep')");
 
diff --git a/src/test/shell/bazel/external_skylark_load_test.sh b/src/test/shell/bazel/external_skylark_load_test.sh
index 5f221c0..bd8318a 100755
--- a/src/test/shell/bazel/external_skylark_load_test.sh
+++ b/src/test/shell/bazel/external_skylark_load_test.sh
@@ -123,6 +123,117 @@
     "LOCAL!"
 }
 
+function test_skylark_repository_relative_label() {
+  repo2=$TEST_TMPDIR/repo2
+  mkdir -p $repo2
+  touch $repo2/WORKSPACE $repo2/BUILD
+  cat > $repo2/remote.bzl <<EOF
+def _impl(ctx):
+  print(Label("//foo:bar"))
+
+remote_rule = rule(
+    implementation = _impl,
+)
+EOF
+
+  cat > WORKSPACE <<EOF
+local_repository(
+    name = "r",
+    path = "$repo2",
+)
+EOF
+  cat > BUILD <<EOF
+load('@r//:remote.bzl', 'remote_rule')
+
+remote_rule(name = 'local')
+EOF
+
+  bazel build //:local &> $TEST_log || fail "Building local failed"
+  expect_log "@r//foo:bar"
+
+  cat > $repo2/remote.bzl <<EOF
+def _impl(ctx):
+  print(Label("//foo:bar", relative_to_caller_repository = True))
+
+remote_rule = rule(
+    implementation = _impl,
+)
+EOF
+  bazel build //:local &> $TEST_log || fail "Building local failed"
+  expect_log "//foo:bar"
+}
+
+# Going one level deeper: if we have:
+# local/
+#   BUILD
+# r1/
+#   BUILD
+# r2/
+#   BUILD
+#   remote.bzl
+# If //foo in local depends on //bar in r1, which is a Skylark rule
+# defined in r2/remote.bzl, then a Label in remote.bzl should either
+# resolve to @r2//whatever or @r1//whatever.
+function test_skylark_repository_nested_relative_label() {
+  repo1=$TEST_TMPDIR/repo1
+  repo2=$TEST_TMPDIR/repo2
+  mkdir -p $repo1 $repo2
+
+  # local
+  cat > WORKSPACE <<EOF
+local_repository(
+    name = "r1",
+    path = "$repo1",
+)
+local_repository(
+    name = "r2",
+    path = "$repo2",
+)
+EOF
+  cat > BUILD <<'EOF'
+genrule(
+    name = "foo",
+    srcs = ["@r1//:bar"],
+    outs = ["foo.out"],
+    cmd = "echo '$(SRCS)' > $@",
+)
+EOF
+
+  # r1
+  touch $repo1/WORKSPACE
+  cat > $repo1/BUILD <<EOF
+load('@r2//:remote.bzl', 'remote_rule')
+
+remote_rule(
+    name = 'bar',
+    visibility = ["//visibility:public"]
+)
+EOF
+
+  # r2
+  touch $repo2/WORKSPACE $repo2/BUILD
+  cat > $repo2/remote.bzl <<EOF
+def _impl(ctx):
+  print(Label("//foo:bar"))
+
+remote_rule = rule(
+    implementation = _impl,
+)
+EOF
+
+  bazel build //:foo &> $TEST_log || fail "Building local failed"
+  expect_log "@r2//foo:bar"
+
+  cat > $repo2/remote.bzl <<EOF
+def _impl(ctx):
+  print(Label("//foo:bar", relative_to_caller_repository = True))
+
+remote_rule = rule(
+    implementation = _impl,
+)
+EOF
+  bazel build //:foo &> $TEST_log || fail "Building local failed"
+  expect_log "@r1//foo:bar"
+}
+
 run_suite "Test Skylark loads from/in external repositories"
-
-
diff --git a/src/test/shell/bazel/skylark_repository_test.sh b/src/test/shell/bazel/skylark_repository_test.sh
index 16f52ab..a79feb7 100755
--- a/src/test/shell/bazel/skylark_repository_test.sh
+++ b/src/test/shell/bazel/skylark_repository_test.sh
@@ -305,9 +305,8 @@
 function test_skylark_repository_which_and_execute() {
   setup_skylark_repository
 
-  bazel info
-
   # Test we are using the client environment, not the server one
+  bazel info &> /dev/null  # Start up the server.
   echo "#!/bin/bash" > bin.sh
   echo "exit 0" >> bin.sh
   chmod +x bin.sh