Allow labels in the '--aspects' parameter.

--
MOS_MIGRATED_REVID=139573590
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index b29d2a2..5cd90d2 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -75,6 +75,9 @@
 import com.google.devtools.build.lib.skyframe.SkyframeBuildView;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.SkylarkImport;
+import com.google.devtools.build.lib.syntax.SkylarkImports;
+import com.google.devtools.build.lib.syntax.SkylarkImports.SkylarkImportSyntaxException;
 import com.google.devtools.build.lib.util.OrderedSetMultimap;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.util.RegexFilter;
@@ -465,7 +468,22 @@
         // TODO(jfield): For consistency with Skylark loads, the aspect should be specified
         // as an absolute path. Also, we probably need to do at least basic validation of
         // path well-formedness here.
-        PathFragment bzlFile = new PathFragment("/" + aspect.substring(0, delimiterPosition));
+        String bzlFileLoadLikeString = aspect.substring(0, delimiterPosition);
+        if (!bzlFileLoadLikeString.startsWith("//") && !bzlFileLoadLikeString.startsWith("@")) {
+          // "Legacy" behavior of '--aspects' parameter.
+          bzlFileLoadLikeString = new PathFragment("/" + bzlFileLoadLikeString).toString();
+          if (bzlFileLoadLikeString.endsWith(".bzl")) {
+            bzlFileLoadLikeString = bzlFileLoadLikeString.substring(0,
+                bzlFileLoadLikeString.length() - ".bzl".length());
+          }
+        }
+        SkylarkImport skylarkImport;
+        try {
+          skylarkImport = SkylarkImports.create(bzlFileLoadLikeString);
+        } catch (SkylarkImportSyntaxException e) {
+          throw new ViewCreationFailedException(
+              String.format("Invalid aspect '%s': %s", aspect, e.getMessage()), e);
+        }
 
         String skylarkFunctionName = aspect.substring(delimiterPosition + 1);
         for (TargetAndConfiguration targetSpec : topLevelTargetsWithConfigs) {
@@ -479,7 +497,7 @@
                   // aspect and the base target while the top-level configuration is untrimmed.
                   targetSpec.getConfiguration(),
                   targetSpec.getConfiguration(),
-                  bzlFile,
+                  skylarkImport,
                   skylarkFunctionName));
         }
       } else {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java
index 1d3b0fd..d4366fa 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java
@@ -26,9 +26,8 @@
 import com.google.devtools.build.lib.packages.AspectClass;
 import com.google.devtools.build.lib.packages.AspectParameters;
 import com.google.devtools.build.lib.packages.Package;
-import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.syntax.SkylarkImport;
 import com.google.devtools.build.skyframe.SkyFunctionName;
-
 import javax.annotation.Nullable;
 
 /**
@@ -223,20 +222,20 @@
     private final Label targetLabel;
     private final BuildConfiguration aspectConfiguration;
     private final BuildConfiguration targetConfiguration;
-    private final PathFragment extensionFile;
+    private final SkylarkImport skylarkImport;
     private final String skylarkValueName;
 
     private SkylarkAspectLoadingKey(
         Label targetLabel,
         BuildConfiguration aspectConfiguration,
         BuildConfiguration targetConfiguration,
-        PathFragment extensionFile,
+        SkylarkImport skylarkImport,
         String skylarkFunctionName) {
       this.targetLabel = targetLabel;
       this.aspectConfiguration = aspectConfiguration;
       this.targetConfiguration = targetConfiguration;
 
-      this.extensionFile = extensionFile;
+      this.skylarkImport = skylarkImport;
       this.skylarkValueName = skylarkFunctionName;
     }
 
@@ -245,16 +244,16 @@
       return SkyFunctions.LOAD_SKYLARK_ASPECT;
     }
 
-    public PathFragment getExtensionFile() {
-      return extensionFile;
+    public Label getTargetLabel() {
+      return targetLabel;
     }
 
     public String getSkylarkValueName() {
       return skylarkValueName;
     }
 
-    public Label getTargetLabel() {
-      return targetLabel;
+    public SkylarkImport getSkylarkImport() {
+      return skylarkImport;
     }
 
     /**
@@ -274,7 +273,35 @@
     @Override
     public String getDescription() {
       // Skylark aspects are referred to on command line with <file>%<value ame>
-      return String.format("%s%%%s of %s", extensionFile.toString(), skylarkValueName, targetLabel);
+      return String.format("%s%%%s of %s", skylarkImport.getImportString(),
+          skylarkValueName, targetLabel);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(targetLabel,
+          aspectConfiguration,
+          targetConfiguration,
+          skylarkImport,
+          skylarkValueName);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o == this) {
+        return true;
+      }
+
+      if (!(o instanceof SkylarkAspectLoadingKey)) {
+        return false;
+      }
+      SkylarkAspectLoadingKey that = (SkylarkAspectLoadingKey) o;
+      return Objects.equal(targetLabel, that.targetLabel)
+          && Objects.equal(aspectConfiguration, that.aspectConfiguration)
+          && Objects.equal(targetConfiguration, that.targetConfiguration)
+          && Objects.equal(skylarkImport, that.skylarkImport)
+          && Objects.equal(skylarkValueName, that.skylarkValueName);
+
     }
   }
 
@@ -350,9 +377,9 @@
       Label targetLabel,
       BuildConfiguration aspectConfiguration,
       BuildConfiguration targetConfiguration,
-      PathFragment skylarkFile,
+      SkylarkImport skylarkImport,
       String skylarkExportName) {
     return new SkylarkAspectLoadingKey(
-        targetLabel, aspectConfiguration, targetConfiguration, skylarkFile, skylarkExportName);
+        targetLabel, aspectConfiguration, targetConfiguration, skylarkImport, skylarkExportName);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelSkylarkAspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelSkylarkAspectFunction.java
index 5d2c163..4d6dbf2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelSkylarkAspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelSkylarkAspectFunction.java
@@ -14,8 +14,9 @@
 
 package com.google.devtools.build.lib.skyframe;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.AspectDescriptor;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Event;
@@ -24,12 +25,11 @@
 import com.google.devtools.build.lib.skyframe.AspectFunction.AspectCreationException;
 import com.google.devtools.build.lib.skyframe.AspectValue.SkylarkAspectLoadingKey;
 import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException;
-import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.syntax.SkylarkImport;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
-
 import javax.annotation.Nullable;
 
 /**
@@ -47,13 +47,17 @@
       throws LoadSkylarkAspectFunctionException, InterruptedException {
     SkylarkAspectLoadingKey aspectLoadingKey = (SkylarkAspectLoadingKey) skyKey.argument();
     String skylarkValueName = aspectLoadingKey.getSkylarkValueName();
-    PathFragment extensionFile = aspectLoadingKey.getExtensionFile();
+    SkylarkImport extensionFile = aspectLoadingKey.getSkylarkImport();
     
     // Find label corresponding to skylark file, if one exists.
-    ImmutableMap<PathFragment, Label> labelLookupMap;
+    ImmutableMap<String, Label> labelLookupMap;
     try {
       labelLookupMap =
-          SkylarkImportLookupFunction.labelsForAbsoluteImports(ImmutableSet.of(extensionFile), env);
+          SkylarkImportLookupFunction.findLabelsForLoadStatements(
+              ImmutableList.of(extensionFile),
+              Label.parseAbsoluteUnchecked("//:empty"),
+              env
+          );
     } catch (SkylarkImportFailedException e) {
       env.getListener().handle(Event.error(e.getMessage()));
       throw new LoadSkylarkAspectFunctionException(
@@ -63,16 +67,17 @@
       return null;
     }
 
-    SkylarkAspect skylarkAspect = null;
+    SkylarkAspect skylarkAspect;
+    Label extensionFileLabel = Iterables.getOnlyElement(labelLookupMap.values());
     try {
       skylarkAspect = AspectFunction.loadSkylarkAspect(
-          env, labelLookupMap.get(extensionFile), skylarkValueName);
+          env, extensionFileLabel, skylarkValueName);
       if (skylarkAspect == null) {
         return null;
       }
       if (!skylarkAspect.getParamAttributes().isEmpty()) {
         throw new AspectCreationException("Cannot instantiate parameterized aspect "
-            + skylarkAspect.getName() + " at the top level.", labelLookupMap.get(extensionFile));
+            + skylarkAspect.getName() + " at the top level.", extensionFileLabel);
       }
     } catch (AspectCreationException e) {
       throw new LoadSkylarkAspectFunctionException(e);
@@ -101,4 +106,4 @@
       super(cause, Transience.PERSISTENT);
     }
   }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImport.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImport.java
index 95ddf96..e581436 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImport.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImport.java
@@ -17,6 +17,7 @@
 import com.google.devtools.build.lib.vfs.PathFragment;
 
 import java.io.Serializable;
+import javax.annotation.Nullable;
 
 /**
  * Encapsulates the four syntactic variants of Skylark imports: Absolute paths, relative
@@ -45,7 +46,7 @@
    *
    * @throws IllegalStateException if this import takes the form of an absolute path.
    */
-  Label getLabel(Label containingFileLabel);
+  Label getLabel(@Nullable Label containingFileLabel);
 
   /**
    * True if this import takes the form of an absolute path.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
index 2910beb..44d4a9b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
@@ -19,6 +19,7 @@
 import com.google.devtools.build.lib.cmdline.LabelValidator;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.Objects;
 
 /**
  * Factory class for creating appropriate instances of {@link SkylarkImports}.
@@ -31,7 +32,11 @@
 
   // Default implementation class for SkylarkImport.
   private abstract static class SkylarkImportImpl implements SkylarkImport {
-    protected String importString;
+    private final String importString;
+
+    protected SkylarkImportImpl(String importString) {
+      this.importString = importString;
+    }
 
     @Override
     public String getImportString() {
@@ -53,13 +58,32 @@
     public PathFragment getAbsolutePath() {
       throw new IllegalStateException("can't request absolute path from a non-absolute import");
     }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(getClass(), importString);
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      if (this == that) {
+        return true;
+      }
+
+      if (!(that instanceof SkylarkImportImpl)) {
+        return false;
+      }
+
+      return Objects.equals(getClass(), that.getClass())
+          && Objects.equals(importString, ((SkylarkImportImpl) that).importString);
+    }
   }
 
   private static final class AbsolutePathImport extends SkylarkImportImpl {
     private final PathFragment importPath;
 
     private AbsolutePathImport(String importString, PathFragment importPath) {
-      this.importString = importString;
+      super(importString);
       this.importPath = importPath;
     }
 
@@ -82,13 +106,14 @@
     public PathFragment getAbsolutePath() {
       return this.importPath;
     }
+
   }
 
   private static final class RelativePathImport extends SkylarkImportImpl {
     private final String importFile;
 
     private RelativePathImport(String importString, String importFile) {
-      this.importString = importString;
+      super(importString);
       this.importFile = importFile;
     }
 
@@ -113,13 +138,14 @@
         throw new IllegalStateException(e);
       }
     }
+
   }
 
   private static final class AbsoluteLabelImport extends SkylarkImportImpl {
     private final Label importLabel;
 
     private AbsoluteLabelImport(String importString, Label importLabel) {
-      this.importString = importString;
+      super(importString);
       this.importLabel = importLabel;
     }
 
@@ -134,13 +160,14 @@
       // to the repo of the containing file.
       return containingFileLabel.resolveRepositoryRelative(importLabel);
     }
+
   }
 
   private static final class RelativeLabelImport extends SkylarkImportImpl {
     private final String importTarget;
 
     private RelativeLabelImport(String importString, String importTarget) {
-      this.importString = importString;
+      super(importString);
       this.importTarget = importTarget;
     }
 
@@ -161,6 +188,7 @@
         throw new IllegalStateException(e);
       }
     }
+
   }
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
index d053f05..53da165 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
@@ -69,33 +69,80 @@
 
     AnalysisResult analysisResult =
         update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
-    assertThat(
-            transform(
-                analysisResult.getTargetsToBuild(),
-                new Function<ConfiguredTarget, String>() {
-                  @Nullable
-                  @Override
-                  public String apply(ConfiguredTarget configuredTarget) {
-                    return configuredTarget.getLabel().toString();
-                  }
-                }))
-        .containsExactly("//test:xxx");
-    assertThat(
-            transform(
-                analysisResult.getAspects(),
-                new Function<AspectValue, String>() {
-                  @Nullable
-                  @Override
-                  public String apply(AspectValue aspectValue) {
-                    return String.format(
-                        "%s(%s)",
-                        aspectValue.getConfiguredAspect().getName(),
-                        aspectValue.getLabel().toString());
-                  }
-                }))
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+    assertThat(getAspectDescriptions(analysisResult))
         .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
   }
 
+  private Iterable<String> getAspectDescriptions(AnalysisResult analysisResult) {
+    return transform(
+        analysisResult.getAspects(),
+        new Function<AspectValue, String>() {
+          @Nullable
+          @Override
+          public String apply(AspectValue aspectValue) {
+            return String.format(
+                "%s(%s)",
+                aspectValue.getConfiguredAspect().getName(),
+                aspectValue.getLabel().toString());
+          }
+        });
+  }
+
+  @Test
+  public void testAspectCommandLineLabel() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        "def _impl(target, ctx):",
+        "   print('This aspect does nothing')",
+        "   return struct()",
+        "MyAspect = aspect(implementation=_impl)");
+    scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("//test:aspect.bzl%MyAspect"), "//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+    assertThat(getAspectDescriptions(analysisResult))
+        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+  }
+
+  @Test
+  public void testAspectCommandLineRepoLabel() throws Exception {
+    scratch.overwriteFile(
+        "WORKSPACE",
+        scratch.readFile("WORKSPACE"),
+        "local_repository(name='local', path='local/repo')"
+    );
+    scratch.file(
+        "local/repo/aspect.bzl",
+        "def _impl(target, ctx):",
+        "   print('This aspect does nothing')",
+        "   return struct()",
+        "MyAspect = aspect(implementation=_impl)");
+    scratch.file("local/repo/BUILD");
+
+    scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("@local//:aspect.bzl%MyAspect"), "//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+    assertThat(getAspectDescriptions(analysisResult))
+        .containsExactly("@local//:aspect.bzl%MyAspect(//test:xxx)");
+  }
+
+  private Iterable<String> getLabelsToBuild(AnalysisResult analysisResult) {
+    return transform(
+        analysisResult.getTargetsToBuild(),
+        new Function<ConfiguredTarget, String>() {
+          @Nullable
+          @Override
+          public String apply(ConfiguredTarget configuredTarget) {
+            return configuredTarget.getLabel().toString();
+          }
+        });
+  }
+
+
   @Test
   public void testAspectAllowsFragmentsToBeSpecified() throws Exception {
     scratch.file(
@@ -157,17 +204,7 @@
 
     AnalysisResult analysisResult =
         update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
-    assertThat(
-            transform(
-                analysisResult.getTargetsToBuild(),
-                new Function<ConfiguredTarget, String>() {
-                  @Nullable
-                  @Override
-                  public String apply(ConfiguredTarget configuredTarget) {
-                    return configuredTarget.getLabel().toString();
-                  }
-                }))
-        .containsExactly("//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
     AspectValue aspectValue = analysisResult.getAspects().iterator().next();
     SkylarkProviders skylarkProviders =
         aspectValue.getConfiguredAspect().getProvider(SkylarkProviders.class);
@@ -270,17 +307,7 @@
 
     AnalysisResult analysisResult =
         update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
-    assertThat(
-        transform(
-            analysisResult.getTargetsToBuild(),
-            new Function<ConfiguredTarget, String>() {
-              @Nullable
-              @Override
-              public String apply(ConfiguredTarget configuredTarget) {
-                return configuredTarget.getLabel().toString();
-              }
-            }))
-        .containsExactly("//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
     AspectValue aspectValue = analysisResult.getAspects().iterator().next();
     OutputGroupProvider outputGroupProvider =
         aspectValue.getConfiguredAspect().getProvider(OutputGroupProvider.class);
@@ -331,17 +358,7 @@
         ")");
 
     AnalysisResult analysisResult = update("//test:xxx");
-    assertThat(
-        transform(
-            analysisResult.getTargetsToBuild(),
-            new Function<ConfiguredTarget, String>() {
-              @Nullable
-              @Override
-              public String apply(ConfiguredTarget configuredTarget) {
-                return configuredTarget.getLabel().toString();
-              }
-            }))
-        .containsExactly("//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
     ConfiguredTarget target = analysisResult.getTargetsToBuild().iterator().next();
     SkylarkProviders skylarkProviders = target.getProvider(SkylarkProviders.class);
     assertThat(skylarkProviders).isNotNull();
@@ -428,7 +445,7 @@
       assertThat(result.hasError()).isTrue();
     } catch (ViewCreationFailedException expected) {
       assertThat(expected.getMessage())
-          .contains("Analysis of aspect '/test/aspect.bzl%MyAspect of //test:xxx' failed");
+          .contains("Analysis of aspect '/test/aspect%MyAspect of //test:xxx' failed");
     }
     assertContainsEvent("//test:aspect.bzl%MyAspect is attached to source file zzz.jar but "
         + "aspects must be attached to rules");
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/Scratch.java b/src/test/java/com/google/devtools/build/lib/testutil/Scratch.java
index 7179b37..6004661 100644
--- a/src/test/java/com/google/devtools/build/lib/testutil/Scratch.java
+++ b/src/test/java/com/google/devtools/build/lib/testutil/Scratch.java
@@ -14,13 +14,13 @@
 
 package com.google.devtools.build.lib.testutil;
 
+import com.google.common.io.ByteStreams;
 import com.google.devtools.build.lib.util.BlazeClock;
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -131,6 +131,12 @@
     return file;
   }
 
+  public String readFile(String pathName) throws IOException {
+    return new String(
+        ByteStreams.toByteArray(resolve(pathName).getInputStream()),
+        DEFAULT_CHARSET);
+  }
+
   /**
    * Like {@code scratch.file}, but the file is first deleted if it already
    * exists.