// 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"); } // Default implementation class for SkylarkImport. private abstract static class SkylarkImportImpl implements SkylarkImport { protected String 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"); } } private static final class AbsolutePathImport extends SkylarkImportImpl { private PathFragment importPath; private AbsolutePathImport(String importString, PathFragment importPath) { this.importString = 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 String importFile; private RelativePathImport(String importString, String importFile) { this.importString = importString; this.importFile = importFile; } @Override public PathFragment asPathFragment() { return new PathFragment(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); } } } private static final class AbsoluteLabelImport extends SkylarkImportImpl { private Label importLabel; private AbsoluteLabelImport(String importString, Label importLabel) { this.importString = importString; this.importLabel = importLabel; } @Override public PathFragment asPathFragment() { return new PathFragment(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 String importTarget; private RelativeLabelImport(String importString, String importTarget) { this.importString = importString; this.importTarget = importTarget; } @Override public PathFragment asPathFragment() { return new PathFragment(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 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 = "A relative import path may not contain subdirectories"; /** * Creates and syntactically validates a {@link SkylarkImports} instance from a string. *

* 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(BZL_EXT_IMPLICIT_MSG); } PathFragment importPath = new PathFragment(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")) { 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(importString, importTarget); } } }