aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax
diff options
context:
space:
mode:
authorGravatar John Field <jfield@google.com>2015-12-16 18:17:25 +0000
committerGravatar Nathan Harmata <nharmata@google.com>2015-12-16 20:24:34 +0000
commit3683ad67c984080730abdc390f6ceb5eeba3e8f2 (patch)
tree71b98a9680ccc1f9db56392f557d65c5ee0a251e /src/main/java/com/google/devtools/build/lib/syntax
parentf5c3a974a7c9568cb071c56800899de6d8650c5d (diff)
Add a new class to encapsulate operations on Skylark import statements. This will make it easier to manage the transition from legacy Skylark path-style loads to label-based loads. We handle both old-style (path-based) imports and new-style (label-based) imports.
This class is in the cmdline package since it depends on Label-related code in the same package. The cmdline package depends on the syntax package, so this class can't go there, which would otherwise be a reasonable place to put it. -- MOS_MIGRATED_REVID=110372547
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkImport.java45
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java242
2 files changed, 287 insertions, 0 deletions
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
new file mode 100644
index 0000000000..2410c23a34
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImport.java
@@ -0,0 +1,45 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.syntax;
+
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * Encapsulates the four syntactic variants of Skylark imports: Absolute paths, relative
+ * paths, absolute labels, and relative labels.
+ */
+public interface SkylarkImport {
+
+ /**
+ * Given a {@link Label} representing the file that contains this import, returns a {@link Label}
+ * representing the .bzl file to be imported.
+ *
+ * @throws IllegalStateException if this import takes the form of an absolute path.
+ */
+ Label getLabel(Label containingFileLabel);
+
+ /**
+ * True if this import takes the form of an absolute path.
+ */
+ boolean hasAbsolutePath();
+
+ /**
+ * Returns a {@link PathFragment} representing the import path.
+ *
+ * @throws IllegalStateException if this import does not take the form of an absolute path.
+ */
+ PathFragment getAbsolutePath();
+}
+
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
new file mode 100644
index 0000000000..6c11ac2cbd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
@@ -0,0 +1,242 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.syntax;
+
+import com.google.common.annotations.VisibleForTesting;
+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.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * Factory class for creating appropriate instances of {@link SkylarkImports}.
+ */
+public class SkylarkImports {
+
+ private SkylarkImports() {
+ throw new IllegalStateException("This class should not be instantiated");
+ }
+
+ private static final class AbsolutePathImport implements SkylarkImport {
+ private PathFragment importPath;
+
+ private AbsolutePathImport(PathFragment importPath) {
+ this.importPath = importPath;
+ }
+
+ @Override
+ public Label getLabel(Label containingFileLabel) {
+ throw new IllegalStateException("can't request a label from an absolute path import");
+ }
+
+ @Override
+ public boolean hasAbsolutePath() {
+ return true;
+ }
+
+ @Override
+ public PathFragment getAbsolutePath() {
+ return this.importPath;
+ }
+ }
+
+ private static final class RelativePathImport implements SkylarkImport {
+ private String importFile;
+
+ private RelativePathImport(String importFile) {
+ this.importFile = importFile;
+ }
+
+ @Override
+ public Label getLabel(Label containingFileLabel) {
+ // The twistiness of the code below is due to the fact that the containing file may be in
+ // a subdirectory of the package that contains it. We need to construct a Label with
+ // the imported file in the same subdirectory of the package.
+ PathFragment containingDirInPkg =
+ (new PathFragment(containingFileLabel.getName())).getParentDirectory();
+ String targetNameForImport = containingDirInPkg.getRelative(importFile).toString();
+ try {
+ return containingFileLabel.getRelative(targetNameForImport);
+ } catch (LabelSyntaxException e) {
+ // Shouldn't happen because the parent label is assumed to be valid and the target string is
+ // validated on construction.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public boolean hasAbsolutePath() {
+ return false;
+ }
+
+ @Override
+ public PathFragment getAbsolutePath() {
+ throw new IllegalStateException("can't request absolute path from a non-absolute import");
+ }
+ }
+
+ private static final class AbsoluteLabelImport implements SkylarkImport {
+ private Label importLabel;
+
+ private AbsoluteLabelImport(Label importLabel) {
+ this.importLabel = importLabel;
+ }
+
+ @Override
+ public Label getLabel(Label containingFileLabel) {
+ // The containing file label is irrelevant here since this is an absolute path.
+ return importLabel;
+ }
+
+ @Override
+ public boolean hasAbsolutePath() {
+ return false;
+ }
+
+ @Override
+ public PathFragment getAbsolutePath() {
+ throw new IllegalStateException("can't request absolute path from a non-absolute import");
+ }
+ }
+
+ private static final class RelativeLabelImport implements SkylarkImport {
+ private String importTarget;
+
+ private RelativeLabelImport(String importTarget) {
+ this.importTarget = importTarget;
+ }
+
+ @Override
+ public Label getLabel(Label containingFileLabel) {
+ // Unlike a relative path import, the import target is relative to the containing package,
+ // not the containing directory within the package.
+ try {
+ return containingFileLabel.getRelative(importTarget);
+ } catch (LabelSyntaxException e) {
+ // shouldn't happen because the parent label is assumed validated and the target string is
+ // validated on construction
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public boolean hasAbsolutePath() {
+ return false;
+ }
+
+ @Override
+ public PathFragment getAbsolutePath() {
+ throw new IllegalStateException("can't request absolute path from a non-absolute import");
+ }
+ }
+
+ /**
+ * Exception raised for syntactically-invalid Skylark load strings.
+ */
+ public static class SkylarkImportSyntaxException extends Exception {
+ public SkylarkImportSyntaxException(String message) {
+ super(message);
+ }
+ }
+
+ @VisibleForTesting
+ static final String INVALID_LABEL_PREFIX = "invalid label: ";
+
+ @VisibleForTesting
+ static final String MUST_HAVE_BZL_EXT_MSG = "must reference a file with extension '.bzl'";
+
+ @VisibleForTesting
+ static final String EXTERNAL_PKG_NOT_ALLOWED_MSG =
+ "Skylark files may not be loaded from the //external package";
+
+ @VisibleForTesting
+ static final String BZL_EXT_IMPLICIT_MSG =
+ "the '.bzl' file extension is implicit; remove it from the path";
+
+ @VisibleForTesting
+ static final String INVALID_TARGET_PREFIX = "invalid target: ";
+
+ @VisibleForTesting
+ static final String INVALID_FILENAME_PREFIX = "invalid filename: ";
+
+ @VisibleForTesting
+ static final String RELATIVE_PATH_NO_SUBDIRS_MSG =
+ "relative import path may not contain subdirectories";
+
+ /**
+ * Creates and syntactically validates a {@link SkylarkImports} instance from a string.
+ * <p>
+ * There four syntactic import variants: Absolute paths, relative paths, absolute labels, and
+ * relative labels
+ *
+ * @throws SkylarkImportSyntaxException if the string is not a valid Skylark import.
+ */
+ public static SkylarkImport create(String importString) throws SkylarkImportSyntaxException {
+ if (importString.startsWith("//") || importString.startsWith("@")) {
+ // Absolute label.
+ Label importLabel;
+ try {
+ importLabel = Label.parseAbsolute(importString);
+ } catch (LabelSyntaxException e) {
+ throw new SkylarkImportSyntaxException(INVALID_LABEL_PREFIX + e.getMessage());
+ }
+ String targetName = importLabel.getName();
+ if (!targetName.endsWith(".bzl")) {
+ throw new SkylarkImportSyntaxException(MUST_HAVE_BZL_EXT_MSG);
+ }
+ PackageIdentifier packageId = importLabel.getPackageIdentifier();
+ if (packageId.equals(Label.EXTERNAL_PACKAGE_IDENTIFIER)) {
+ throw new SkylarkImportSyntaxException(EXTERNAL_PKG_NOT_ALLOWED_MSG);
+ }
+ return new AbsoluteLabelImport(importLabel);
+ } else if (importString.startsWith("/")) {
+ // Absolute path.
+ if (importString.endsWith(".bzl")) {
+ throw new SkylarkImportSyntaxException(BZL_EXT_IMPLICIT_MSG);
+ }
+ PathFragment importPath = new PathFragment(importString);
+ return new AbsolutePathImport(importPath);
+ } else if (importString.startsWith(":")) {
+ // Relative label. We require that relative labels use an explicit ':' prefix to distinguish
+ // them from relative paths, which have a different semantics.
+ String importTarget = importString.substring(1);
+ if (!importTarget.endsWith(".bzl")) {
+ throw new SkylarkImportSyntaxException(MUST_HAVE_BZL_EXT_MSG);
+ }
+ String maybeErrMsg = LabelValidator.validateTargetName(importTarget);
+ if (maybeErrMsg != null) {
+ // Null indicates successful target validation.
+ throw new SkylarkImportSyntaxException(INVALID_TARGET_PREFIX + maybeErrMsg);
+ }
+ return new RelativeLabelImport(importTarget);
+ } else {
+ // Relative path.
+ if (importString.endsWith(".bzl")) {
+ throw new SkylarkImportSyntaxException(BZL_EXT_IMPLICIT_MSG);
+ }
+ if (importString.contains("/")) {
+ throw new SkylarkImportSyntaxException(RELATIVE_PATH_NO_SUBDIRS_MSG);
+ }
+ String importTarget = importString + ".bzl";
+ String maybeErrMsg = LabelValidator.validateTargetName(importTarget);
+ if (maybeErrMsg != null) {
+ // Null indicates successful target validation.
+ throw new SkylarkImportSyntaxException(INVALID_FILENAME_PREFIX + maybeErrMsg);
+ }
+ return new RelativePathImport(importTarget);
+ }
+ }
+}
+