aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
blob: 1d9fd67f40721c8ec7e4ed9dc48505c4d52ad97a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// 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.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.Instantiator;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import java.util.Objects;
import javax.annotation.Nullable;

/**
 * Parameters of a filesystem traversal requested by a Fileset rule.
 *
 * <p>This object stores the details of the traversal request, e.g. whether it's a direct or nested
 * traversal (see {@link #getDirectTraversal()} and {@link #getNestedArtifact()}) or who the owner
 * of the traversal is.
 */
public interface FilesetTraversalParams {

  /** Desired behavior if the traversal hits a directory with a BUILD file, i.e. a subpackage. */
  enum PackageBoundaryMode {
    /** The traversal should recurse into the directory, optionally reporting a warning. */
    CROSS,

    // TODO(bazel-team): deprecate CROSS and REPORT_ERROR in favor of DONT_CROSS. Clean up the depot
    // and lock down the semantics of FilesetEntry.srcdir to only accept other Filesets or BUILD
    // files of a package, in which case also require an explicit list of files.
    /** The traversal should not recurse into the directory but silently skip it. */
    DONT_CROSS,

    /** The traversal should not recurse into the directory and report an error. */
    REPORT_ERROR;

    public static PackageBoundaryMode forStrictFilesetFlag(boolean flagEnabled) {
      return flagEnabled ? REPORT_ERROR : CROSS;
    }

    public void fingerprint(Fingerprint fp) {
      fp.addInt(ordinal());
    }
  }

  /**
   * The root directory of a {@link DirectTraversal}.
   *
   * <ul>
   *   <li>The root of package traversals is the package directory, i.e. the parent of the BUILD
   *       file.
   *   <li>The root of "recursive" directory traversals is the directory's path.
   *   <li>The root of "file" traversals is the path of the file (or directory, or symlink) itself.
   * </ul>
   *
   * <p>For the meaning of "recursive" and "file" traversals see {@link DirectTraversal}.
   */
  @AutoCodec
  @AutoValue
  abstract class DirectTraversalRoot {

    /**
     * Returns the output Artifact corresponding to this traversal, if present. Only present when
     * traversing a generated output.
     */
    @Nullable
    public abstract Artifact getOutputArtifact();

    /**
     * Returns the root part of the full path.
     *
     * <p>This is typically the workspace root or some output tree's root (e.g. genfiles, binfiles).
     */
    public abstract Root getRootPart();

    /**
     * Returns the {@link #getRootPart() root}-relative part of the path.
     *
     * <p>This is typically the source directory under the workspace or the output file under an
     * output directory.
     */
    public abstract PathFragment getRelativePart();

    /** Returns a {@link RootedPath} composed of the root and relative parts. */
    public RootedPath asRootedPath() {
      return RootedPath.toRootedPath(getRootPart(), getRelativePart());
    }

    @Override
    public final boolean equals(Object o) {
      if (o == this) {
        return true;
      }
      if (o instanceof FilesetTraversalParams.DirectTraversalRoot) {
        FilesetTraversalParams.DirectTraversalRoot that =
            (FilesetTraversalParams.DirectTraversalRoot) o;
        return Objects.equals(this.getOutputArtifact(), that.getOutputArtifact())
            && (this.getRootPart().equals(that.getRootPart()))
            && (this.getRelativePart().equals(that.getRelativePart()));
      }
      return false;
    }

    @Memoized
    @Override
    public abstract int hashCode();

    public static DirectTraversalRoot forPackage(Artifact buildFile) {
      return create(
          null,
          buildFile.getRoot().getRoot(),
          buildFile.getRootRelativePath().getParentDirectory());
    }

    public static DirectTraversalRoot forFileOrDirectory(Artifact fileOrDirectory) {
      return create(
          fileOrDirectory.isSourceArtifact() ? null : fileOrDirectory,
          fileOrDirectory.getRoot().getRoot(),
          fileOrDirectory.getRootRelativePath());
    }

    public static DirectTraversalRoot forRootedPath(RootedPath newPath) {
      return create(null, newPath.getRoot(), newPath.getRootRelativePath());
    }

    @Instantiator
    @VisibleForSerialization
    static DirectTraversalRoot create(
        @Nullable Artifact outputArtifact, Root rootPart, PathFragment relativePart) {
      return new AutoValue_FilesetTraversalParams_DirectTraversalRoot(
          outputArtifact, rootPart, relativePart);
    }
  }

  /**
   * A request for a direct filesystem traversal.
   *
   * <p>"Direct" means this corresponds to an actual filesystem traversal as opposed to traversing
   * another Fileset rule, which is called a "nested" traversal.
   *
   * <p>Direct traversals can further be divided into two categories, "file" traversals and
   * "recursive" traversals.
   *
   * <p>File traversal requests are created when the FilesetEntry.files attribute is defined; one
   * file traversal request is created for each entry.
   *
   * <p>Recursive traversal requests are created when the FilesetEntry.files attribute is
   * unspecified; one recursive traversal request is created for the FilesetEntry.srcdir.
   *
   * <p>See {@link DirectTraversal#getRoot()} for more details.
   */
  @AutoValue
  @AutoCodec
  abstract class DirectTraversal {

