+ subpackages() is a helper function, similar to glob()
+ that lists subpackages instead of files and directories. It uses the same
+ path patterns as glob() and can match any subpackage that is a
+ direct decendant of the currently loading BUILD file. See glob for a detailed explanation and examples of include and
+ exclude patterns.
+
+ The resulting list of subpackages returned is in sorted order and contains
+ paths relative to the current loading package that match the given patterns in
+ include and not those in exclude.
-
In the above example, multiplatform_app links with additional
- options when invoked with bazel build //pkg:multiplatform_app
- --crosstool_top=//crosstools/windows .
+
Example
+ The following example lists all the direct subpackages for the package foo/BUILD
+
+
+# The following BUILD files exist:
+# foo/BUILD
+# foo/bar/baz/BUILD
+# foo/sub/BUILD
+# foo/sub/deeper/BUILD
+#
+# In foo/BUILD a call to
+subs = subpackages(include = ["**"])
+
+# results in subs == ["sub", "bar/baz"]
+#
+# 'sub/deeper' is not included because it is a subpackage of 'foo/sub' not of
+# 'foo'
+
+
+
+ In general it is preferred that instead of calling this function directly
+ that users use the 'subpackages' module of
+ skylib.
#if (!$singlePage)
#parse("com/google/devtools/build/docgen/templates/be/footer.vm")
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index eb84111920299d..ce55b174f16cd0 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -400,6 +400,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/exec:test_xml_output_parser_exception",
"//src/main/java/com/google/devtools/build/lib/graph",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
"//src/main/java/com/google/devtools/build/lib/packages:exec_group",
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
"//src/main/java/com/google/devtools/build/lib/profiler",
diff --git a/src/main/java/com/google/devtools/build/lib/includescanning/BUILD b/src/main/java/com/google/devtools/build/lib/includescanning/BUILD
index 7da1f1179557ca..d156e189f0a2ca 100644
--- a/src/main/java/com/google/devtools/build/lib/includescanning/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/includescanning/BUILD
@@ -33,6 +33,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/exec:module_action_context_registry",
"//src/main/java/com/google/devtools/build/lib/exec:spawn_strategy_resolver",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber",
"//src/main/java/com/google/devtools/build/lib/profiler",
"//src/main/java/com/google/devtools/build/lib/rules/cpp",
"//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_value",
diff --git a/src/main/java/com/google/devtools/build/lib/includescanning/IncludeParser.java b/src/main/java/com/google/devtools/build/lib/includescanning/IncludeParser.java
index 0e405b0a259934..b0d0b1f07936f1 100644
--- a/src/main/java/com/google/devtools/build/lib/includescanning/IncludeParser.java
+++ b/src/main/java/com/google/devtools/build/lib/includescanning/IncludeParser.java
@@ -38,6 +38,7 @@
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.includescanning.IncludeParser.Inclusion.Kind;
+import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
@@ -347,7 +348,7 @@ ImmutableSet getPathLevelHintedInclusions(
containingPackageLookupValue.getContainingPackageName(),
containingPackageLookupValue.getContainingPackageRoot(),
pattern,
- /*excludeDirs=*/ true,
+ Globber.Operation.FILES,
relativePath.relativeTo(packageFragment)));
} catch (InvalidGlobPatternException e) {
env.getListener()
diff --git a/src/main/java/com/google/devtools/build/lib/packages/BUILD b/src/main/java/com/google/devtools/build/lib/packages/BUILD
index f4f1d8164671d8..b7fac19dac5551 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/packages/BUILD
@@ -10,21 +10,51 @@ filegroup(
visibility = ["//src:__subpackages__"],
)
+java_library(
+ name = "configured_attribute_mapper",
+ srcs = ["ConfiguredAttributeMapper.java"],
+ deps = [
+ ":packages",
+ "//src/main/java/com/google/devtools/build/lib/analysis:config/config_matching_provider",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
+
+java_library(
+ name = "globber",
+ srcs = ["Globber.java"],
+)
+
+java_library(
+ name = "globber_utils",
+ srcs = ["GlobberUtils.java"],
+ deps = [
+ ":globber",
+ "//third_party:error_prone_annotations",
+ ],
+)
+
java_library(
name = "packages",
srcs = glob(
["*.java"],
exclude = [
"BuilderFactoryForTesting.java", # see builder_factory_for_testing
+ "Globber.java",
+ "GlobberUtils.java",
"ExecGroup.java",
+ "ConfiguredAttributeMapper.java",
],
),
deps = [
":exec_group",
+ ":globber",
+ ":globber_utils",
"//src/main/java/com/google/devtools/build/docgen/annot",
"//src/main/java/com/google/devtools/build/lib/actions:execution_requirements",
"//src/main/java/com/google/devtools/build/lib/actions:thread_state_receiver",
- "//src/main/java/com/google/devtools/build/lib/analysis:config/config_matching_provider",
"//src/main/java/com/google/devtools/build/lib/analysis:config/fragment",
"//src/main/java/com/google/devtools/build/lib/analysis:config/fragment_class_set",
"//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/configuration_transition",
diff --git a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
index 33c882d98ffdbb..11caecc411b115 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
@@ -15,7 +15,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
@@ -29,6 +28,7 @@
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.UnixGlob;
+import com.google.devtools.build.lib.vfs.UnixGlobPathDiscriminator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,36 +44,26 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-/**
- * Caches the results of glob expansion for a package.
- */
+/** Caches the results of glob expansion for a package. */
@ThreadSafety.ThreadCompatible
public class GlobCache {
/**
- * A mapping from glob expressions (e.g. "*.java") to the list of files it
- * matched (in the order returned by VFS) at the time the package was
- * constructed. Required for sound dependency analysis.
+ * A mapping from glob expressions (e.g. "*.java") to the list of files it matched (in the order
+ * returned by VFS) at the time the package was constructed. Required for sound dependency
+ * analysis.
*
- * We don't use a Multimap because it provides no way to distinguish "key not
- * present" from (key -> {}).
+ *
We don't use a Multimap because it provides no way to distinguish "key not present" from
+ * (key -> {}).
*/
- private final Map, Future>> globCache = new HashMap<>();
+ private final Map, Future>> globCache =
+ new HashMap<>();
- /**
- * The directory in which our package's BUILD file resides.
- */
+ /** The directory in which our package's BUILD file resides. */
private final Path packageDirectory;
- /**
- * The name of the package we belong to.
- */
+ /** The name of the package we belong to. */
private final PackageIdentifier packageId;
- /**
- * The package locator-based directory traversal predicate.
- */
- private final Predicate childDirectoryPredicate;
-
/** System call caching layer. */
private final AtomicReference extends UnixGlob.FilesystemCalls> syscalls;
@@ -84,6 +74,10 @@ public class GlobCache {
private final AtomicBoolean globalStarted = new AtomicBoolean(false);
+ private final CachingPackageLocator packageLocator;
+
+ private final ImmutableSet ignoredGlobPrefixes;
+
/**
* Create a glob expansion cache.
*
@@ -120,47 +114,55 @@ public GlobCache(
this.maxDirectoriesToEagerlyVisit = maxDirectoriesToEagerlyVisit;
Preconditions.checkNotNull(locator);
- childDirectoryPredicate =
- directory -> {
- if (directory.equals(packageDirectory)) {
- return true;
- }
+ this.packageLocator = locator;
+ this.ignoredGlobPrefixes = ignoredGlobPrefixes;
+ }
- PathFragment subPackagePath =
- packageId.getPackageFragment().getRelative(directory.relativeTo(packageDirectory));
+ private boolean globCacheShouldTraverseDirectory(Path directory) {
+ if (directory.equals(packageDirectory)) {
+ return true;
+ }
- for (PathFragment ignoredPrefix : ignoredGlobPrefixes) {
- if (subPackagePath.startsWith(ignoredPrefix)) {
- return false;
- }
- }
+ PathFragment subPackagePath =
+ packageId.getPackageFragment().getRelative(directory.relativeTo(packageDirectory));
+
+ for (PathFragment ignoredPrefix : ignoredGlobPrefixes) {
+ if (subPackagePath.startsWith(ignoredPrefix)) {
+ return false;
+ }
+ }
- PackageIdentifier subPackageId =
- PackageIdentifier.create(packageId.getRepository(), subPackagePath);
- return locator.getBuildFileForPackage(subPackageId) == null;
- };
+ return !isSubPackage(PackageIdentifier.create(packageId.getRepository(), subPackagePath));
+ }
+
+ private boolean isSubPackage(Path directory) {
+ return isSubPackage(
+ PackageIdentifier.create(
+ packageId.getRepository(),
+ packageId.getPackageFragment().getRelative(directory.relativeTo(packageDirectory))));
+ }
+
+ private boolean isSubPackage(PackageIdentifier subPackageId) {
+ return packageLocator.getBuildFileForPackage(subPackageId) != null;
}
/**
- * Returns the future result of evaluating glob "pattern" against this
- * package's directory, using the package's cache of previously-started
- * globs if possible.
+ * Returns the future result of evaluating glob "pattern" against this package's directory, using
+ * the package's cache of previously-started globs if possible.
*
- * @return the list of paths matching the pattern, relative to the package's
- * directory.
- * @throws BadGlobException if the glob was syntactically invalid, or
- * contained uplevel references.
+ * @return the list of paths matching the pattern, relative to the package's directory.
+ * @throws BadGlobException if the glob was syntactically invalid, or contained uplevel
+ * references.
*/
- Future> getGlobUnsortedAsync(String pattern, boolean excludeDirs)
+ Future> getGlobUnsortedAsync(String pattern, Globber.Operation globberOperation)
throws BadGlobException {
- Future> cached = globCache.get(Pair.of(pattern, excludeDirs));
+ Future> cached = globCache.get(Pair.of(pattern, globberOperation));
if (cached == null) {
- if (maxDirectoriesToEagerlyVisit > -1
- && !globalStarted.getAndSet(true)) {
+ if (maxDirectoriesToEagerlyVisit > -1 && !globalStarted.getAndSet(true)) {
packageDirectory.prefetchPackageAsync(maxDirectoriesToEagerlyVisit);
}
- cached = safeGlobUnsorted(pattern, excludeDirs);
- setGlobPaths(pattern, excludeDirs, cached);
+ cached = safeGlobUnsorted(pattern, globberOperation);
+ setGlobPaths(pattern, globberOperation, cached);
}
return cached;
}
@@ -168,20 +170,20 @@ Future> getGlobUnsortedAsync(String pattern, boolean excludeDirs)
@VisibleForTesting
List getGlobUnsorted(String pattern)
throws IOException, BadGlobException, InterruptedException {
- return getGlobUnsorted(pattern, false);
+ return getGlobUnsorted(pattern, Globber.Operation.FILES_AND_DIRS);
}
@VisibleForTesting
- protected List getGlobUnsorted(String pattern, boolean excludeDirs)
+ protected List getGlobUnsorted(String pattern, Globber.Operation globberOperation)
throws IOException, BadGlobException, InterruptedException {
- Future> futureResult = getGlobUnsortedAsync(pattern, excludeDirs);
+ Future> futureResult = getGlobUnsortedAsync(pattern, globberOperation);
List globPaths = fromFuture(futureResult);
// Replace the UnixGlob.GlobFuture with a completed future object, to allow
// garbage collection of the GlobFuture and GlobVisitor objects.
if (!(futureResult instanceof SettableFuture>)) {
SettableFuture> completedFuture = SettableFuture.create();
completedFuture.set(globPaths);
- globCache.put(Pair.of(pattern, excludeDirs), completedFuture);
+ globCache.put(Pair.of(pattern, globberOperation), completedFuture);
}
List result = Lists.newArrayListWithCapacity(globPaths.size());
@@ -198,16 +200,15 @@ protected List getGlobUnsorted(String pattern, boolean excludeDirs)
}
/** Adds glob entries to the cache. */
- private void setGlobPaths(String pattern, boolean excludeDirectories, Future> result) {
- globCache.put(Pair.of(pattern, excludeDirectories), result);
+ private void setGlobPaths(
+ String pattern, Globber.Operation globberOperation, Future> result) {
+ globCache.put(Pair.of(pattern, globberOperation), result);
}
- /**
- * Actually execute a glob against the filesystem. Otherwise similar to
- * getGlob().
- */
+ /** Actually execute a glob against the filesystem. Otherwise similar to getGlob(). */
@VisibleForTesting
- Future> safeGlobUnsorted(String pattern, boolean excludeDirs) throws BadGlobException {
+ Future> safeGlobUnsorted(String pattern, Globber.Operation globberOperation)
+ throws BadGlobException {
// Forbidden patterns:
if (pattern.indexOf('?') != -1) {
throw new BadGlobException("glob pattern '" + pattern + "' contains forbidden '?' wildcard");
@@ -220,8 +221,7 @@ Future> safeGlobUnsorted(String pattern, boolean excludeDirs) throws
try {
return UnixGlob.forPath(packageDirectory)
.addPattern(pattern)
- .setExcludeDirectories(excludeDirs)
- .setDirectoryFilter(childDirectoryPredicate)
+ .setPathDiscriminator(new GlobUnixPathDiscriminator(globberOperation))
.setExecutor(globExecutor)
.setFilesystemCalls(syscalls)
.globAsync();
@@ -230,18 +230,14 @@ Future> safeGlobUnsorted(String pattern, boolean excludeDirs) throws
}
}
- /**
- * Sanitize the future exceptions - the only expected checked exception
- * is IOException.
- */
+ /** Sanitize the future exceptions - the only expected checked exception is IOException. */
private static List fromFuture(Future> future)
throws IOException, InterruptedException {
try {
return future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
- Throwables.propagateIfPossible(cause,
- IOException.class, InterruptedException.class);
+ Throwables.propagateIfPossible(cause, IOException.class, InterruptedException.class);
throw new RuntimeException(e);
}
}
@@ -253,26 +249,24 @@ private static List fromFuture(Future> future)
*
Called by PackageFactory via Package.
*/
public List globUnsorted(
- List includes, List excludes, boolean excludeDirs, boolean allowEmpty)
+ List includes,
+ List excludes,
+ Globber.Operation globberOperation,
+ boolean allowEmpty)
throws IOException, BadGlobException, InterruptedException {
// Start globbing all patterns in parallel. The getGlob() calls below will
// block on an individual pattern's results, but the other globs can
// continue in the background.
for (String pattern : includes) {
@SuppressWarnings("unused")
- Future> possiblyIgnoredError = getGlobUnsortedAsync(pattern, excludeDirs);
+ Future> possiblyIgnoredError = getGlobUnsortedAsync(pattern, globberOperation);
}
HashSet results = new HashSet<>();
for (String pattern : includes) {
- List items = getGlobUnsorted(pattern, excludeDirs);
+ List items = getGlobUnsorted(pattern, globberOperation);
if (!allowEmpty && items.isEmpty()) {
- throw new BadGlobException(
- "glob pattern '"
- + pattern
- + "' didn't match anything, but allow_empty is set to False "
- + "(the default value of allow_empty can be set with "
- + "--incompatible_disallow_empty_glob).");
+ GlobberUtils.throwBadGlobExceptionEmptyResult(pattern, globberOperation);
}
results.addAll(items);
}
@@ -282,21 +276,16 @@ public List globUnsorted(
throw new BadGlobException(ex.getMessage());
}
if (!allowEmpty && results.isEmpty()) {
- throw new BadGlobException(
- "all files in the glob have been excluded, but allow_empty is set to False "
- + "(the default value of allow_empty can be set with "
- + "--incompatible_disallow_empty_glob).");
+ GlobberUtils.throwBadGlobExceptionAllExcluded(globberOperation);
}
return new ArrayList<>(results);
}
- public Set> getKeySet() {
+ public Set> getKeySet() {
return globCache.keySet();
}
- /**
- * Block on the completion of all potentially-abandoned background tasks.
- */
+ /** Block on the completion of all potentially-abandoned background tasks. */
public void finishBackgroundTasks() {
finishBackgroundTasks(globCache.values());
}
@@ -334,4 +323,45 @@ private static void cancelBackgroundTasks(Collection>> tasks)
public String toString() {
return "GlobCache for " + packageId + " in " + packageDirectory;
}
+
+ /**
+ * Used by 'glob()' and 'subpackages()' with UnixGlob to determine if a directory should be
+ * traversed when recursing through a filesystem directory structure or include a Path in the
+ * result. This essentially filters out a set of ignored prefixes and then checks to see if a
+ * given sub-dir actually represents a sub-package or not when traversing.
+ *
+ *
The logic of including inspects the Globber.Operation to determine if it will include all
+ * files, include directories or subpackages in the output.
+ */
+ private class GlobUnixPathDiscriminator implements UnixGlobPathDiscriminator {
+ private final Globber.Operation globberOperation;
+
+ GlobUnixPathDiscriminator(Globber.Operation globberOperation) {
+ this.globberOperation = globberOperation;
+ }
+
+ @Override
+ public boolean shouldTraverseDirectory(Path directory) {
+ return globCacheShouldTraverseDirectory(directory);
+ }
+
+ @Override
+ public boolean shouldIncludePathInResult(Path path, boolean isDirectory) {
+ switch (globberOperation) {
+ case FILES_AND_DIRS:
+ return !isDirectory || !isSubPackage(path);
+ case SUBPACKAGES:
+ // no files, or root pkg
+ if (!isDirectory || path.equals(packageDirectory)) {
+ return false;
+ }
+ return isSubPackage(path);
+
+ case FILES:
+ return !isDirectory;
+ }
+ throw new IllegalStateException(
+ "Unexpected unhandled Globber.Operation enum value: " + globberOperation);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Globber.java b/src/main/java/com/google/devtools/build/lib/packages/Globber.java
index a580a47f9f7833..4bc21847eee00c 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Globber.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Globber.java
@@ -21,6 +21,19 @@ public interface Globber {
/** An opaque token for fetching the result of a glob computation. */
abstract class Token {}
+ /**
+ * Indiciates they type of globbing operations we're doing, whether looking for Files+Dirs or
+ * sub-packages.
+ */
+ enum Operation {
+ // Return only files,
+ FILES,
+ // Return files and directories, but not sub-packages
+ FILES_AND_DIRS,
+ // Return only sub-packages
+ SUBPACKAGES,
+ }
+
/** Used to indicate an invalid glob pattern. */
class BadGlobException extends Exception {
public BadGlobException(String message) {
@@ -35,7 +48,7 @@ public BadGlobException(String message) {
* invalid.
*/
Token runAsync(
- List includes, List excludes, boolean excludeDirs, boolean allowEmpty)
+ List includes, List excludes, Operation operation, boolean allowEmpty)
throws BadGlobException, InterruptedException;
/**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/GlobberUtils.java b/src/main/java/com/google/devtools/build/lib/packages/GlobberUtils.java
new file mode 100644
index 00000000000000..766cb83939d8d7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/GlobberUtils.java
@@ -0,0 +1,56 @@
+// Copyright 2022 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.packages;
+
+import com.google.errorprone.annotations.CheckReturnValue;
+
+/** Static functionality shared by different implementations of the Globber interface. */
+@CheckReturnValue
+public final class GlobberUtils {
+
+ private GlobberUtils() {}
+
+ public static void throwBadGlobExceptionEmptyResult(
+ String pattern, Globber.Operation globberOperation) throws Globber.BadGlobException {
+ switch (globberOperation) {
+ case SUBPACKAGES:
+ throw new Globber.BadGlobException(
+ "subpackages pattern '"
+ + pattern
+ + "' didn't match anything, but allow_empty is set to False (the default value)");
+ default:
+ throw new Globber.BadGlobException(
+ "glob pattern '"
+ + pattern
+ + "' didn't match anything, but allow_empty is set to False "
+ + "(the default value of allow_empty can be set with "
+ + "--incompatible_disallow_empty_glob).");
+ }
+ }
+
+ public static void throwBadGlobExceptionAllExcluded(Globber.Operation globberOperation)
+ throws Globber.BadGlobException {
+ switch (globberOperation) {
+ case SUBPACKAGES:
+ throw new Globber.BadGlobException(
+ "all subpackages in subpackages() have been excluded, but allow_empty is"
+ + " set to False ");
+ default:
+ throw new Globber.BadGlobException(
+ "all files in the glob have been excluded, but allow_empty is set to False "
+ + "(the default value of allow_empty can be set with "
+ + "--incompatible_disallow_empty_glob).");
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/NonSkyframeGlobber.java b/src/main/java/com/google/devtools/build/lib/packages/NonSkyframeGlobber.java
index 01246fc58f95cc..1a85563c40f0af 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/NonSkyframeGlobber.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/NonSkyframeGlobber.java
@@ -30,27 +30,34 @@ public class NonSkyframeGlobber implements Globber {
public static class Token extends Globber.Token {
private final List includes;
private final List excludes;
- private final boolean excludeDirs;
+ private final Globber.Operation globberOperation;
private final boolean allowEmpty;
private Token(
- List includes, List excludes, boolean excludeDirs, boolean allowEmpty) {
+ List includes,
+ List excludes,
+ Globber.Operation globberOperation,
+ boolean allowEmpty) {
this.includes = includes;
this.excludes = excludes;
- this.excludeDirs = excludeDirs;
+ this.globberOperation = globberOperation;
this.allowEmpty = allowEmpty;
}
}
@Override
public Token runAsync(
- List includes, List excludes, boolean excludeDirs, boolean allowEmpty)
+ List includes,
+ List excludes,
+ Globber.Operation globberOperation,
+ boolean allowEmpty)
throws BadGlobException {
+
for (String pattern : includes) {
@SuppressWarnings("unused")
- Future> possiblyIgnoredError = globCache.getGlobUnsortedAsync(pattern, excludeDirs);
+ Future> possiblyIgnoredError = globCache.getGlobUnsortedAsync(pattern, globberOperation);
}
- return new Token(includes, excludes, excludeDirs, allowEmpty);
+ return new Token(includes, excludes, globberOperation, allowEmpty);
}
@Override
@@ -58,10 +65,7 @@ public List fetchUnsorted(Globber.Token token)
throws BadGlobException, IOException, InterruptedException {
Token ourToken = (Token) token;
return globCache.globUnsorted(
- ourToken.includes,
- ourToken.excludes,
- ourToken.excludeDirs,
- ourToken.allowEmpty);
+ ourToken.includes, ourToken.excludes, ourToken.globberOperation, ourToken.allowEmpty);
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index dd57fe75e31934..b0e956743c53c2 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -601,6 +601,7 @@ public void executeBuildFile(
Program buildFileProgram,
ImmutableList globs,
ImmutableList globsWithDirs,
+ ImmutableList subpackages,
ImmutableMap predeclared,
ImmutableMap loadedModules,
StarlarkSemantics starlarkSemantics,
@@ -610,8 +611,11 @@ public void executeBuildFile(
if (maxDirectoriesToEagerlyVisitInGlobbing == -2) {
try {
boolean allowEmpty = true;
- globber.runAsync(globs, ImmutableList.of(), /*excludeDirs=*/ true, allowEmpty);
- globber.runAsync(globsWithDirs, ImmutableList.of(), /*excludeDirs=*/ false, allowEmpty);
+ globber.runAsync(globs, ImmutableList.of(), Globber.Operation.FILES, allowEmpty);
+ globber.runAsync(
+ globsWithDirs, ImmutableList.of(), Globber.Operation.FILES_AND_DIRS, allowEmpty);
+ globber.runAsync(
+ subpackages, ImmutableList.of(), Globber.Operation.SUBPACKAGES, allowEmpty);
} catch (BadGlobException ex) {
logger.atWarning().withCause(ex).log(
"Suppressing exception for globs=%s, globsWithDirs=%s", globs, globsWithDirs);
@@ -733,6 +737,7 @@ public static boolean checkBuildSyntax(
StarlarkFile file,
Collection globs,
Collection globsWithDirs,
+ Collection subpackages,
Map generatorNameByLocation,
Consumer errors) {
final boolean[] success = {true};
@@ -746,11 +751,16 @@ void error(Location loc, String message) {
// Extract literal glob patterns from calls of the form:
// glob(include = ["pattern"])
// glob(["pattern"])
- // This may spuriously match user-defined functions named glob;
- // that's ok, it's only a heuristic.
+ // subpackages(include = ["pattern"])
+ // This may spuriously match user-defined functions named glob or
+ // subpackages; that's ok, it's only a heuristic.
void extractGlobPatterns(CallExpression call) {
- if (call.getFunction() instanceof Identifier
- && ((Identifier) call.getFunction()).getName().equals("glob")) {
+ if (call.getFunction() instanceof Identifier) {
+ String functionName = ((Identifier) call.getFunction()).getName();
+ if (!functionName.equals("glob") && !functionName.equals("subpackages")) {
+ return;
+ }
+
Expression excludeDirectories = null, include = null;
List arguments = call.getArguments();
for (int i = 0; i < arguments.size(); i++) {
@@ -778,7 +788,11 @@ void extractGlobPatterns(CallExpression call) {
exclude = false;
}
}
- (exclude ? globs : globsWithDirs).add(pattern);
+ if (functionName.equals("glob")) {
+ (exclude ? globs : globsWithDirs).add(pattern);
+ } else {
+ subpackages.add(pattern);
+ }
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeModule.java
index b18598b1c8c88e..916f312351c6df 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeModule.java
@@ -99,8 +99,9 @@ public Sequence> glob(
List includes = Type.STRING_LIST.convert(include, "'glob' argument");
List excludes = Type.STRING_LIST.convert(exclude, "'glob' argument");
+ Globber.Operation op =
+ excludeDirs.signum() != 0 ? Globber.Operation.FILES : Globber.Operation.FILES_AND_DIRS;
- List matches;
boolean allowEmpty;
if (allowEmptyArgument == Starlark.UNBOUND) {
allowEmpty =
@@ -112,36 +113,7 @@ public Sequence> glob(
"expected boolean for argument `allow_empty`, got `%s`", allowEmptyArgument);
}
- try {
- Globber.Token globToken =
- context.globber.runAsync(includes, excludes, excludeDirs.signum() != 0, allowEmpty);
- matches = context.globber.fetchUnsorted(globToken);
- } catch (IOException e) {
- logger.atWarning().withCause(e).log(
- "Exception processing includes=%s, excludes=%s)", includes, excludes);
- String errorMessage =
- String.format(
- "error globbing [%s]%s: %s",
- Joiner.on(", ").join(includes),
- excludes.isEmpty() ? "" : " - [" + Joiner.on(", ").join(excludes) + "]",
- e.getMessage());
- Location loc = thread.getCallerLocation();
- Event error =
- Package.error(
- loc,
- errorMessage,
- // If there are other IOExceptions that can result from user error, they should be
- // tested for here. Currently FileNotFoundException is not one of those, because globs
- // only encounter that error in the presence of an inconsistent filesystem.
- e instanceof FileSymlinkException
- ? Code.EVAL_GLOBS_SYMLINK_ERROR
- : Code.GLOB_IO_EXCEPTION);
- context.eventHandler.handle(error);
- context.pkgBuilder.setIOException(e, errorMessage, error.getProperty(DetailedExitCode.class));
- matches = ImmutableList.of();
- } catch (BadGlobException e) {
- throw new EvalException(e);
- }
+ List matches = runGlobOperation(context, thread, includes, excludes, op, allowEmpty);
ArrayList result = new ArrayList<>(matches.size());
for (String match : matches) {
@@ -750,4 +722,62 @@ private static class NotRepresentableException extends EvalException {
super(msg);
}
}
+
+ @Override
+ public Sequence> subpackages(
+ Sequence> include, Sequence> exclude, boolean allowEmpty, StarlarkThread thread)
+ throws EvalException, InterruptedException {
+ BazelStarlarkContext.from(thread).checkLoadingPhase("native.subpackages");
+ PackageContext context = getContext(thread);
+
+ List includes = Type.STRING_LIST.convert(include, "'subpackages' argument");
+ List excludes = Type.STRING_LIST.convert(exclude, "'subpackages' argument");
+
+ List matches =
+ runGlobOperation(
+ context, thread, includes, excludes, Globber.Operation.SUBPACKAGES, allowEmpty);
+ matches.sort(naturalOrder());
+
+ return StarlarkList.copyOf(thread.mutability(), matches);
+ }
+
+ private List runGlobOperation(
+ PackageContext context,
+ StarlarkThread thread,
+ List includes,
+ List excludes,
+ Globber.Operation operation,
+ boolean allowEmpty)
+ throws EvalException, InterruptedException {
+ try {
+ Globber.Token globToken = context.globber.runAsync(includes, excludes, operation, allowEmpty);
+ return context.globber.fetchUnsorted(globToken);
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log(
+ "Exception processing includes=%s, excludes=%s)", includes, excludes);
+ String errorMessage =
+ String.format(
+ "error globbing [%s]%s op=%s: %s",
+ Joiner.on(", ").join(includes),
+ excludes.isEmpty() ? "" : " - [" + Joiner.on(", ").join(excludes) + "]",
+ operation,
+ e.getMessage());
+ Location loc = thread.getCallerLocation();
+ Event error =
+ Package.error(
+ loc,
+ errorMessage,
+ // If there are other IOExceptions that can result from user error, they should be
+ // tested for here. Currently FileNotFoundException is not one of those, because globs
+ // only encounter that error in the presence of an inconsistent filesystem.
+ e instanceof FileSymlinkException
+ ? Code.EVAL_GLOBS_SYMLINK_ERROR
+ : Code.GLOB_IO_EXCEPTION);
+ context.eventHandler.handle(error);
+ context.pkgBuilder.setIOException(e, errorMessage, error.getProperty(DetailedExitCode.class));
+ return ImmutableList.of();
+ } catch (BadGlobException e) {
+ throw new EvalException(e);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index 7e7c5470e5e1fa..963956dab128fc 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -146,8 +146,9 @@ public void execute(
List globs = new ArrayList<>(); // unused
if (PackageFactory.checkBuildSyntax(
file,
- globs,
- globs,
+ /*globs=*/ globs,
+ /*globsWithDirs=*/ globs,
+ /*subpackages=*/ globs,
new HashMap<>(),
error ->
localReporter.handle(
diff --git a/src/main/java/com/google/devtools/build/lib/query2/BUILD b/src/main/java/com/google/devtools/build/lib/query2/BUILD
index 8e8486c08bf7ea..ed7f359e92b7af 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD
@@ -59,6 +59,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/graph",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
"//src/main/java/com/google/devtools/build/lib/packages:exec_group",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
"//src/main/java/com/google/devtools/build/lib/pkgcache:QueryTransitivePackagePreloader",
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/BUILD b/src/main/java/com/google/devtools/build/lib/runtime/commands/BUILD
index dd2f4c8257acc4..2a382fdd065d7b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/BUILD
@@ -56,6 +56,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/exec:test_policy",
"//src/main/java/com/google/devtools/build/lib/exec/local:options",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
"//src/main/java/com/google/devtools/build/lib/profiler",
"//src/main/java/com/google/devtools/build/lib/profiler:profiler-output",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 8fc7732933dc0b..468b1290e45e5b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -293,6 +293,8 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/packages:exec_group",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber_utils",
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
"//src/main/java/com/google/devtools/build/lib/pkgcache:QueryTransitivePackagePreloader",
@@ -1441,6 +1443,7 @@ java_library(
":sky_functions",
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/concurrent",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
"//src/main/java/com/google/devtools/build/lib/util:string",
"//src/main/java/com/google/devtools/build/lib/vfs",
@@ -1465,6 +1468,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_exception",
"//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
"//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/build/skyframe",
@@ -1482,6 +1486,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
"//src/main/java/com/google/devtools/build/lib/concurrent",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobDescriptor.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobDescriptor.java
index f64d451c9e90d9..a436090f9841f5 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobDescriptor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobDescriptor.java
@@ -18,6 +18,7 @@
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.StringCanonicalizer;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -46,7 +47,7 @@ public final class GlobDescriptor implements SkyKey {
* @param subdir the subdirectory being looked at (must exist and must be a directory. It's
* assumed that there are no other packages between {@code packageName} and {@code subdir}.
* @param pattern a valid glob pattern
- * @param excludeDirs true if directories should be excluded from results
+ * @param globberOperation type of Globber operation being tracked.
*/
@AutoCodec.Instantiator
public static GlobDescriptor create(
@@ -54,36 +55,35 @@ public static GlobDescriptor create(
Root packageRoot,
PathFragment subdir,
String pattern,
- boolean excludeDirs) {
+ Globber.Operation globberOperation) {
return interner.intern(
- new GlobDescriptor(packageId, packageRoot, subdir, pattern, excludeDirs));
-
+ new GlobDescriptor(packageId, packageRoot, subdir, pattern, globberOperation));
}
private final PackageIdentifier packageId;
private final Root packageRoot;
private final PathFragment subdir;
private final String pattern;
- private final boolean excludeDirs;
+ private final Globber.Operation globberOperation;
private GlobDescriptor(
PackageIdentifier packageId,
Root packageRoot,
PathFragment subdir,
String pattern,
- boolean excludeDirs) {
+ Globber.Operation globberOperation) {
this.packageId = Preconditions.checkNotNull(packageId);
this.packageRoot = Preconditions.checkNotNull(packageRoot);
this.subdir = Preconditions.checkNotNull(subdir);
this.pattern = Preconditions.checkNotNull(StringCanonicalizer.intern(pattern));
- this.excludeDirs = excludeDirs;
+ this.globberOperation = globberOperation;
}
@Override
public String toString() {
return String.format(
- "",
- packageId, packageRoot, subdir, pattern, excludeDirs);
+ "",
+ packageId, packageRoot, subdir, pattern, globberOperation.name());
}
/**
@@ -117,11 +117,9 @@ public String getPattern() {
return pattern;
}
- /**
- * Returns true if directories should be excluded from results.
- */
- public boolean excludeDirs() {
- return excludeDirs;
+ /** Returns the type of Globber operation that produced the results. */
+ public Globber.Operation globberOperation() {
+ return globberOperation;
}
@Override
@@ -133,9 +131,11 @@ public boolean equals(Object obj) {
return false;
}
GlobDescriptor other = (GlobDescriptor) obj;
- return packageId.equals(other.packageId) && packageRoot.equals(other.packageRoot)
- && subdir.equals(other.subdir) && pattern.equals(other.pattern)
- && excludeDirs == other.excludeDirs;
+ return packageId.equals(other.packageId)
+ && packageRoot.equals(other.packageRoot)
+ && subdir.equals(other.subdir)
+ && pattern.equals(other.pattern)
+ && globberOperation == other.globberOperation;
}
@Override
@@ -143,7 +143,7 @@ public int hashCode() {
// Generated instead of Objects.hashCode to avoid intermediate array required for latter.
final int prime = 31;
int result = 1;
- result = prime * result + (excludeDirs ? 1231 : 1237);
+ result = prime * result + globberOperation.hashCode();
result = prime * result + packageId.hashCode();
result = prime * result + packageRoot.hashCode();
result = prime * result + pattern.hashCode();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
index 9f3c88ed9d3c9c..ee4b2b16d3dc6c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
@@ -25,6 +25,7 @@
import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
import com.google.devtools.build.lib.io.InconsistentFilesystemException;
+import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
@@ -58,6 +59,7 @@ public GlobFunction(boolean alwaysUseDirListing) {
public SkyValue compute(SkyKey skyKey, Environment env)
throws GlobFunctionException, InterruptedException {
GlobDescriptor glob = (GlobDescriptor) skyKey.argument();
+ Globber.Operation globberOperation = glob.globberOperation();
RepositoryName repositoryName = glob.getPackageId().getRepository();
IgnoredPackagePrefixesValue ignoredPackagePrefixes =
@@ -78,19 +80,32 @@ public SkyValue compute(SkyKey skyKey, Environment env)
// Note that the glob's package is assumed to exist which implies that the package's BUILD file
// exists which implies that the package's directory exists.
if (!globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) {
+ PathFragment subDirFragment =
+ glob.getPackageId().getPackageFragment().getRelative(globSubdir);
+
PackageLookupValue globSubdirPkgLookupValue =
(PackageLookupValue)
env.getValue(
- PackageLookupValue.key(
- PackageIdentifier.create(
- repositoryName,
- glob.getPackageId().getPackageFragment().getRelative(globSubdir))));
+ PackageLookupValue.key(PackageIdentifier.create(repositoryName, subDirFragment)));
if (globSubdirPkgLookupValue == null) {
return null;
}
+
if (globSubdirPkgLookupValue.packageExists()) {
// We crossed the package boundary, that is, pkg/subdir contains a BUILD file and thus
- // defines another package, so glob expansion should not descend into that subdir.
+ // defines another package, so glob expansion should not descend into
+ // that subdir.
+ //
+ // For SUBPACKAGES, we encounter this when the pattern is a recursive ** and we are a
+ // terminal package for that pattern. In that case we should include the subDirFragment
+ // PathFragment (relative to the glob's package) in the GlobValue.getMatches,
+ // otherwise for file/dir matching return EMPTY;
+ if (globberOperation == Globber.Operation.SUBPACKAGES) {
+ return new GlobValue(
+ NestedSetBuilder.stableOrder()
+ .add(subDirFragment.relativeTo(glob.getPackageId().getPackageFragment()))
+ .build());
+ }
return GlobValue.EMPTY;
} else if (globSubdirPkgLookupValue
instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) {
@@ -117,7 +132,6 @@ public SkyValue compute(SkyKey skyKey, Environment env)
boolean globMatchesBareFile = patternTail == null;
-
RootedPath dirRootedPath = RootedPath.toRootedPath(glob.getPackageRoot(), dirPathFragment);
if (alwaysUseDirListing || containsGlobs(patternHead)) {
// Pattern contains globs, so a directory listing is required.
@@ -135,7 +149,8 @@ public SkyValue compute(SkyKey skyKey, Environment env)
// "**" also matches an empty segment, so try the case where it is not present.
if (globMatchesBareFile) {
// Recursive globs aren't supposed to match the package's directory.
- if (!glob.excludeDirs() && !globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) {
+ if (globberOperation == Globber.Operation.FILES_AND_DIRS
+ && !globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) {
matches.add(globSubdir);
}
} else {
@@ -148,7 +163,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
glob.getPackageRoot(),
globSubdir,
patternTail,
- glob.excludeDirs());
+ globberOperation);
Map listingAndRecursiveGlobMap =
env.getValues(
ImmutableList.of(keyForRecursiveGlobInCurrentDirectory, directoryListingKey));
@@ -209,7 +224,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
if (keyToRequest != null) {
subdirMap.put(keyToRequest, dirent);
}
- } else if (globMatchesBareFile) {
+ } else if (globMatchesBareFile && globberOperation != Globber.Operation.SUBPACKAGES) {
sortedResultMap.put(dirent, glob.getSubdir().getRelative(fileName));
}
}
@@ -266,7 +281,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
if (keyToRequest != null) {
symlinkSubdirMap.put(keyToRequest, dirent);
}
- } else if (globMatchesBareFile) {
+ } else if (globMatchesBareFile && globberOperation != Globber.Operation.SUBPACKAGES) {
sortedResultMap.put(dirent, glob.getSubdir().getRelative(fileName));
}
} else {
@@ -308,7 +323,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
addToMatches(fileMatches, matches);
}
}
- } else if (globMatchesBareFile) {
+ } else if (globMatchesBareFile && globberOperation != Globber.Operation.SUBPACKAGES) {
matches.add(glob.getSubdir().getRelative(fileName));
}
}
@@ -346,9 +361,10 @@ private static boolean containsGlobs(String pattern) {
private static void addToMatches(Object toAdd, NestedSetBuilder matches) {
if (toAdd instanceof PathFragment) {
matches.add((PathFragment) toAdd);
- } else {
+ } else if (toAdd instanceof NestedSet) {
matches.addTransitive((NestedSet) toAdd);
}
+ // else Not actually a valid type and ignore.
}
/**
@@ -365,17 +381,19 @@ private static void addToMatches(Object toAdd, NestedSetBuilder ma
private static SkyKey getSkyKeyForSubdir(
String fileName, GlobDescriptor glob, String subdirPattern) {
if (subdirPattern == null) {
- if (glob.excludeDirs()) {
+ if (glob.globberOperation() == Globber.Operation.FILES) {
return null;
- } else {
- return PackageLookupValue.key(
- PackageIdentifier.create(
- glob.getPackageId().getRepository(),
- glob.getPackageId()
- .getPackageFragment()
- .getRelative(glob.getSubdir())
- .getRelative(fileName)));
}
+
+ // For FILES_AND_DIRS and SUBPACKAGES we want to maybe inspect a
+ // PackageLookupValue for it.
+ return PackageLookupValue.key(
+ PackageIdentifier.create(
+ glob.getPackageId().getRepository(),
+ glob.getPackageId()
+ .getPackageFragment()
+ .getRelative(glob.getSubdir())
+ .getRelative(fileName)));
} else {
// There is some more pattern to match. Get the glob for the subdirectory. Note that this
// directory may also match directly in the case of a pattern that starts with "**", but that
@@ -385,43 +403,60 @@ private static SkyKey getSkyKeyForSubdir(
glob.getPackageRoot(),
glob.getSubdir().getRelative(fileName),
subdirPattern,
- glob.excludeDirs());
+ glob.globberOperation());
}
}
/**
- * Returns matches coming from the directory {@code fileName} if appropriate, either an individual
- * file or a nested set of files.
+ * Returns an Object indicating a match was found for the given fileName in the given
+ * valueRequested. The Object will be one of:
*
- *
{@code valueRequested} must be the SkyValue whose key was returned by
- * {@link #getSkyKeyForSubdir} for these parameters.
+ *
+ *
{@code null} if no matches for the given parameters exists
+ *
{@code NestedSet} if a match exists, either because we are looking for
+ * files/directories or the SkyValue is a package and we're globbing for {@link
+ * Globber.Operation.SUBPACKAGES}
+ *
+ *
+ *
{@code valueRequested} must be the SkyValue whose key was returned by {@link
+ * #getSkyKeyForSubdir} for these parameters.
*/
@Nullable
private static Object getSubdirMatchesFromSkyValue(
- String fileName,
- GlobDescriptor glob,
- SkyValue valueRequested) {
+ String fileName, GlobDescriptor glob, SkyValue valueRequested) {
if (valueRequested instanceof GlobValue) {
return ((GlobValue) valueRequested).getMatches();
- } else {
- Preconditions.checkState(
- valueRequested instanceof PackageLookupValue,
- "%s is not a GlobValue or PackageLookupValue (%s %s)",
- valueRequested,
- fileName,
- glob);
- PackageLookupValue packageLookupValue = (PackageLookupValue) valueRequested;
- if (packageLookupValue.packageExists()) {
- // This is a separate package, so ignore it.
- return null;
- } else if (packageLookupValue
- instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) {
- // This is a separate repository, so ignore it.
- return null;
- } else {
- return glob.getSubdir().getRelative(fileName);
- }
}
+
+ Preconditions.checkState(
+ valueRequested instanceof PackageLookupValue,
+ "%s is not a GlobValue or PackageLookupValue (%s %s)",
+ valueRequested,
+ fileName,
+ glob);
+
+ PackageLookupValue packageLookupValue = (PackageLookupValue) valueRequested;
+ if (packageLookupValue
+ instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) {
+ // This is a separate repository, so ignore it.
+ return null;
+ }
+
+ boolean isSubpackagesOp = glob.globberOperation() == Globber.Operation.SUBPACKAGES;
+ boolean pkgExists = packageLookupValue.packageExists();
+
+ if (!isSubpackagesOp && pkgExists) {
+ // We're in our repo and fileName is a package. Since we're not doing SUBPACKAGES listing, we
+ // do not want to add it to the results.
+ return null;
+ } else if (isSubpackagesOp && !pkgExists) {
+ // We're in our repo and the package exists. Since we're doing SUBPACKAGES listing, we do
+ // want to add fileName to the results.
+ return null;
+ }
+
+ // The fileName should be added to the results of the glob.
+ return glob.getSubdir().getRelative(fileName);
}
@Nullable
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java
index bcc89fc82ff131..bc7900ab11d475 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobValue.java
@@ -20,6 +20,7 @@
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.UnixGlob;
@@ -84,7 +85,7 @@ public static GlobDescriptor key(
PackageIdentifier packageId,
Root packageRoot,
String pattern,
- boolean excludeDirs,
+ Globber.Operation globOperation,
PathFragment subdir)
throws InvalidGlobPatternException {
if (pattern.indexOf('?') != -1) {
@@ -96,7 +97,7 @@ public static GlobDescriptor key(
throw new InvalidGlobPatternException(pattern, error);
}
- return internalKey(packageId, packageRoot, subdir, pattern, excludeDirs);
+ return internalKey(packageId, packageRoot, subdir, pattern, globOperation);
}
/**
@@ -110,8 +111,8 @@ static GlobDescriptor internalKey(
Root packageRoot,
PathFragment subdir,
String pattern,
- boolean excludeDirs) {
- return GlobDescriptor.create(packageId, packageRoot, subdir, pattern, excludeDirs);
+ Globber.Operation globOperation) {
+ return GlobDescriptor.create(packageId, packageRoot, subdir, pattern, globOperation);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index b4e1bac183985a..5a27d6179db333 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -43,6 +43,7 @@
import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
import com.google.devtools.build.lib.packages.CachingPackageLocator;
import com.google.devtools.build.lib.packages.Globber;
+import com.google.devtools.build.lib.packages.GlobberUtils;
import com.google.devtools.build.lib.packages.InvalidPackageNameException;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NonSkyframeGlobber;
@@ -139,6 +140,7 @@ public static class CompiledBuildFile {
@Nullable private final Program prog;
@Nullable private final ImmutableList globs;
@Nullable private final ImmutableList globsWithDirs;
+ @Nullable private final ImmutableList subpackages;
@Nullable private final ImmutableMap generatorMap;
@Nullable private final ImmutableMap predeclared;
@@ -151,11 +153,13 @@ boolean ok() {
Program prog,
ImmutableList globs,
ImmutableList globsWithDirs,
+ ImmutableList subpackages,
ImmutableMap generatorMap,
ImmutableMap predeclared) {
this.errors = null;
this.prog = prog;
this.globs = globs;
+ this.subpackages = subpackages;
this.globsWithDirs = globsWithDirs;
this.generatorMap = generatorMap;
this.predeclared = predeclared;
@@ -167,6 +171,7 @@ boolean ok() {
this.prog = null;
this.globs = null;
this.globsWithDirs = null;
+ this.subpackages = null;
this.generatorMap = null;
this.predeclared = null;
}
@@ -923,9 +928,12 @@ public Set getGlobDepsRequested() {
@Override
public Token runAsync(
- List includes, List excludes, boolean excludeDirs, boolean allowEmpty)
+ List includes,
+ List excludes,
+ Globber.Operation globberOperation,
+ boolean allowEmpty)
throws BadGlobException, InterruptedException {
- return delegate.runAsync(includes, excludes, excludeDirs, allowEmpty);
+ return delegate.runAsync(includes, excludes, globberOperation, allowEmpty);
}
@Override
@@ -1025,10 +1033,11 @@ public Set getGlobDepsRequested() {
return ImmutableSet.copyOf(globDepsRequested);
}
- private SkyKey getGlobKey(String pattern, boolean excludeDirs) throws BadGlobException {
+ private SkyKey getGlobKey(String pattern, Globber.Operation globberOperation)
+ throws BadGlobException {
try {
return GlobValue.key(
- packageId, packageRoot, pattern, excludeDirs, PathFragment.EMPTY_FRAGMENT);
+ packageId, packageRoot, pattern, globberOperation, PathFragment.EMPTY_FRAGMENT);
} catch (InvalidGlobPatternException e) {
throw new BadGlobException(e.getMessage());
}
@@ -1036,13 +1045,16 @@ private SkyKey getGlobKey(String pattern, boolean excludeDirs) throws BadGlobExc
@Override
public Token runAsync(
- List includes, List excludes, boolean excludeDirs, boolean allowEmpty)
+ List includes,
+ List excludes,
+ Globber.Operation globberOperation,
+ boolean allowEmpty)
throws BadGlobException, InterruptedException {
LinkedHashSet globKeys = Sets.newLinkedHashSetWithExpectedSize(includes.size());
Map globKeyToPatternMap = Maps.newHashMapWithExpectedSize(includes.size());
for (String pattern : includes) {
- SkyKey globKey = getGlobKey(pattern, excludeDirs);
+ SkyKey globKey = getGlobKey(pattern, globberOperation);
globKeys.add(globKey);
globKeyToPatternMap.put(globKey, pattern);
}
@@ -1066,9 +1078,9 @@ public Token runAsync(
globsToDelegate.isEmpty()
? null
: nonSkyframeGlobber.runAsync(
- globsToDelegate, ImmutableList.of(), excludeDirs, allowEmpty);
+ globsToDelegate, ImmutableList.of(), globberOperation, allowEmpty);
return new HybridToken(
- globValueMap, globKeys, nonSkyframeIncludesToken, excludes, allowEmpty);
+ globValueMap, globKeys, nonSkyframeIncludesToken, excludes, globberOperation, allowEmpty);
}
private static Collection getMissingKeys(
@@ -1126,6 +1138,8 @@ private static class HybridToken extends Globber.Token {
private final List excludes;
+ private final Globber.Operation globberOperation;
+
private final boolean allowEmpty;
private HybridToken(
@@ -1133,11 +1147,13 @@ private HybridToken(
Iterable includesGlobKeys,
@Nullable NonSkyframeGlobber.Token nonSkyframeGlobberIncludesToken,
List excludes,
+ Globber.Operation globberOperation,
boolean allowEmpty) {
this.globValueMap = globValueMap;
this.includesGlobKeys = includesGlobKeys;
this.nonSkyframeGlobberIncludesToken = nonSkyframeGlobberIncludesToken;
this.excludes = excludes;
+ this.globberOperation = globberOperation;
this.allowEmpty = allowEmpty;
}
@@ -1152,12 +1168,8 @@ private List resolve(NonSkyframeGlobber nonSkyframeGlobber)
foundMatch = true;
}
if (!allowEmpty && !foundMatch) {
- throw new BadGlobException(
- "glob pattern '"
- + ((GlobDescriptor) includeGlobKey.argument()).getPattern()
- + "' didn't match anything, but allow_empty is set to False "
- + "(the default value of allow_empty can be set with "
- + "--incompatible_disallow_empty_glob).");
+ GlobberUtils.throwBadGlobExceptionEmptyResult(
+ ((GlobDescriptor) includeGlobKey.argument()).getPattern(), globberOperation);
}
}
if (nonSkyframeGlobberIncludesToken != null) {
@@ -1171,10 +1183,7 @@ private List resolve(NonSkyframeGlobber nonSkyframeGlobber)
List result = new ArrayList<>(matches);
if (!allowEmpty && result.isEmpty()) {
- throw new BadGlobException(
- "all files in the glob have been excluded, but allow_empty is set to False "
- + "(the default value of allow_empty can be set with "
- + "--incompatible_disallow_empty_glob).");
+ GlobberUtils.throwBadGlobExceptionAllExcluded(globberOperation);
}
return result;
}
@@ -1370,6 +1379,7 @@ private LoadedPackageCacheEntry loadPackage(
compiled.prog,
compiled.globs,
compiled.globsWithDirs,
+ compiled.subpackages,
compiled.predeclared,
loadedModules,
starlarkBuiltinsValue.starlarkSemantics,
@@ -1470,9 +1480,11 @@ private CompiledBuildFile compileBuildFile(
// - record the generator_name of each top-level macro call
Set globs = new HashSet<>();
Set globsWithDirs = new HashSet<>();
+ Set subpackages = new HashSet<>();
Map generatorMap = new HashMap<>();
ImmutableList.Builder errors = ImmutableList.builder();
- if (!PackageFactory.checkBuildSyntax(file, globs, globsWithDirs, generatorMap, errors::add)) {
+ if (!PackageFactory.checkBuildSyntax(
+ file, globs, globsWithDirs, subpackages, generatorMap, errors::add)) {
return new CompiledBuildFile(errors.build());
}
@@ -1520,6 +1532,7 @@ private CompiledBuildFile compileBuildFile(
prog,
ImmutableList.copyOf(globs),
ImmutableList.copyOf(globsWithDirs),
+ ImmutableList.copyOf(subpackages),
ImmutableMap.copyOf(generatorMap),
ImmutableMap.copyOf(predeclared));
}
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java
index 4776b8d5427249..efdb8e57a86e0a 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java
@@ -237,4 +237,41 @@ NoneType exportsFiles(Sequence> srcs, Object visibility, Object licenses, Star
+ "REPOSITORY_NAME.",
useStarlarkThread = true)
String repositoryName(StarlarkThread thread) throws EvalException;
+
+ @StarlarkMethod(
+ name = "subpackages",
+ doc =
+ "Returns a new mutable list of every direct subpackage of the current package,"
+ + " regardless of file-system directory depth. List returned is sorted and contains"
+ + " the names of subpackages relative to the current package. It is advised to"
+ + " prefer using the methods in bazel_skylib.subpackages module rather than calling"
+ + " this function directly.",
+ parameters = {
+ @Param(
+ name = "include",
+ allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
+ positional = false,
+ named = true,
+ doc = "The list of glob patterns to include in subpackages scan."),
+ @Param(
+ name = "exclude",
+ allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
+ defaultValue = "[]",
+ positional = false,
+ named = true,
+ doc = "The list of glob patterns to exclude from subpackages scan."),
+ @Param(
+ name = "allow_empty",
+ defaultValue = "False",
+ positional = false,
+ named = true,
+ doc =
+ "Whether we fail if the call returns an empty list. By default empty list indicates"
+ + " potential error in BUILD file where the call to subpackages() is"
+ + " superflous. Setting to true allows this function to succeed in that case.")
+ },
+ useStarlarkThread = true)
+ Sequence> subpackages(
+ Sequence> include, Sequence> exclude, boolean allowEmpty, StarlarkThread thread)
+ throws EvalException, InterruptedException;
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
index 23ab5ab5e0512d..92f1c331c0b78b 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
@@ -17,8 +17,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
@@ -60,6 +58,9 @@
*
Importantly, note that the glob matches are in an unspecified order.
*/
public final class UnixGlob {
+ private static final UnixGlobPathDiscriminator DEFAULT_DISCRIMINATOR =
+ new UnixGlobPathDiscriminator() {};
+
private UnixGlob() {}
/** Indicates an invalid glob pattern. */
@@ -72,51 +73,46 @@ private BadPattern(String message) {
private static List globInternal(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls,
Executor executor)
throws IOException, InterruptedException, BadPattern {
GlobVisitor visitor = new GlobVisitor(executor);
- return visitor.glob(base, patterns, excludeDirectories, dirPred, syscalls);
+ return visitor.glob(base, patterns, pathDiscriminator, syscalls);
}
private static List globInternalUninterruptible(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls,
Executor executor)
throws IOException, BadPattern {
GlobVisitor visitor = new GlobVisitor(executor);
- return visitor.globUninterruptible(base, patterns, excludeDirectories, dirPred, syscalls);
+ return visitor.globUninterruptible(base, patterns, pathDiscriminator, syscalls);
}
private static long globInternalAndReturnNumGlobTasksForTesting(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls,
Executor executor)
throws IOException, InterruptedException, BadPattern {
GlobVisitor visitor = new GlobVisitor(executor);
- visitor.glob(base, patterns, excludeDirectories, dirPred, syscalls);
+ visitor.glob(base, patterns, pathDiscriminator, syscalls);
return visitor.getNumGlobTasksForTesting();
}
private static Future> globAsyncInternal(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls,
Executor executor)
throws BadPattern {
Preconditions.checkNotNull(executor, "%s %s", base, patterns);
- return new GlobVisitor(executor)
- .globAsync(base, patterns, excludeDirectories, dirPred, syscalls);
+ return new GlobVisitor(executor).globAsync(base, patterns, pathDiscriminator, syscalls);
}
/**
@@ -345,8 +341,7 @@ public static Builder forPath(Path path) {
public static class Builder {
private Path base;
private List patterns;
- private boolean excludeDirectories;
- private Predicate pathFilter;
+ private UnixGlobPathDiscriminator pathDiscriminator = DEFAULT_DISCRIMINATOR;
private Executor executor;
private AtomicReference extends FilesystemCalls> syscalls =
new AtomicReference<>(DEFAULT_SYSCALLS);
@@ -357,8 +352,6 @@ public static class Builder {
public Builder(Path base) {
this.base = base;
this.patterns = Lists.newArrayList();
- this.excludeDirectories = false;
- this.pathFilter = Predicates.alwaysTrue();
}
/**
@@ -401,14 +394,6 @@ public Builder setFilesystemCalls(AtomicReference extends FilesystemCalls> sys
return this;
}
- /**
- * If set to true, directories are not returned in the glob result.
- */
- public Builder setExcludeDirectories(boolean excludeDirectories) {
- this.excludeDirectories = excludeDirectories;
- return this;
- }
-
/**
* Sets the executor to use for parallel glob evaluation. If unset, evaluation is done
* in-thread.
@@ -418,21 +403,27 @@ public Builder setExecutor(Executor pool) {
return this;
}
-
/**
- * If set, the given predicate is called for every directory
- * encountered. If it returns false, the corresponding item is not
- * returned in the output and directories are not traversed either.
+ * Sets the UnixGlobPathDiscriminator which determines how to handle Path entries encountered
+ * during glob traversal. The interface determines if Paths should be added to the {@code
+ * List} results and whether to traverse a given directory during recursion.
+ *
+ *
The UnixGlobPathDiscriminator should only be called with Paths that have been resolved to
+ * a regular file or regular directory, it will not properly handle symlinks or special files.
+ *
+ *
This is used for handling the previous use case of 'excludeDirectories' where we wish to
+ * exclude files from the glob and decide which directories to traverse, like skipping sub-dirs
+ * containing BUILD files.
*/
- public Builder setDirectoryFilter(Predicate pathFilter) {
- this.pathFilter = pathFilter;
+ public Builder setPathDiscriminator(UnixGlobPathDiscriminator pathDiscriminator) {
+ this.pathDiscriminator = pathDiscriminator;
return this;
}
/** Executes the glob. */
public List glob() throws IOException, BadPattern {
return globInternalUninterruptible(
- base, patterns, excludeDirectories, pathFilter, syscalls.get(), executor);
+ base, patterns, pathDiscriminator, syscalls.get(), executor);
}
/**
@@ -441,14 +432,14 @@ public List glob() throws IOException, BadPattern {
* @throws InterruptedException if the thread is interrupted.
*/
public List globInterruptible() throws IOException, InterruptedException, BadPattern {
- return globInternal(base, patterns, excludeDirectories, pathFilter, syscalls.get(), executor);
+ return globInternal(base, patterns, pathDiscriminator, syscalls.get(), executor);
}
@VisibleForTesting
public long globInterruptibleAndReturnNumGlobTasksForTesting()
throws IOException, InterruptedException, BadPattern {
return globInternalAndReturnNumGlobTasksForTesting(
- base, patterns, excludeDirectories, pathFilter, syscalls.get(), executor);
+ base, patterns, pathDiscriminator, syscalls.get(), executor);
}
/**
@@ -456,8 +447,7 @@ public long globInterruptibleAndReturnNumGlobTasksForTesting()
* non-null argument.
*/
public Future> globAsync() throws BadPattern {
- return globAsyncInternal(
- base, patterns, excludeDirectories, pathFilter, syscalls.get(), executor);
+ return globAsyncInternal(base, patterns, pathDiscriminator, syscalls.get(), executor);
}
}
@@ -522,9 +512,11 @@ private static final class GlobVisitor {
/**
* Performs wildcard globbing: returns the list of filenames that match any of {@code patterns}
- * relative to {@code base}. Directories are traversed if and only if they match {@code
- * dirPred}. The predicate is also called for the root of the traversal. The order of the
- * returned list is unspecified.
+ * relative to {@code base}. Directories are traversed if and only if they return true from
+ * {@code pathDiscriminator.shouldTraverseDirectory}. The predicate is also called for the root
+ * of the traversal. {@code pathDiscriminator.shouldIncludePathInResult} is called to determine
+ * if a directory result should be included in the output. The The order of the returned list is
+ * unspecified.
*
*
Patterns may include "*" and "?", but not "[a-z]".
*
@@ -538,12 +530,11 @@ private static final class GlobVisitor {
List glob(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls)
throws IOException, InterruptedException, BadPattern {
try {
- return globAsync(base, patterns, excludeDirectories, dirPred, syscalls).get();
+ return globAsync(base, patterns, pathDiscriminator, syscalls).get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
Throwables.propagateIfPossible(cause, IOException.class);
@@ -554,13 +545,12 @@ List glob(
List globUninterruptible(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls)
throws IOException, BadPattern {
try {
return Uninterruptibles.getUninterruptibly(
- globAsync(base, patterns, excludeDirectories, dirPred, syscalls));
+ globAsync(base, patterns, pathDiscriminator, syscalls));
} catch (ExecutionException e) {
Throwable cause = e.getCause();
Throwables.propagateIfPossible(cause, IOException.class);
@@ -580,8 +570,7 @@ private static boolean isRecursivePattern(String pattern) {
Future> globAsync(
Path base,
Collection patterns,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls)
throws BadPattern {
FileStatus baseStat;
@@ -610,9 +599,10 @@ Future> globAsync(
++numRecursivePatterns;
}
}
- GlobTaskContext context = numRecursivePatterns > 1
- ? new RecursiveGlobTaskContext(splitPattern, excludeDirectories, dirPred, syscalls)
- : new GlobTaskContext(splitPattern, excludeDirectories, dirPred, syscalls);
+ GlobTaskContext context =
+ numRecursivePatterns > 1
+ ? new RecursiveGlobTaskContext(splitPattern, pathDiscriminator, syscalls)
+ : new GlobTaskContext(splitPattern, pathDiscriminator, syscalls);
context.queueGlob(base, baseStat.isDirectory(), 0);
}
} finally {
@@ -657,10 +647,9 @@ public void run() {
@Override
public String toString() {
return String.format(
- "%s glob(include=[%s], exclude_directories=%s)",
+ "%s glob(include=[%s])",
base.getPathString(),
- "\"" + Joiner.on("\", \"").join(context.patternParts) + "\"",
- context.excludeDirectories);
+ "\"" + Joiner.on("\", \"").join(context.patternParts) + "\"");
}
});
}
@@ -720,18 +709,15 @@ private void decrementAndCheckDone() {
/** A context for evaluating all the subtasks of a single top-level glob task. */
private class GlobTaskContext {
private final String[] patternParts;
- private final boolean excludeDirectories;
- private final Predicate dirPred;
+ private final UnixGlobPathDiscriminator pathDiscriminator;
private final FilesystemCalls syscalls;
GlobTaskContext(
String[] patternParts,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls) {
this.patternParts = patternParts;
- this.excludeDirectories = excludeDirectories;
- this.dirPred = dirPred;
+ this.pathDiscriminator = pathDiscriminator;
this.syscalls = syscalls;
}
@@ -779,10 +765,9 @@ public int hashCode() {
private RecursiveGlobTaskContext(
String[] patternParts,
- boolean excludeDirectories,
- Predicate dirPred,
+ UnixGlobPathDiscriminator pathDiscriminator,
FilesystemCalls syscalls) {
- super(patternParts, excludeDirectories, dirPred, syscalls);
+ super(patternParts, pathDiscriminator, syscalls);
}
@Override
@@ -811,14 +796,14 @@ protected void queueGlob(Path base, boolean baseIsDir, int patternIdx) {
*/
private void reallyGlob(Path base, boolean baseIsDir, int idx, GlobTaskContext context)
throws IOException {
- if (baseIsDir && !context.dirPred.apply(base)) {
+
+ if (baseIsDir && !context.pathDiscriminator.shouldTraverseDirectory(base)) {
+ maybeAddResult(context, base, baseIsDir);
return;
}
if (idx == context.patternParts.length) { // Base case.
- if (!(context.excludeDirectories && baseIsDir)) {
- results.add(base);
- }
+ maybeAddResult(context, base, baseIsDir);
return;
}
@@ -868,6 +853,12 @@ private void reallyGlob(Path base, boolean baseIsDir, int idx, GlobTaskContext c
}
}
+ private void maybeAddResult(GlobTaskContext context, Path base, boolean isDirectory) {
+ if (context.pathDiscriminator.shouldIncludePathInResult(base, isDirectory)) {
+ results.add(base);
+ }
+ }
+
/**
* Process symlinks asynchronously. If we should used readdir(..., Symlinks.FOLLOW), that would
* result in a sequential symlink resolution with many file system implementations. If the
@@ -894,7 +885,7 @@ private void processFileOrDirectory(
if (isDir) {
context.queueGlob(path, /* baseIsDir= */ true, idx + (isRecursivePattern ? 0 : 1));
} else if (idx + 1 == context.patternParts.length) {
- results.add(path);
+ maybeAddResult(context, path, /* isDirectory= */ false);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlobPathDiscriminator.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlobPathDiscriminator.java
new file mode 100644
index 00000000000000..04e49b00c10195
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlobPathDiscriminator.java
@@ -0,0 +1,41 @@
+// Copyright 2022 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.vfs;
+
+import com.google.errorprone.annotations.CheckReturnValue;
+
+/**
+ * Interface that provides details on how UnixGlob should discriminate between different Paths.
+ * Instances of this interface will be handed either real directories or real files after symlink
+ * resolution and excluding special files.
+ */
+@CheckReturnValue
+public interface UnixGlobPathDiscriminator {
+
+ /**
+ * Determine if UnixGlob should enumerate entries in this directory and traverse it during
+ * recursive globbing. Defaults to true.
+ */
+ default boolean shouldTraverseDirectory(Path path) {
+ return true;
+ }
+
+ /**
+ * Determine if UnixGlob should include the given path in a {@code List} result. Defaults to
+ * true;
+ */
+ default boolean shouldIncludePathInResult(Path path, boolean isDirectory) {
+ return true;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkNativeModuleApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkNativeModuleApi.java
index c22d07c2dec750..b4fd592582fc5f 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkNativeModuleApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkNativeModuleApi.java
@@ -77,6 +77,13 @@ public String repositoryName(StarlarkThread thread) {
return "";
}
+ @Override
+ public Sequence> subpackages(
+ Sequence> include, Sequence> exclude, boolean allowEmpty, StarlarkThread thread)
+ throws EvalException, InterruptedException {
+ return StarlarkList.of(thread.mutability());
+ }
+
@Nullable
@Override
public Object getValue(String name) throws EvalException {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BUILD b/src/test/java/com/google/devtools/build/lib/analysis/BUILD
index 267d082f764627..83cb5eb8349923 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BUILD
@@ -122,6 +122,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/exec:bin_tools",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
"//src/main/java/com/google/devtools/build/lib/packages:exec_group",
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD b/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
index 65c8b002629696..b6a36d5d9c5a85 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
@@ -90,6 +90,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/exec:execution_options",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
"//src/main/java/com/google/devtools/build/lib/rules:repository/local_repository_rule",
diff --git a/src/test/java/com/google/devtools/build/lib/packages/BUILD b/src/test/java/com/google/devtools/build/lib/packages/BUILD
index 61ef029a9d0bf0..b19ea2294f78e8 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/packages/BUILD
@@ -69,9 +69,12 @@ java_test(
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/exec:test_policy",
"//src/main/java/com/google/devtools/build/lib/packages",
+ "//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
"//src/main/java/com/google/devtools/build/lib/packages:exec_group",
+ "//src/main/java/com/google/devtools/build/lib/packages:globber",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
"//src/main/java/com/google/devtools/build/lib/runtime/commands",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
"//src/main/java/com/google/devtools/build/lib/skyframe:tests_for_target_pattern_value",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
"//src/main/java/com/google/devtools/build/lib/util",
diff --git a/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java b/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java
index f1b693fe7aceac..93e2d42ddfa660 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java
@@ -131,7 +131,7 @@ public final void deleteFiles() throws Exception {
@Test
public void testIgnoredDirectory() throws Exception {
createCache(PathFragment.create("isolated/foo"));
- List paths = cache.safeGlobUnsorted("**/*.js", true).get();
+ List paths = cache.safeGlobUnsorted("**/*.js", Globber.Operation.FILES).get();
assertPathsAre(
paths,
"/workspace/isolated/first.js",
@@ -142,7 +142,7 @@ public void testIgnoredDirectory() throws Exception {
@Test
public void testSafeGlob() throws Exception {
- List paths = cache.safeGlobUnsorted("*.js", false).get();
+ List paths = cache.safeGlobUnsorted("*.js", Globber.Operation.FILES_AND_DIRS).get();
assertPathsAre(paths,
"/workspace/isolated/first.js", "/workspace/isolated/second.js");
}
@@ -150,7 +150,9 @@ public void testSafeGlob() throws Exception {
@Test
public void testSafeGlobInvalidPattern() throws Exception {
String invalidPattern = "Foo?.txt";
- assertThrows(BadGlobException.class, () -> cache.safeGlobUnsorted(invalidPattern, false).get());
+ assertThrows(
+ BadGlobException.class,
+ () -> cache.safeGlobUnsorted(invalidPattern, Globber.Operation.FILES_AND_DIRS).get());
}
@Test
@@ -170,38 +172,53 @@ public void testGetKeySet() throws Exception {
assertThat(cache.getKeySet()).isEmpty();
cache.getGlobUnsorted("*.java");
- assertThat(cache.getKeySet()).containsExactly(Pair.of("*.java", false));
+ assertThat(cache.getKeySet())
+ .containsExactly(Pair.of("*.java", Globber.Operation.FILES_AND_DIRS));
cache.getGlobUnsorted("*.java");
- assertThat(cache.getKeySet()).containsExactly(Pair.of("*.java", false));
+ assertThat(cache.getKeySet())
+ .containsExactly(Pair.of("*.java", Globber.Operation.FILES_AND_DIRS));
cache.getGlobUnsorted("*.js");
- assertThat(cache.getKeySet()).containsExactly(Pair.of("*.java", false), Pair.of("*.js", false));
+ assertThat(cache.getKeySet())
+ .containsExactly(
+ Pair.of("*.java", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("*.js", Globber.Operation.FILES_AND_DIRS));
- cache.getGlobUnsorted("*.java", true);
- assertThat(cache.getKeySet()).containsExactly(Pair.of("*.java", false), Pair.of("*.js", false),
- Pair.of("*.java", true));
+ cache.getGlobUnsorted("*.java", Globber.Operation.FILES);
+ assertThat(cache.getKeySet())
+ .containsExactly(
+ Pair.of("*.java", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("*.js", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("*.java", Globber.Operation.FILES));
assertThrows(BadGlobException.class, () -> cache.getGlobUnsorted("invalid?"));
- assertThat(cache.getKeySet()).containsExactly(Pair.of("*.java", false), Pair.of("*.js", false),
- Pair.of("*.java", true));
+ assertThat(cache.getKeySet())
+ .containsExactly(
+ Pair.of("*.java", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("*.js", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("*.java", Globber.Operation.FILES));
cache.getGlobUnsorted("foo/first.*");
- assertThat(cache.getKeySet()).containsExactly(Pair.of("*.java", false), Pair.of("*.java", true),
- Pair.of("*.js", false), Pair.of("foo/first.*", false));
+ assertThat(cache.getKeySet())
+ .containsExactly(
+ Pair.of("*.java", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("*.java", Globber.Operation.FILES),
+ Pair.of("*.js", Globber.Operation.FILES_AND_DIRS),
+ Pair.of("foo/first.*", Globber.Operation.FILES_AND_DIRS));
}
@Test
public void testGlob() throws Exception {
- assertEmpty(cache.globUnsorted(list("*.java"), NONE, false, true));
+ assertEmpty(cache.globUnsorted(list("*.java"), NONE, Globber.Operation.FILES, true));
- assertThat(cache.globUnsorted(list("*.*"), NONE, false, true))
+ assertThat(cache.globUnsorted(list("*.*"), NONE, Globber.Operation.FILES, true))
.containsExactly("first.js", "first.txt", "second.js", "second.txt");
- assertThat(cache.globUnsorted(list("*.*"), list("first.js"), false, true))
+ assertThat(cache.globUnsorted(list("*.*"), list("first.js"), Globber.Operation.FILES, true))
.containsExactly("first.txt", "second.js", "second.txt");
- assertThat(cache.globUnsorted(list("*.txt", "first.*"), NONE, false, true))
+ assertThat(cache.globUnsorted(list("*.txt", "first.*"), NONE, Globber.Operation.FILES, true))
.containsExactly("first.txt", "second.txt", "first.js");
}
@@ -214,13 +231,17 @@ public void testRecursiveGlobDoesNotMatchSubpackage() throws Exception {
@Test
public void testSingleFileExclude_star() throws Exception {
- assertThat(cache.globUnsorted(list("*"), list("first.txt"), false, true))
+ assertThat(
+ cache.globUnsorted(
+ list("*"), list("first.txt"), Globber.Operation.FILES_AND_DIRS, true))
.containsExactly("BUILD", "bar", "first.js", "foo", "second.js", "second.txt");
}
@Test
public void testSingleFileExclude_starStar() throws Exception {
- assertThat(cache.globUnsorted(list("**"), list("first.txt"), false, true))
+ assertThat(
+ cache.globUnsorted(
+ list("**"), list("first.txt"), Globber.Operation.FILES_AND_DIRS, true))
.containsExactly(
"BUILD",
"bar",
@@ -236,48 +257,78 @@ public void testSingleFileExclude_starStar() throws Exception {
@Test
public void testExcludeAll_star() throws Exception {
- assertThat(cache.globUnsorted(list("*"), list("*"), false, true)).isEmpty();
+ assertThat(cache.globUnsorted(list("*"), list("*"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
}
@Test
public void testExcludeAll_star_noMatchesAnyway() throws Exception {
- assertThat(cache.globUnsorted(list("nope"), list("*"), false, true)).isEmpty();
+ assertThat(cache.globUnsorted(list("nope"), list("*"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
}
@Test
public void testExcludeAll_starStar() throws Exception {
- assertThat(cache.globUnsorted(list("**"), list("**"), false, true)).isEmpty();
+ assertThat(cache.globUnsorted(list("**"), list("**"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
}
@Test
public void testExcludeAll_manual() throws Exception {
- assertThat(cache.globUnsorted(list("**"), list("*", "*/*", "*/*/*"), false, true)).isEmpty();
+ assertThat(
+ cache.globUnsorted(
+ list("**"), list("*", "*/*", "*/*/*"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
}
@Test
public void testSingleFileExcludeDoesntMatch() throws Exception {
- assertThat(cache.globUnsorted(list("first.txt"), list("nope.txt"), false, true))
+ assertThat(
+ cache.globUnsorted(
+ list("first.txt"), list("nope.txt"), Globber.Operation.FILES_AND_DIRS, true))
.containsExactly("first.txt");
}
@Test
public void testExcludeDirectory() throws Exception {
- assertThat(cache.globUnsorted(list("foo/*"), NONE, true, true))
+ assertThat(cache.globUnsorted(list("foo/*"), NONE, Globber.Operation.FILES, true))
.containsExactly("foo/first.js", "foo/second.js");
- assertThat(cache.globUnsorted(list("foo/*"), list("foo"), false, true))
+ assertThat(
+ cache.globUnsorted(list("foo/*"), list("foo"), Globber.Operation.FILES_AND_DIRS, true))
.containsExactly("foo/first.js", "foo/second.js");
}
@Test
public void testChildGlobWithChildExclude() throws Exception {
- assertThat(cache.globUnsorted(list("foo/*"), list("foo/*"), false, true)).isEmpty();
assertThat(
- cache.globUnsorted(list("foo/first.js", "foo/second.js"), list("foo/*"), false, true))
+ cache.globUnsorted(
+ list("foo/*"), list("foo/*"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
+ assertThat(
+ cache.globUnsorted(
+ list("foo/first.js", "foo/second.js"),
+ list("foo/*"),
+ Globber.Operation.FILES_AND_DIRS,
+ true))
.isEmpty();
- assertThat(cache.globUnsorted(list("foo/first.js"), list("foo/first.js"), false, true))
+ assertThat(
+ cache.globUnsorted(
+ list("foo/first.js"), list("foo/first.js"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
+ assertThat(
+ cache.globUnsorted(
+ list("foo/first.js"), list("*/first.js"), Globber.Operation.FILES_AND_DIRS, true))
.isEmpty();
- assertThat(cache.globUnsorted(list("foo/first.js"), list("*/first.js"), false, true)).isEmpty();
- assertThat(cache.globUnsorted(list("foo/first.js"), list("*/*"), false, true)).isEmpty();
+ assertThat(
+ cache.globUnsorted(
+ list("foo/first.js"), list("*/*"), Globber.Operation.FILES_AND_DIRS, true))
+ .isEmpty();
+ }
+
+ @Test
+ public void testSubpackages() throws Exception {
+ assertThat(cache.globUnsorted(list("**"), list(), Globber.Operation.SUBPACKAGES, true))
+ .containsExactly("sub");
}
private void assertEmpty(Collection> glob) {
diff --git a/src/test/java/com/google/devtools/build/lib/packages/NativeGlobTest.java b/src/test/java/com/google/devtools/build/lib/packages/NativeGlobTest.java
new file mode 100644
index 00000000000000..19e40a93995673
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/packages/NativeGlobTest.java
@@ -0,0 +1,161 @@
+// Copyright 2021 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.packages;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
+import com.google.devtools.build.lib.vfs.ModifiedFileSet;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@code native.glob} function. */
+@RunWith(JUnit4.class)
+public class NativeGlobTest extends BuildViewTestCase {
+
+ @Test
+ public void glob_simple() throws Exception {
+ makeFile("test/starlark/file1.txt");
+ makeFile("test/starlark/file2.txt");
+ makeFile("test/starlark/file3.txt");
+
+ makeGlobFilegroup("test/starlark/BUILD", "glob(['*'])");
+
+ assertAttrLabelList(
+ "//test/starlark:files",
+ "srcs",
+ ImmutableList.of(
+ "//test/starlark:BUILD",
+ "//test/starlark:file1.txt",
+ "//test/starlark:file2.txt",
+ "//test/starlark:file3.txt"));
+ }
+
+ @Test
+ public void glob_not_empty() throws Exception {
+
+ makeGlobFilegroup("test/starlark/BUILD", "glob(['foo*'], allow_empty=False)");
+
+ AssertionError e =
+ assertThrows(
+ AssertionError.class,
+ () -> assertAttrLabelList("//test/starlark:files", "srcs", ImmutableList.of()));
+ assertThat(e).hasMessageThat().contains("allow_empty");
+ }
+
+ @Test
+ public void glob_simple_subdirs() throws Exception {
+ makeFile("test/starlark/sub/file1.txt");
+ makeFile("test/starlark/sub2/file2.txt");
+ makeFile("test/starlark/sub3/file3.txt");
+
+ makeGlobFilegroup("test/starlark/BUILD", "glob(['**'])");
+
+ assertAttrLabelList(
+ "//test/starlark:files",
+ "srcs",
+ ImmutableList.of(
+ "//test/starlark:BUILD",
+ "//test/starlark:sub/file1.txt",
+ "//test/starlark:sub2/file2.txt",
+ "//test/starlark:sub3/file3.txt"));
+ }
+
+ @Test
+ public void glob_incremental() throws Exception {
+ makeFile("test/starlark/file1.txt");
+ makeGlobFilegroup("test/starlark/BUILD", "glob(['**'])");
+
+ assertAttrLabelList(
+ "//test/starlark:files",
+ "srcs",
+ ImmutableList.of("//test/starlark:BUILD", "//test/starlark:file1.txt"));
+
+ scratch.file("test/starlark/file2.txt");
+ scratch.file("test/starlark/sub/subfile3.txt");
+
+ // Poke SkyFrame to tell it what changed.
+ invalidateSkyFrameFiles(
+ "test/starlark", "test/starlark/file2.txt", "test/starlark/sub/subfile3.txt");
+
+ assertAttrLabelList(
+ "//test/starlark:files",
+ "srcs",
+ ImmutableList.of(
+ "//test/starlark:BUILD",
+ "//test/starlark:file1.txt",
+ "//test/starlark:file2.txt",
+ "//test/starlark:sub/subfile3.txt"));
+ }
+
+ /**
+ * Constructs a BUILD file containing a single rule with uses glob() to list files look for a rule
+ * called :files in it.
+ */
+ private void makeGlobFilegroup(String buildPath, String glob) throws IOException {
+ scratch.file(buildPath, "filegroup(", " name = 'files',", " srcs = " + glob, ")");
+ }
+
+ private void assertAttrLabelList(String target, String attrName, List expectedLabels)
+ throws Exception {
+ ConfiguredTargetAndData cfgTarget = getConfiguredTargetAndData(target);
+ assertThat(cfgTarget).isNotNull();
+
+ ImmutableList