blob: ca1a6e6e01db6884f2477ff2ae8e7769e1909841 [file] [log] [blame]
// 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.actions;
import com.google.common.base.Preconditions;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
import com.google.devtools.build.lib.actions.Artifact.SourceArtifact;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.starlarkbuildapi.FileRootApi;
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 java.util.Objects;
import net.starlark.java.eval.Printer;
/**
* A root for an artifact. The roots are the directories containing artifacts, and they are mapped
* together into a single directory tree to form the execution environment. There are two kinds of
* roots: source roots and derived roots. Source roots correspond to entries of the package path,
* and they can be anywhere on disk. Derived roots correspond to output directories; there are
* generally different output directories for different configurations, and different types of
* output (bin, genfiles, includes, etc.).
*
* <p>When mapping the roots into a single directory tree, the source roots are merged, such that
* each package is accessed in its entirety from a single source root. The package cache is
* responsible for determining that mapping. The derived roots, on the other hand, have to be
* distinct. (It is currently allowed to have a derived root that is the prefix of another one.)
*
* <p>Derived roots must have paths that point inside the exec root ({@link
* com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot}), i.e. below the directory
* that is the root of the merged directory tree.
*
* <p>For example, if
*
* <ul>
* <li>your workspace is {@code /home/my_workspace/}
* <li>your package path is {@code /home/my_workspace/} (the norm unless you're customizing {@link
* com.google.devtools.build.lib.pkgcache.PackageOptions#packagePath}).
* <li>your workspace has a source file at {@code /home/my_workspace/myapp/source.go}
* <li>you build a binary that outputs {@code /home/my_workspace/bazel-out/x86-opt/bin/a/mybinary}
* </ul>
*
* <p>then
*
* <ul>
* <li>Bazel creates an "output base" directory {@code $OUTPUT_BASE} which it uses for staging
* build work: {@link com.google.devtools.build.lib.analysis.BlazeDirectories#getOutputBase()}
* <li>Bazel creates an exec root at {@code $OUTPUT_BASE/execroot/my_workspace/}. This symlinks
* all files and directories under {@code /home/my_workspace/}. This is the working directory
* where actions run (either directly for local execution or as the base for staging remote
* execution paths). This is also the base directory for writing outputs.
* <li>{@code /home/my_workspace/myapp/source.go} is a {@link SourceArtifact} with source root
* {@code /home/my_workspace/}
* <li>{@code /home/my_workspace/bazel-out/x86-opt/bin/a/mybinary} is a {@link DerivedArtifact}.
* Because derived artifacts are written under the exec root, {@code
* /home/my_workspace/bazel-out} is a symlink to {@code $EXEC_ROOT/bazel-out}. So {@code
* mybinary} is actually at {@code $EXEC_ROOT/bazel-out/x86-opt/bin/mybinary}. Its derived
* root is therefore {@code $EXEC_ROOT/bazel-out/x86-opt/bin/}.
* </ul>
*
* <p>The "exec path" ({@link Artifact#getExecPath()}, {@link #getExecPath()}, etc.) is an entity's
* path relative to the exec root. So {@code /home/my_workspace/myapp/source.go}'s exec path is
* {@code myapp/source.go} and {@code /home/my_workspace/bazel-out/x86-opt/bin/a/mybinary}'s exec
* path is {@code bazel-out/x86-opt/bin/a/mybinary}
*
* <p>The "root-relative path" ({@link Artifact#getRootRelativePath()}) is a entity's path relative
* to its root. So {@code /home/my_workspace/myapp/source.go}'s root-relative path is {@code
* myapp/source.go} and {@code /home/my_workspace/bazel-out/x86-opt/bin/a/mybinary}'s root-relative
* path is {@code a/mybinary}.
*
* <p>For concrete examples, run {@code $ bazel info} in your terminal after a build. Also see <a
* href="https://bazel.build/remote/output-directories">Bazel's output directory docs</a>.
*/
@AutoCodec
@Immutable
public final class ArtifactRoot implements Comparable<ArtifactRoot>, FileRootApi {
private static final Interner<ArtifactRoot> INTERNER = Interners.newWeakInterner();
/**
* Do not use except in tests and in {@link
* com.google.devtools.build.lib.skyframe.SkyframeExecutor}.
*
* <p>Returns the given path as a source root. The path may not be {@code null}.
*/
public static ArtifactRoot asSourceRoot(Root root) {
return INTERNER.intern(
new ArtifactRoot(root, PathFragment.EMPTY_FRAGMENT, RootType.MainSource));
}
/**
* Do not use except in tests and in {@link
* com.google.devtools.build.lib.skyframe.SkyframeExecutor}.
*
* <p>Returns the given path as the external source root. The path should end with {@link
* LabelConstants.EXTERNAL_REPOSITORY_LOCATION} since the external repository root is always
* $OUTPUT_BASE/external regardless of the layout of the exec root.
*/
public static ArtifactRoot asExternalSourceRoot(Root root) {
Preconditions.checkArgument(
root.asPath()
.asFragment()
.getParentDirectory()
.endsWith(LabelConstants.EXTERNAL_REPOSITORY_LOCATION));
return INTERNER.intern(
new ArtifactRoot(root, PathFragment.EMPTY_FRAGMENT, RootType.ExternalSource));
}
/**
* Constructs an ArtifactRoot given the output prefixes. (eg, "bin"), and (eg, "testlogs")
* relative to the execRoot.
*
* <p>Be careful with this method - all derived roots must be within the derived artifacts tree,
* defined in ArtifactFactory (see {@link ArtifactFactory#isDerivedArtifact(PathFragment)}).
*
* <p>Call {@link #asDerivedRoot(Path, RootType, PathFragment)} if you already have a {@link
* PathFragment} instance for the exec path.
*/
public static ArtifactRoot asDerivedRoot(Path execRoot, RootType rootType, String... prefixes) {
PathFragment execPath = PathFragment.EMPTY_FRAGMENT;
for (String prefix : prefixes) {
// Tests can have empty segments here, be gentle to them.
if (!prefix.isEmpty()) {
execPath = execPath.getChild(prefix);
}
}
return asDerivedRoot(execRoot, rootType, execPath);
}
/**
* Constructs an {@link ArtifactRoot} given the execPath, relative to the execRoot.
*
* <p>Be careful with this method - all derived roots must be within the derived artifacts tree,
* defined in ArtifactFactory (see {@link ArtifactFactory#isDerivedArtifact(PathFragment)}).
*/
public static ArtifactRoot asDerivedRoot(
Path execRoot, RootType rootType, PathFragment execPath) {
// Make sure that we are not creating a derived artifact under the execRoot.
Preconditions.checkArgument(!execPath.isEmpty(), "empty execPath");
Preconditions.checkArgument(!execPath.isAbsolute(), "execPath must be relative: %s", execPath);
Preconditions.checkArgument(
!execPath.containsUplevelReferences(),
"execPath: %s contains parent directory reference (..)",
execPath);
Preconditions.checkArgument(
isOutputRootType(rootType) || isMiddlemanRootType(rootType),
"%s is not a derived root type",
rootType);
Path root = execRoot.getRelative(execPath);
return INTERNER.intern(new ArtifactRoot(Root.fromPath(root), execPath, rootType));
}
@AutoCodec.VisibleForSerialization
@AutoCodec.Instantiator
static ArtifactRoot createForSerialization(
Root rootForSerialization, PathFragment execPath, RootType rootType) {
if (!isOutputRootType(rootType)) {
return INTERNER.intern(new ArtifactRoot(rootForSerialization, execPath, rootType));
}
return asDerivedRoot(rootForSerialization.asPath(), rootType, execPath);
}
/**
* ArtifactRoot types. Callers of asDerivedRoot methods need to specify which type of derived root
* artifact they want to create, which is why this enum is public.
*/
public enum RootType {
MainSource,
ExternalSource,
Output,
Middleman,
// Sibling root types are in effect when --experimental_sibling_repository_layout is activated.
// These will eventually replace the above Output and Middleman types when the flag becomes
// the default option and then removed.
SiblingMainOutput,
SiblingMainMiddleman,
SiblingExternalOutput,
SiblingExternalMiddleman,
}
private final Root root;
private final PathFragment execPath;
private final RootType rootType;
private ArtifactRoot(Root root, PathFragment execPath, RootType rootType) {
this.root = Preconditions.checkNotNull(root);
this.execPath = execPath;
this.rootType = rootType;
}
@Override
public boolean isImmutable() {
return true; // immutable and Starlark-hashable
}
public Root getRoot() {
return root;
}
/**
* Returns the path fragment from the exec root to the actual root. For source roots, this returns
* the empty fragment.
*/
public PathFragment getExecPath() {
return execPath;
}
@Override
public String getExecPathString() {
return execPath.getPathString();
}
public boolean isSourceRoot() {
return rootType == RootType.MainSource || rootType == RootType.ExternalSource;
}
private static boolean isOutputRootType(RootType rootType) {
return rootType == RootType.SiblingMainOutput
|| rootType == RootType.SiblingExternalOutput
|| rootType == RootType.Output;
}
private static boolean isMiddlemanRootType(RootType rootType) {
return rootType == RootType.SiblingMainMiddleman
|| rootType == RootType.SiblingExternalMiddleman
|| rootType == RootType.Middleman;
}
boolean isMiddlemanRoot() {
return isMiddlemanRootType(rootType);
}
public boolean isExternal() {
return rootType == RootType.ExternalSource
|| rootType == RootType.SiblingExternalOutput
|| rootType == RootType.SiblingExternalMiddleman;
}
/**
* Returns true if the ArtifactRoot is a legacy derived root type, i.e. a derived root type
* created without the --experimental_sibling_repository_layout flag set.
*/
public boolean isLegacy() {
return rootType == RootType.Output || rootType == RootType.Middleman;
}
@Override
public int compareTo(ArtifactRoot o) {
return root.compareTo(o.root);
}
@Override
public int hashCode() {
return Objects.hash(root, execPath, rootType);
}
/**
* The Root of a derived ArtifactRoot contains the exec path. In order to avoid duplicating that
* path, and enable the Root to be serialized as a constant, we return the "exec root" Root here,
* by stripping the exec path. That Root is likely to be serialized as a constant by {@link
* Root.RootCodec}, saving a lot of serialized bytes on the wire.
*/
@SuppressWarnings("unused") // Used by @AutoCodec.
Root getRootForSerialization() {
if (!isOutputRootType(rootType)) {
return root;
}
// Find fragment of root that does not include execPath and return just that root. It is likely
// to be serialized as a constant by RootCodec. For instance, if the original exec root was
// /execroot, and this root was /execroot/bazel-out/bin, with execPath bazel-out/bin, then we
// just serialize /execroot and bazel-out/bin separately.
// We just want to strip execPath from root, but I don't know a trivial way to do that.
PathFragment rootFragment = root.asPath().asFragment();
return Root.fromPath(
root.asPath()
.getFileSystem()
.getPath(
rootFragment.subFragment(
0, rootFragment.segmentCount() - execPath.segmentCount())));
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof ArtifactRoot)) {
return false;
}
ArtifactRoot r = (ArtifactRoot) o;
return root.equals(r.root) && execPath.equals(r.execPath) && rootType == r.rootType;
}
@Override
public String toString() {
return root + (isSourceRoot() ? "[source]" : "[derived]");
}
@Override
public void repr(Printer printer) {
printer.append(isSourceRoot() ? "<source root>" : "<derived root>");
}
}