// 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); } } }