blob: d12e185fa6db68047f4df095290deaa984d57dc4 [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.ImmutableList;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
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.skylarkbuildapi.FileRootApi;
import com.google.devtools.build.lib.syntax.Printer;
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.io.Serializable;
import java.util.Objects;
/**
* 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>The derived roots must have paths that point inside the exec root, i.e. below the directory
* that is the root of the merged directory tree.
*/
@AutoCodec
@Immutable
public final class ArtifactRoot implements Comparable<ArtifactRoot>, Serializable, 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 new ArtifactRoot(root, PathFragment.EMPTY_FRAGMENT, RootType.Source);
}
/**
* 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)}).
*/
public static ArtifactRoot asDerivedRoot(Path execRoot, String... prefixes) {
Path root = execRoot;
for (String prefix : prefixes) {
// Tests can have empty segments here, be gentle to them.
if (!prefix.isEmpty()) {
root = root.getChild(prefix);
}
}
Preconditions.checkArgument(root.startsWith(execRoot));
Preconditions.checkArgument(!root.equals(execRoot));
PathFragment execPath = root.relativeTo(execRoot);
return INTERNER.intern(new ArtifactRoot(Root.fromPath(root), execPath, RootType.Output));
}
public static ArtifactRoot middlemanRoot(Path execRoot, Path outputDir) {
Path root = outputDir.getRelative("internal");
Preconditions.checkArgument(root.startsWith(execRoot));
Preconditions.checkArgument(!root.equals(execRoot));
PathFragment execPath = root.relativeTo(execRoot);
return INTERNER.intern(new ArtifactRoot(Root.fromPath(root), execPath, RootType.Middleman));
}
@AutoCodec.VisibleForSerialization
@AutoCodec.Instantiator
static ArtifactRoot createForSerialization(
Root rootForSerialization, PathFragment execPath, RootType rootType) {
if (rootType != RootType.Output) {
return INTERNER.intern(new ArtifactRoot(rootForSerialization, execPath, rootType));
}
return asDerivedRoot(
rootForSerialization.asPath(), execPath.getSegments().toArray(new String[0]));
}
@AutoCodec.VisibleForSerialization
enum RootType {
Source,
Output,
Middleman
}
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 getExecPath().getPathString();
}
public ImmutableList<String> getComponents() {
return execPath.getSegments();
}
public boolean isSourceRoot() {
return rootType == RootType.Source;
}
boolean isMiddlemanRoot() {
return 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 (rootType != RootType.Output) {
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>");
}
}