// Copyright 2014 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.packages; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.collect.CollectionUtils; import com.google.devtools.build.lib.collect.ImmutableSortedKeyMap; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.License.DistributionType; import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext; import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; import com.google.devtools.build.lib.skyframe.serialization.SerializationContext; import com.google.devtools.build.lib.skyframe.serialization.SerializationException; import com.google.devtools.build.lib.syntax.SkylarkSemantics; import com.google.devtools.build.lib.util.SpellChecker; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import com.google.protobuf.CodedInputStream; import com.google.protobuf.CodedOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import javax.annotation.Nullable; /** * A package, which is a container of {@link Rule}s, each of which contains a dictionary of named * attributes. * *

Package instances are intended to be immutable and for all practical purposes can be treated * as such. Note, however, that some member variables exposed via the public interface are not * strictly immutable, so until their types are guaranteed immutable we're not applying the * {@code @Immutable} annotation here. * *

When changing this class, make sure to make corresponding changes to serialization! */ @SuppressWarnings("JavaLangClash") public class Package { /** * Common superclass for all name-conflict exceptions. */ public static class NameConflictException extends Exception { protected NameConflictException(String message) { super(message); } } /** * The repository identifier for this package. */ private final PackageIdentifier packageIdentifier; /** * The name of the package, e.g. "foo/bar". */ protected final String name; /** * Like name, but in the form of a PathFragment. */ private final PathFragment nameFragment; /** * The filename of this package's BUILD file. */ protected Path filename; /** * The directory in which this package's BUILD file resides. All InputFile * members of the packages are located relative to this directory. */ private Path packageDirectory; /** * The name of the workspace this package is in. Used as a prefix for the runfiles directory. * This can be set in the WORKSPACE file. This must be a valid target name. */ protected String workspaceName; /** * The root of the source tree in which this package was found. It is an invariant that {@code * sourceRoot.getRelative(packageId.getSourceRoot()).equals(packageDirectory)}. */ private Root sourceRoot; /** * The "Make" environment of this package, containing package-local * definitions of "Make" variables. */ private ImmutableMap makeEnv; /** The collection of all targets defined in this package, indexed by name. */ protected ImmutableSortedKeyMap targets; /** * Default visibility for rules that do not specify it. */ private RuleVisibility defaultVisibility; private boolean defaultVisibilitySet; /** * Default package-level 'testonly' value for rules that do not specify it. */ private boolean defaultTestOnly = false; /** * Default package-level 'deprecation' value for rules that do not specify it. */ private String defaultDeprecation; /** * Default header strictness checking for rules that do not specify it. */ private String defaultHdrsCheck; /** Default copts for cc_* rules. The rules' individual copts will append to this value. */ private ImmutableList defaultCopts; /** * The InputFile target corresponding to this package's BUILD file. */ private InputFile buildFile; /** * True iff this package's BUILD files contained lexical or grammatical * errors, or experienced errors during evaluation, or semantic errors during * the construction of any rule. * *

Note: A package containing errors does not necessarily prevent a build; * if all the rules needed for a given build were constructed prior to the * first error, the build may proceed. */ private boolean containsErrors; /** * The list of transitive closure of the Skylark file dependencies. */ private ImmutableList

As part of initialization, {@link Builder} constructs {@link InputFile} * and {@link PackageGroup} instances that require a valid Package instance where * {@link Package#getNameFragment()} is accessible. That's why these settings are * applied here at the start. * * @precondition {@code name} must be a suffix of * {@code filename.getParentDirectory())}. */ protected Package(PackageIdentifier packageId, String runfilesPrefix) { this.packageIdentifier = packageId; this.workspaceName = runfilesPrefix; this.nameFragment = packageId.getPackageFragment(); this.name = nameFragment.getPathString(); } /** Returns this packages' identifier. */ public PackageIdentifier getPackageIdentifier() { return packageIdentifier; } /** * Returns the repository mapping for the requested external repository. * * @throws UnsupportedOperationException if called from a package other than * the //external package */ public ImmutableMap getRepositoryMapping( RepositoryName repository) { if (!isWorkspace()) { throw new UnsupportedOperationException("Can only access the external package repository" + "mappings from the //external package"); } return externalPackageRepositoryMappings.getOrDefault(repository, ImmutableMap.of()); } /** * Returns the repository mapping for the requested external repository. * * @throws LabelSyntaxException if repository is not a valid {@link RepositoryName} * @throws UnsupportedOperationException if called from any package other than the //external * package */ public ImmutableMap getRepositoryMapping(String repository) throws LabelSyntaxException, UnsupportedOperationException { RepositoryName repositoryName = RepositoryName.create(repository); return getRepositoryMapping(repositoryName); } /** Get the repository mapping for this package. */ public ImmutableMap getRepositoryMapping() { return repositoryMapping; } /** * Gets the global name for a repository within an external repository. * *

{@code localName} is a repository name reference found in a BUILD file within a repository * external to the main workspace. This method returns the main workspace's global remapped name * for {@code localName}. */ public RepositoryName getGlobalName(RepositoryName localName) { RepositoryName globalname = repositoryMapping.get(localName); return globalname != null ? globalname : localName; } /** Returns whether we are in the WORKSPACE file or not. */ public boolean isWorkspace() { return getPackageIdentifier().equals(Label.EXTERNAL_PACKAGE_IDENTIFIER); } /** * Package initialization: part 2 of 3: sets this package's default header * strictness checking. * *

This is needed to support C++-related rule classes * which accesses {@link #getDefaultHdrsCheck} from the still-under-construction * package. */ protected void setDefaultHdrsCheck(String defaultHdrsCheck) { this.defaultHdrsCheck = defaultHdrsCheck; } /** * Set the default 'testonly' value for this package. */ protected void setDefaultTestOnly(boolean testOnly) { defaultTestOnly = testOnly; } /** * Set the default 'deprecation' value for this package. */ protected void setDefaultDeprecation(String deprecation) { defaultDeprecation = deprecation; } /** * Sets the default value to use for a rule's {@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} * attribute when not explicitly specified by the rule. */ protected void setDefaultCompatibleWith(Set

Assumes invariant: {@code * getSourceRoot().getRelative(packageId.getSourceRoot()).equals(getPackageDirectory())} */ public Root getSourceRoot() { return sourceRoot; } // This must always be consistent with Root.computeSourceRoot; otherwise computing source roots // from exec paths does not work, which can break the action cache for input-discovering actions. private static Root getSourceRoot(Path buildFile, PathFragment packageFragment) { Path current = buildFile.getParentDirectory(); for (int i = 0, len = packageFragment.segmentCount(); i < len && !packageFragment.equals(PathFragment.EMPTY_FRAGMENT); i++) { if (current != null) { current = current.getParentDirectory(); } } return Root.fromPath(current); } /** * Package initialization: part 3 of 3: applies all other settings and completes * initialization of the package. * *

Only after this method is called can this package be considered "complete" * and be shared publicly. */ protected void finishInit(Builder builder) { // If any error occurred during evaluation of this package, consider all // rules in the package to be "in error" also (even if they were evaluated // prior to the error). This behaviour is arguably stricter than need be, // but stopping a build only for some errors but not others creates user // confusion. if (builder.containsErrors) { for (Rule rule : builder.getTargets(Rule.class)) { rule.setContainsErrors(); } } this.filename = builder.getFilename(); this.packageDirectory = filename.getParentDirectory(); this.sourceRoot = getSourceRoot(filename, packageIdentifier.getSourceRoot()); if ((sourceRoot.asPath() == null || !sourceRoot.getRelative(packageIdentifier.getSourceRoot()).equals(packageDirectory)) && !filename.getBaseName().equals("WORKSPACE")) { throw new IllegalArgumentException( "Invalid BUILD file name for package '" + packageIdentifier + "': " + filename); } this.makeEnv = ImmutableMap.copyOf(builder.makeEnv); this.targets = ImmutableSortedKeyMap.copyOf(builder.targets); this.defaultVisibility = builder.defaultVisibility; this.defaultVisibilitySet = builder.defaultVisibilitySet; if (builder.defaultCopts == null) { this.defaultCopts = ImmutableList.of(); } else { this.defaultCopts = ImmutableList.copyOf(builder.defaultCopts); } this.buildFile = builder.buildFile; this.containsErrors = builder.containsErrors; this.skylarkFileDependencies = builder.skylarkFileDependencies; this.defaultLicense = builder.defaultLicense; this.defaultDistributionSet = builder.defaultDistributionSet; this.features = ImmutableSortedSet.copyOf(builder.features); this.events = ImmutableList.copyOf(builder.events); this.posts = ImmutableList.copyOf(builder.posts); this.registeredExecutionPlatforms = ImmutableList.copyOf(builder.registeredExecutionPlatforms); this.registeredToolchains = ImmutableList.copyOf(builder.registeredToolchains); this.repositoryMapping = Preconditions.checkNotNull(builder.repositoryMapping); ImmutableMap.Builder> repositoryMappingsBuilder = ImmutableMap.builder(); if (!builder.externalPackageRepositoryMappings.isEmpty() && !builder.isWorkspace()) { // 'repo_mapping' should only be used in the //external package, i.e. should only appear // in WORKSPACE files. Currently, if someone tries to use 'repo_mapping' in a BUILD rule, they // will get a "no such attribute" error. This check is to protect against a 'repo_mapping' // attribute being added to a rule in the future. throw new IllegalArgumentException( "'repo_mapping' may only be used in the //external package"); } builder.externalPackageRepositoryMappings.forEach((k, v) -> repositoryMappingsBuilder.put(k, ImmutableMap.copyOf(v))); this.externalPackageRepositoryMappings = repositoryMappingsBuilder.build(); } /** * Returns the list of transitive closure of the Skylark file dependencies of this package. */ public ImmutableList

Typically getBuildFileLabel().getName().equals("BUILD") -- * though not necessarily: data in a subdirectory of a test package may use a * different filename to avoid inadvertently creating a new package. */ public Label getBuildFileLabel() { return buildFile.getLabel(); } /** * Returns the InputFile target for this package's BUILD file. */ public InputFile getBuildFile() { return buildFile; } /** * Returns true if errors were encountered during evaluation of this package. * (The package may be incomplete and its contents should not be relied upon * for critical operations. However, any Rules belonging to the package are * guaranteed to be intact, unless their containsErrors() flag * is set.) */ public boolean containsErrors() { return containsErrors; } public List getPosts() { return posts; } public List getEvents() { return events; } /** Returns an (immutable, ordered) view of all the targets belonging to this package. */ public ImmutableSortedKeyMap getTargets() { return targets; } /** * Common getTargets implementation, accessible by {@link Package.Builder}. */ private static Collection getTargets(Map targetMap) { return Collections.unmodifiableCollection(targetMap.values()); } /** * Returns a (read-only, ordered) iterable of all the targets belonging * to this package which are instances of the specified class. */ public Iterable getTargets(Class targetClass) { return getTargets(targets, targetClass); } /** * Common getTargets implementation, accessible by both {@link Package} and * {@link Package.Builder}. */ private static Iterable getTargets(Map targetMap, Class targetClass) { return Iterables.filter(targetMap.values(), targetClass); } /** * Returns the rule that corresponds to a particular BUILD target name. Useful * for walking through the dependency graph of a target. * Fails if the target is not a Rule. */ @VisibleForTesting // Should be package-private public Rule getRule(String targetName) { return (Rule) targets.get(targetName); } /** Returns all rules in the package that match the given rule class. */ public Iterable getRulesMatchingRuleClass(final String ruleClass) { Iterable targets = getTargets(Rule.class); return Iterables.filter(targets, rule -> rule.getRuleClass().equals(ruleClass)); } /** * Returns this package's workspace name. */ public String getWorkspaceName() { return workspaceName; } /** * Returns the features specified in the package() declaration. */ public ImmutableSet getFeatures() { return features; } /** * Returns the target (a member of this package) whose name is "targetName". * First rules are searched, then output files, then input files. The target * name must be valid, as defined by {@code LabelValidator#validateTargetName}. * * @throws NoSuchTargetException if the specified target was not found. */ public Target getTarget(String targetName) throws NoSuchTargetException { Target target = targets.get(targetName); if (target != null) { return target; } // No such target. // If there's a file on the disk that's not mentioned in the BUILD file, // produce a more informative error. NOTE! this code path is only executed // on failure, which is (relatively) very rare. In the common case no // stat(2) is executed. Path filename = getPackageDirectory().getRelative(targetName); String suffix; if (!PathFragment.isNormalized(targetName) || "*".equals(targetName)) { // Don't check for file existence if the target name is not normalized // because the error message would be confusing and wrong. If the // targetName is "foo/bar/.", and there is a directory "foo/bar", it // doesn't mean that "//pkg:foo/bar/." is a valid label. // Also don't check if the target name is a single * character since // it's invalid on Windows. suffix = ""; } else if (filename.isDirectory()) { suffix = "; however, a source directory of this name exists. (Perhaps add " + "'exports_files([\"" + targetName + "\"])' to " + name + "/BUILD, or define a " + "filegroup?)"; } else if (filename.exists()) { suffix = "; however, a source file of this name exists. (Perhaps add " + "'exports_files([\"" + targetName + "\"])' to " + name + "/BUILD?)"; } else { suffix = SpellChecker.didYouMean(targetName, targets.keySet()); } throw makeNoSuchTargetException(targetName, suffix); } protected NoSuchTargetException makeNoSuchTargetException(String targetName, String suffix) { Label label; try { label = createLabel(targetName); } catch (LabelSyntaxException e) { throw new IllegalArgumentException(targetName); } String msg = String.format( "target '%s' not declared in package '%s'%s defined by %s", targetName, name, suffix, filename); return new NoSuchTargetException(label, msg); } /** * Creates a label for a target inside this package. * * @throws LabelSyntaxException if the {@code targetName} is invalid */ public Label createLabel(String targetName) throws LabelSyntaxException { return Label.create(packageIdentifier, targetName); } /** * Returns the default visibility for this package. */ public RuleVisibility getDefaultVisibility() { return defaultVisibility; } /** * Returns the default testonly value. */ public Boolean getDefaultTestOnly() { return defaultTestOnly; } /** * Returns the default deprecation value. */ public String getDefaultDeprecation() { return defaultDeprecation; } /** Gets the default header checking mode. */ public String getDefaultHdrsCheck() { return defaultHdrsCheck != null ? defaultHdrsCheck : "strict"; } /** * Returns the default copts value, to which rules should append their * specific copts. */ public ImmutableList getDefaultCopts() { return defaultCopts; } /** * Returns whether the default header checking mode has been set or it is the * default value. */ public boolean isDefaultHdrsCheckSet() { return defaultHdrsCheck != null; } public boolean isDefaultVisibilitySet() { return defaultVisibilitySet; } /** * Gets the parsed license object for the default license * declared by this package. */ public License getDefaultLicense() { return defaultLicense; } /** * Returns the parsed set of distributions declared as the default for this * package. */ public Set getDefaultDistribs() { return defaultDistributionSet; } /** * Returns the default value to use for a rule's {@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} * attribute when not explicitly specified by the rule. */ public Set

" to "@" for packages within // the main workspace. private ImmutableMap repositoryMapping = ImmutableMap.of(); private Path filename = null; private Label buildFileLabel = null; private InputFile buildFile = null; // TreeMap so that the iteration order of variables is predictable. This is useful so that the // serialized representation is deterministic. private TreeMap makeEnv = new TreeMap<>(); private RuleVisibility defaultVisibility = ConstantRuleVisibility.PRIVATE; private boolean defaultVisibilitySet; private List defaultCopts = null; private List features = new ArrayList<>(); private List events = Lists.newArrayList(); private List posts = Lists.newArrayList(); @Nullable String ioExceptionMessage = null; @Nullable private IOException ioException = null; private boolean containsErrors = false; private License defaultLicense = License.NO_LICENSE; private Set defaultDistributionSet = License.DEFAULT_DISTRIB; protected Map targets = new HashMap<>(); protected Map environmentGroups = new HashMap<>(); protected ImmutableList