| // 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); |
| } |
| } |
| } |