diff options
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.java | 45 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java | 242 |
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); + } + } +} + |