blob: 992cfc1a64c99866cdc6477368475d3bd3776db7 [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.actions;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversal;
import com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversalRoot;
import com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.FilesetEntry.SymlinkBehavior;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
/** Factory of {@link FilesetTraversalParams}. */
public final class FilesetTraversalParamsFactory {
/**
* Creates parameters for a recursive traversal request in a package.
*
* <p>"Recursive" means that a directory is traversed along with all of its subdirectories. Such
* a traversal is created when FilesetEntry.files is unspecified.
*
* @param ownerLabel the rule that created this object
* @param buildFile path of the BUILD file of the package to traverse
* @param destPath path in the Fileset's output directory that will be the root of files found
* in this directory
* @param excludes optional; set of files directly under this package's directory to exclude;
* files in subdirectories cannot be excluded
* @param symlinkBehaviorMode what to do with symlinks
* @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
* subpackage (contains a BUILD file)
*/
public static FilesetTraversalParams recursiveTraversalOfPackage(Label ownerLabel,
Artifact buildFile, PathFragment destPath, @Nullable Set<String> excludes,
SymlinkBehavior symlinkBehaviorMode, PackageBoundaryMode pkgBoundaryMode) {
Preconditions.checkState(buildFile.isSourceArtifact(), "%s", buildFile);
return new DirectoryTraversalParams(ownerLabel, DirectTraversalRootImpl.forPackage(buildFile),
true, destPath, excludes, symlinkBehaviorMode, pkgBoundaryMode, true, false);
}
/**
* Creates parameters for a recursive traversal request in a directory.
*
* <p>"Recursive" means that a directory is traversed along with all of its subdirectories. Such
* a traversal is created when FilesetEntry.files is unspecified.
*
* @param ownerLabel the rule that created this object
* @param directoryToTraverse path of the directory to traverse
* @param destPath path in the Fileset's output directory that will be the root of files found
* in this directory
* @param excludes optional; set of files directly below this directory to exclude; files in
* subdirectories cannot be excluded
* @param symlinkBehaviorMode what to do with symlinks
* @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
* subpackage (contains a BUILD file)
*/
public static FilesetTraversalParams recursiveTraversalOfDirectory(Label ownerLabel,
Artifact directoryToTraverse, PathFragment destPath, @Nullable Set<String> excludes,
SymlinkBehavior symlinkBehaviorMode, PackageBoundaryMode pkgBoundaryMode) {
return new DirectoryTraversalParams(ownerLabel,
DirectTraversalRootImpl.forFileOrDirectory(directoryToTraverse), false, destPath,
excludes, symlinkBehaviorMode, pkgBoundaryMode, true,
!directoryToTraverse.isSourceArtifact());
}
/**
* Creates parameters for a file traversal request.
*
* <p>Such a traversal is created for every entry in FilesetEntry.files, when it is specified.
*
* @param ownerLabel the rule that created this object
* @param fileToTraverse the file to traverse; "traversal" means that if this file is actually a
* directory or a symlink to one then it'll be traversed as one
* @param destPath path in the Fileset's output directory that will be the name of this file's
* respective symlink there, or the root of files found (in case this is a directory)
* @param symlinkBehaviorMode what to do with symlinks
* @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
* subpackage (contains a BUILD file)
*/
public static FilesetTraversalParams fileTraversal(Label ownerLabel, Artifact fileToTraverse,
PathFragment destPath, SymlinkBehavior symlinkBehaviorMode,
PackageBoundaryMode pkgBoundaryMode) {
return new DirectoryTraversalParams(ownerLabel,
DirectTraversalRootImpl.forFileOrDirectory(fileToTraverse), false, destPath, null,
symlinkBehaviorMode, pkgBoundaryMode, false, !fileToTraverse.isSourceArtifact());
}
/**
* Creates traversal request parameters for a FilesetEntry wrapping another Fileset. If possible,
* the original {@code nested} is returned to avoid unnecessary object creation. In that case, the
* {@code ownerLabelForErrorMessages} may be ignored. Since the wrapping traversal could not have
* an error on its own, any error messages printed will still be correct.
*
* @param ownerLabel the rule that created this object
* @param nested the traversal params that were used for the nested (inner) Fileset
* @param destDir path in the Fileset's output directory that will be the root of files coming
* from the nested Fileset
* @param excludes optional; set of files directly below (not in a subdirectory of) the nested
* Fileset that should be excluded from the outer Fileset
*/
public static FilesetTraversalParams nestedTraversal(
Label ownerLabel,
FilesetTraversalParams nested,
PathFragment destDir,
@Nullable Set<String> excludes) {
if (destDir.segmentCount() == 0 && (excludes == null || excludes.isEmpty())) {
// Wrapping the traversal here would not lead to a different result: the output location is
// the same and there are no additional excludes.
return nested;
}
// When srcdir is another Fileset, then files must be null so strip_prefix must also be null.
return new NestedTraversalParams(ownerLabel, nested, destDir, excludes);
}
private abstract static class ParamsCommon implements FilesetTraversalParams {
private final Label ownerLabelForErrorMessages;
private final PathFragment destDir;
private final ImmutableSet<String> excludes;
ParamsCommon(
Label ownerLabelForErrorMessages, PathFragment destDir, @Nullable Set<String> excludes) {
this.ownerLabelForErrorMessages = ownerLabelForErrorMessages;
this.destDir = destDir;
if (excludes == null) {
this.excludes = ImmutableSet.of();
} else {
// Order the set for the sake of deterministic fingerprinting.
this.excludes = ImmutableSet.copyOf(Ordering.natural().immutableSortedCopy(excludes));
}
}
@Override
public Label getOwnerLabelForErrorMessages() {
return ownerLabelForErrorMessages;
}
@Override
public Set<String> getExcludedFiles() {
return excludes;
}
@Override
public PathFragment getDestPath() {
return destDir;
}
protected final void commonFingerprint(Fingerprint fp) {
fp.addPath(destDir);
if (!excludes.isEmpty()) {
fp.addStrings(excludes);
}
}
@Override
public String toString() {
return super.toString()
+ "["
+ destDir
+ ", "
+ ownerLabelForErrorMessages
+ ", "
+ excludes
+ "]";
}
protected boolean internalEquals(ParamsCommon that) {
return Objects.equals(this.ownerLabelForErrorMessages, that.ownerLabelForErrorMessages)
&& Objects.equals(this.destDir, that.destDir)
&& Objects.equals(this.excludes, that.excludes);
}
protected int internalHashCode() {
return Objects.hash(ownerLabelForErrorMessages, destDir, excludes);
}
}
private static final class DirectTraversalImpl implements DirectTraversal {
private final DirectTraversalRoot root;
private final boolean isPackage;
private final boolean followSymlinks;
private final PackageBoundaryMode pkgBoundaryMode;
private final boolean isRecursive;
private final boolean isGenerated;
DirectTraversalImpl(DirectTraversalRoot root, boolean isPackage, boolean followSymlinks,
PackageBoundaryMode pkgBoundaryMode, boolean isRecursive, boolean isGenerated) {
this.root = root;
this.isPackage = isPackage;
this.followSymlinks = followSymlinks;
this.pkgBoundaryMode = pkgBoundaryMode;
this.isRecursive = isRecursive;
this.isGenerated = isGenerated;
}
@Override
public DirectTraversalRoot getRoot() {
return root;
}
@Override
public boolean isPackage() {
return isPackage;
}
@Override
public boolean isRecursive() {
return isRecursive;
}
@Override
public boolean isGenerated() {
return isGenerated;
}
@Override
public boolean isFollowingSymlinks() {
return followSymlinks;
}
@Override
public PackageBoundaryMode getPackageBoundaryMode() {
return pkgBoundaryMode;
}
void fingerprint(Fingerprint fp) {
fp.addPath(root.asRootedPath().asPath());
fp.addBoolean(isPackage);
fp.addBoolean(followSymlinks);
fp.addBoolean(isRecursive);
fp.addBoolean(isGenerated);
pkgBoundaryMode.fingerprint(fp);
}
}
private static final class DirectoryTraversalParams extends ParamsCommon {
private final DirectTraversalImpl traversal;
DirectoryTraversalParams(Label ownerLabel,
DirectTraversalRoot root,
boolean isPackage,
PathFragment destPath,
@Nullable Set<String> excludes,
SymlinkBehavior symlinkBehaviorMode,
PackageBoundaryMode pkgBoundaryMode,
boolean isRecursive,
boolean isGenerated) {
super(ownerLabel, destPath, excludes);
traversal = new DirectTraversalImpl(root, isPackage,
symlinkBehaviorMode == SymlinkBehavior.DEREFERENCE, pkgBoundaryMode, isRecursive,
isGenerated);
}
@Override
public Optional<DirectTraversal> getDirectTraversal() {
return Optional.<DirectTraversal>of(traversal);
}
@Override
public Optional<FilesetTraversalParams> getNestedTraversal() {
return Optional.absent();
}
@Override
public void fingerprint(Fingerprint fp) {
commonFingerprint(fp);
traversal.fingerprint(fp);
}
@Override
public int hashCode() {
return 37 * super.internalHashCode() + Objects.hashCode(traversal);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DirectoryTraversalParams)) {
return false;
}
DirectoryTraversalParams that = (DirectoryTraversalParams) obj;
return Objects.equals(this.traversal, that.traversal) && internalEquals(that);
}
}
private static final class NestedTraversalParams extends ParamsCommon {
private final FilesetTraversalParams nested;
NestedTraversalParams(
Label ownerLabel,
FilesetTraversalParams nested,
PathFragment destDir,
@Nullable Set<String> excludes) {
super(ownerLabel, destDir, excludes);
this.nested = nested;
}
@Override
public Optional<DirectTraversal> getDirectTraversal() {
return Optional.absent();
}
@Override
public Optional<FilesetTraversalParams> getNestedTraversal() {
return Optional.of(nested);
}
@Override
public void fingerprint(Fingerprint fp) {
commonFingerprint(fp);
nested.fingerprint(fp);
}
@Override
public int hashCode() {
return 37 * super.internalHashCode() + Objects.hashCode(nested);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof NestedTraversalParams)) {
return false;
}
NestedTraversalParams that = (NestedTraversalParams) obj;
return Objects.equals(this.nested, that.nested) && internalEquals(that);
}
}
private static final class DirectTraversalRootImpl implements DirectTraversalRoot {
private final Path rootDir;
private final PathFragment relativeDir;
static DirectTraversalRoot forPackage(Artifact buildFile) {
return new DirectTraversalRootImpl(buildFile.getRoot().getPath(),
buildFile.getRootRelativePath().getParentDirectory());
}
static DirectTraversalRoot forFileOrDirectory(Artifact fileOrDirectory) {
return new DirectTraversalRootImpl(fileOrDirectory.getRoot().getPath(),
fileOrDirectory.getRootRelativePath());
}
private DirectTraversalRootImpl(Path rootDir, PathFragment relativeDir) {
this.rootDir = rootDir;
this.relativeDir = relativeDir;
}
@Override
public Path getRootPart() {
return rootDir;
}
@Override
public PathFragment getRelativePart() {
return relativeDir;
}
@Override
public RootedPath asRootedPath() {
return RootedPath.toRootedPath(rootDir, relativeDir);
}
}
}