blob: 5cc4665cf9f1970c3c831b1ee82ec4ec5d37cc48 [file] [log] [blame]
// 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.LabelConstants;
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;
/**
* Factory class for creating appropriate instances of {@link SkylarkImports}.
*
* <p>TODO(laurentlb): Merge class with SkylarkImport.
*/
public class SkylarkImports {
private SkylarkImports() {
throw new IllegalStateException("This class should not be instantiated");
}
@VisibleForSerialization
@AutoCodec
static final class AbsoluteLabelImport extends SkylarkImport {
private final Label importLabel;
@VisibleForSerialization
AbsoluteLabelImport(String importString, Label importLabel) {
super(importString);
this.importLabel = importLabel;
}
@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 SkylarkImport {
private final String importTarget;
@VisibleForSerialization
RelativeLabelImport(String importString, String importTarget) {
super(importString);
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 {
// 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 =
"Starlark 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.
*
* <p>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.
*
* <p>There four syntactic import variants: Absolute paths, relative paths, absolute labels, and
* relative labels
*
* <p>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<RepositoryName, RepositoryName> 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(LabelConstants.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);
}
}