// 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.common.collect.ImmutableMap; 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.cmdline.RepositoryName; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; 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. @VisibleForSerialization 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); } } @VisibleForSerialization @AutoCodec static final class AbsolutePathImport extends SkylarkImportImpl { private final PathFragment importPath; @VisibleForSerialization 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; } } @VisibleForSerialization @AutoCodec static final class RelativePathImport extends SkylarkImportImpl { private final String importFile; @VisibleForSerialization 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 { // This is for imports relative to the current repository, so repositoryMapping can be // empty return containingFileLabel.getRelativeWithRemapping(targetNameForImport, ImmutableMap.of()); } 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); } } } @VisibleForSerialization @AutoCodec static final class AbsoluteLabelImport extends SkylarkImportImpl { private final Label importLabel; @VisibleForSerialization AbsoluteLabelImport(String importString, Label importLabel) { super(importString); this.importLabel = importLabel; } @Override public PathFragment asPathFragment() { return PathFragment.create("/").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); } } @VisibleForSerialization @AutoCodec static final class RelativeLabelImport extends SkylarkImportImpl { private final String importTarget; @VisibleForSerialization 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 { // This is for imports relative to the current repository, so repositoryMapping can be // empty return containingFileLabel.getRelativeWithRemapping(importTarget, ImmutableMap.of()); } 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 = "First argument of 'load' must be a label and start with either '//', ':', or '@'."; @VisibleForTesting static final String INVALID_TARGET_PREFIX = "Invalid target: "; /** * Creates and syntactically validates a {@link SkylarkImports} instance from a string. * *

There are 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 { return create(importString, /* repositoryMapping= */ ImmutableMap.of()); } /** * 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 * *

Absolute labels will have the repository portion of the label remapped if it is present in * {@code repositoryMapping} * * @throws SkylarkImportSyntaxException if the string is not a valid Skylark import. */ public static SkylarkImport create( String importString, ImmutableMap repositoryMapping) throws SkylarkImportSyntaxException { if (importString.startsWith("//") || importString.startsWith("@")) { // Absolute label. Label importLabel; try { importLabel = Label.parseAbsolute(importString, false, repositoryMapping); } 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(":")) { // Relative label. We require that relative labels use an explicit ':' prefix. 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); } throw new SkylarkImportSyntaxException(INVALID_PATH_SYNTAX); } }