    /** Returns the root of the traversal; see {@link DirectTraversalRoot}. */
    public abstract DirectTraversalRoot getRoot();

    /**
     * Returns true if this traversal refers to a whole package.
     *
     * <p>In that case the root (see {@link #getRoot()}) refers to the path of the package.
     *
     * <p>Package traversals are always recursive (see {@link #isRecursive()}) and are never
     * generated (see {@link #isGenerated()}).
     */
    public abstract boolean isPackage();

    /**
     * Returns true if this is a "recursive traversal", i.e. created from FilesetEntry.srcdir.
     *
     * <p>This type of traversal is created when the FilesetEntry doesn't define a "files" list.
     * When it does, the traversal is referred to as a "file traversal". When it doesn't, but the
     * srcdir points to another Fileset, it is called a "nested" traversal.
     *
     * <p>Recursive traversals got their name from recursively traversing a directory structure.
     * These are usually whole-package traversals, i.e. when FilesetEntry.srcdir refers to a BUILD
     * file (see {@link #isPackage()}), but sometimes the srcdir references a input or output
     * directory (the latter being generated by a local genrule) or a symlink (which must point to a
     * directory; enforced during action execution).
     *
     * <p>The files in the results of a recursive traversal are all under the {@link #getRoot()
     * root}. The root's path is stripped from the results.
     *
     * <p>N.B.: "file traversals" can also be recursive if the entry in FilesetEntry.files, for
     * which the traversal parameters were created, turned out to be a directory. The difference
     * lies in how the output paths are computed (with recursive traversals, the directory's name
     * is stripped; with file traversals it is not, modulo usage of strip_prefix and the excludes
     * attributes), and how directory symlinks are handled (in "recursive traversals" they are
     * expanded just like normal directories, subsequent directory symlinks under them are *not*
     * expanded though; they are not expanded at all in "file traversals").
     */
    public abstract boolean isRecursive();

    /** Returns true if the root points to a generated file, symlink or directory. */
    public abstract boolean isGenerated();

    /** Returns true if input symlinks should be dereferenced; false if copied. */
    public abstract boolean isFollowingSymlinks();

    /** Returns the desired behavior when the traversal hits a subpackage. */
    public abstract PackageBoundaryMode getPackageBoundaryMode();

    /** Returns whether Filesets treat outputs in a strict manner, assuming regular files. */
    public abstract boolean isStrictFilesetOutput();

    @Memoized
    @Override
    public abstract int hashCode();

    @Memoized
    byte[] getFingerprint() {
      Fingerprint fp = new Fingerprint();
      fp.addPath(getRoot().asRootedPath().asPath());
      fp.addBoolean(isPackage());
      fp.addBoolean(isFollowingSymlinks());
      fp.addBoolean(isRecursive());
      fp.addBoolean(isGenerated());
      fp.addBoolean(isStrictFilesetOutput());
      getPackageBoundaryMode().fingerprint(fp);
      return fp.digestAndReset();
    }

    @AutoCodec.Instantiator
    static DirectTraversal getDirectTraversal(
        DirectTraversalRoot root,
        boolean isPackage,
        boolean followingSymlinks,
        PackageBoundaryMode packageBoundaryMode,
        boolean isStrictFilesetOutput,
        boolean isRecursive,
        boolean isGenerated) {
      return new AutoValue_FilesetTraversalParams_DirectTraversal(
          root,
          isPackage,
          isRecursive,
          isGenerated,
          followingSymlinks,
          packageBoundaryMode,
          isStrictFilesetOutput);
    }
  }

  /** Label of the Fileset rule that owns this traversal. */
  Label getOwnerLabelForErrorMessages();

  /** Returns the directory under the output path where the files will be mapped. May be empty. */
  PathFragment getDestPath();

  /** Returns a list of file basenames to be excluded from the output. May be empty. */
  ImmutableSortedSet<String> getExcludedFiles();

  /**
   * Returns the parameters of the direct traversal request, if any.
   *
   * <p>A direct traversal is anything that's not a nested traversal, e.g. traversal of a package or
   * directory (when FilesetEntry.srcdir is specified) or traversal of a single file (when
   * FilesetEntry.files is specified). See {@link DirectTraversal} for more detail.
   *
   * <p>The value is present if and only if {@link #getNestedArtifact} is empty.
   */
  Optional<DirectTraversal> getDirectTraversal();

  /**
   * Returns the Fileset Artifact of the nested traversal request, if any.
   *
   * <p>A nested traversal is the traversal of another Fileset referenced by FilesetEntry.srcdir.
   *
   * <p>The value is null when {@link #getDirectTraversal} is absent.
   */
  @Nullable
  Artifact getNestedArtifact();

  /** Adds the fingerprint of this traversal object. */
  void fingerprint(Fingerprint fp);
}