| // 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; |
| import java.util.Objects; |
| |
| /** |
| * Factory class for creating appropriate instances of {@link SkylarkImports}. |
| */ |
| public class SkylarkImports { |
| |
| private SkylarkImports() { |
| throw new IllegalStateException("This class should not be instantiated"); |
| } |
| |
| // Default implementation class for SkylarkImport. |
| private abstract static class SkylarkImportImpl implements SkylarkImport { |
| private final String importString; |
| |
| protected SkylarkImportImpl(String importString) { |
| this.importString = importString; |
| } |
| |
| @Override |
| public String getImportString() { |
| return importString; |
| } |
| |
| @Override |
| public abstract PathFragment asPathFragment(); |
| |
| @Override |
| public abstract Label getLabel(Label containingFileLabel); |
| |
| @Override |
| public boolean hasAbsolutePath() { |
| return false; |
| } |
| |
| @Override |
| 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) { |
| super(importString); |
| this.importPath = importPath; |
| } |
| |
| @Override |
| public PathFragment asPathFragment() { |
| return 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 extends SkylarkImportImpl { |
| private final String importFile; |
| |
| private RelativePathImport(String importString, String importFile) { |
| super(importString); |
| this.importFile = importFile; |
| } |
| |
| @Override |
| public PathFragment asPathFragment() { |
| return PathFragment.create(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 = |
| PathFragment.create(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); |
| } |
| } |
| |
| } |
| |
| private static final class AbsoluteLabelImport extends SkylarkImportImpl { |
| private final Label importLabel; |
| |
| private AbsoluteLabelImport(String importString, Label importLabel) { |
| super(importString); |
| this.importLabel = importLabel; |
| } |
| |
| @Override |
| public PathFragment asPathFragment() { |
| return PathFragment.create(PathFragment.ROOT_DIR).getRelative(importLabel.toPathFragment()); |
| } |
| |
| @Override |
| public Label getLabel(Label containingFileLabel) { |
| // When the import label contains no explicit repository identifier, we resolve it relative |
| // 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) { |
| super(importString); |
| this.importTarget = importTarget; |
| } |
| |
| @Override |
| public PathFragment asPathFragment() { |
| return PathFragment.create(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); |
| } |
| } |
| |
| } |
| |
| /** |
| * 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 = |
| "The label 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 INVALID_PATH_SYNTAX = |
| "Don't use paths for Load statements; " |
| + "use a label instead, e.g. '//foo:bar.bzl' or ':bar.bzl'"; |
| |
| @VisibleForTesting |
| static final String INVALID_TARGET_PREFIX = "Invalid target: "; |
| |
| @VisibleForTesting |
| static final String INVALID_FILENAME_PREFIX = "Invalid filename: "; |
| |
| /** |
| * 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, false); |
| } 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(importString, importLabel); |
| } else if (importString.startsWith("/")) { |
| // Absolute path. |
| if (importString.endsWith(".bzl")) { |
| throw new SkylarkImportSyntaxException(INVALID_PATH_SYNTAX); |
| } |
| PathFragment importPath = PathFragment.create(importString + ".bzl"); |
| return new AbsolutePathImport(importString, 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(importString, importTarget); |
| } else { |
| // Relative path. |
| if (importString.endsWith(".bzl") || importString.contains("/")) { |
| throw new SkylarkImportSyntaxException(INVALID_PATH_SYNTAX); |
| } |
| 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(importString, importTarget); |
| } |
| } |
| } |