Skip to content

Commit 986d4bd

Browse files
kkresscopybara-github
authored andcommitted
Part 2 Implementation for new 'subpackages()` built-in helper function.
Design proposal: https://docs.google.com/document/d/13UOT0GoQofxDW40ILzH2sWpUOmuYy6QZ7CUmhej9vgk/edit# Overview: Add StarlarkNativeModule 'subpackages' function with parameters that mirror glob() PiperOrigin-RevId: 422652954
1 parent 004f45e commit 986d4bd

13 files changed

Lines changed: 901 additions & 117 deletions

File tree

src/main/java/com/google/devtools/build/docgen/templates/be/functions.vm

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ title: Functions
1919
<li><a href="#exports_files">exports_files</a></li>
2020
<li><a href="#glob">glob</a></li>
2121
<li><a href="#select">select</a></li>
22+
<li><a href="#subpackages">subpackages</a></li>
2223
</ul>
2324
</div>
2425
#end
@@ -636,48 +637,53 @@ sh_binary(
636637
<li><code>select</code> works with most, but not all, attributes. Incompatible
637638
attributes are marked <code>nonconfigurable</code> in their documentation.
638639

639-
</li>
640-
</ul>
641-
642-
By default, Bazel produces the following error when no conditions match:
643-
<pre class="code">
644-
Configurable attribute "foo" doesn't match this configuration (would a default
645-
condition help?).
646-
Conditions checked:
647-
//pkg:conditionA.
648-
//pkg:conditionB.
649-
</pre>
640+
<!-- =================================================================
641+
subpackages()
642+
=================================================================
643+
-->
650644

651-
You can signal more precise errors with <code>no_match_error</code>.
645+
<h2 id="subpackages">subpackages</h2>
652646

653-
<h3 id="select_example">Examples</h3>
647+
<pre>subpackages(include, exclude=[], allow_empty=True)</pre>
654648

655-
<pre class="code">
656-
config_setting(
657-
name = "windows",
658-
values = {
659-
"crosstool_top": "//crosstools/windows",
660-
},
661-
)
649+
<p>
650+
<code>subpackages()</code> is a helper function, similar to <code>glob()</code>
651+
that lists subpackages instead of files and directories. It uses the same
652+
path patterns as <code>glob()</code> and can match any subpackage that is a
653+
direct decendant of the currently loading BUILD file. See <a
654+
href="#glob">glob</a> for detailed explinations and example of include and
655+
exclude patterns.
656+
</p>
662657

663-
cc_binary(
664-
name = "multiplatform_app",
665-
...
666-
linkopts = select({
667-
":windows": [
668-
"-Wl,windows_support1.lib",
669-
"-Wl,windows_support2.lib",
670-
],
671-
"//conditions:default": [],
672-
...
673-
)
674-
</pre>
658+
<p>
659+
The resulting list of subpackages returned is in sorted order and contains a
660+
paths relative to the current loading package that match the given patterns in
661+
<code>include</code> and not those in <code>exclude</code>.
675662

676-
<p>In the above example, <code>multiplatform_app</code> links with additional
677-
options when invoked with <code>bazel build //pkg:multiplatform_app
678-
--crosstool_top=//crosstools/windows </code>.
663+
<h3 id=subpackages_example">Example</h3>
679664

680665
<p>
666+
The following example lists all the direct subpackages for the package <code>foo/BUILD</code>
667+
668+
<pre class="code">
669+
# The following BUILD files exist:
670+
# foo/BUILD
671+
# foo/bar/baz/BUILD
672+
# foo/sub/BUILD
673+
# foo/sub/deeper/BUILD
674+
#
675+
# In foo/BUILD a call to
676+
subs = subpackages(include = ["**"])
677+
678+
# results in subs == ["sub", "foo/bar/baz"]`
679+
#
680+
# foo/sub/deeper is not included because it is a subpackage of 'foo/sub' not of
681+
# 'foo'
682+
</pre>
683+
684+
<p>
685+
In general it is preferred that instead of calling this function directly
686+
that users use the 'subpackages' module of
681687

682688
#if (!$singlePage)
683689
#parse("com/google/devtools/build/docgen/templates/be/footer.vm")

src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ public void executeBuildFile(
601601
Program buildFileProgram,
602602
ImmutableList<String> globs,
603603
ImmutableList<String> globsWithDirs,
604+
ImmutableList<String> subpackages,
604605
ImmutableMap<String, Object> predeclared,
605606
ImmutableMap<String, Module> loadedModules,
606607
StarlarkSemantics starlarkSemantics,
@@ -613,6 +614,8 @@ public void executeBuildFile(
613614
globber.runAsync(globs, ImmutableList.of(), Globber.Operation.FILES, allowEmpty);
614615
globber.runAsync(
615616
globsWithDirs, ImmutableList.of(), Globber.Operation.FILES_AND_DIRS, allowEmpty);
617+
globber.runAsync(
618+
subpackages, ImmutableList.of(), Globber.Operation.SUBPACKAGES, allowEmpty);
616619
} catch (BadGlobException ex) {
617620
logger.atWarning().withCause(ex).log(
618621
"Suppressing exception for globs=%s, globsWithDirs=%s", globs, globsWithDirs);
@@ -734,6 +737,7 @@ public static boolean checkBuildSyntax(
734737
StarlarkFile file,
735738
Collection<String> globs,
736739
Collection<String> globsWithDirs,
740+
Collection<String> subpackages,
737741
Map<Location, String> generatorNameByLocation,
738742
Consumer<SyntaxError> errors) {
739743
final boolean[] success = {true};
@@ -747,11 +751,16 @@ void error(Location loc, String message) {
747751
// Extract literal glob patterns from calls of the form:
748752
// glob(include = ["pattern"])
749753
// glob(["pattern"])
750-
// This may spuriously match user-defined functions named glob;
751-
// that's ok, it's only a heuristic.
754+
// subpackages(include = ["pattern"])
755+
// This may spuriously match user-defined functions named glob or
756+
// subpackages; that's ok, it's only a heuristic.
752757
void extractGlobPatterns(CallExpression call) {
753-
if (call.getFunction() instanceof Identifier
754-
&& ((Identifier) call.getFunction()).getName().equals("glob")) {
758+
if (call.getFunction() instanceof Identifier) {
759+
String functionName = ((Identifier) call.getFunction()).getName();
760+
if (!functionName.equals("glob") && !functionName.equals("subpackages")) {
761+
return;
762+
}
763+
755764
Expression excludeDirectories = null, include = null;
756765
List<Argument> arguments = call.getArguments();
757766
for (int i = 0; i < arguments.size(); i++) {
@@ -779,7 +788,11 @@ void extractGlobPatterns(CallExpression call) {
779788
exclude = false;
780789
}
781790
}
782-
(exclude ? globs : globsWithDirs).add(pattern);
791+
if (functionName.equals("glob")) {
792+
(exclude ? globs : globsWithDirs).add(pattern);
793+
} else {
794+
subpackages.add(pattern);
795+
}
783796
}
784797
}
785798
}

src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeModule.java

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ public Sequence<?> glob(
102102
Globber.Operation op =
103103
excludeDirs.signum() != 0 ? Globber.Operation.FILES : Globber.Operation.FILES_AND_DIRS;
104104

105-
List<String> matches;
106105
boolean allowEmpty;
107106
if (allowEmptyArgument == Starlark.UNBOUND) {
108107
allowEmpty =
@@ -114,35 +113,7 @@ public Sequence<?> glob(
114113
"expected boolean for argument `allow_empty`, got `%s`", allowEmptyArgument);
115114
}
116115

117-
try {
118-
Globber.Token globToken = context.globber.runAsync(includes, excludes, op, allowEmpty);
119-
matches = context.globber.fetchUnsorted(globToken);
120-
} catch (IOException e) {
121-
logger.atWarning().withCause(e).log(
122-
"Exception processing includes=%s, excludes=%s)", includes, excludes);
123-
String errorMessage =
124-
String.format(
125-
"error globbing [%s]%s: %s",
126-
Joiner.on(", ").join(includes),
127-
excludes.isEmpty() ? "" : " - [" + Joiner.on(", ").join(excludes) + "]",
128-
e.getMessage());
129-
Location loc = thread.getCallerLocation();
130-
Event error =
131-
Package.error(
132-
loc,
133-
errorMessage,
134-
// If there are other IOExceptions that can result from user error, they should be
135-
// tested for here. Currently FileNotFoundException is not one of those, because globs
136-
// only encounter that error in the presence of an inconsistent filesystem.
137-
e instanceof FileSymlinkException
138-
? Code.EVAL_GLOBS_SYMLINK_ERROR
139-
: Code.GLOB_IO_EXCEPTION);
140-
context.eventHandler.handle(error);
141-
context.pkgBuilder.setIOException(e, errorMessage, error.getProperty(DetailedExitCode.class));
142-
matches = ImmutableList.of();
143-
} catch (BadGlobException e) {
144-
throw new EvalException(e);
145-
}
116+
List<String> matches = runGlobOperation(context, thread, includes, excludes, op, allowEmpty);
146117

147118
ArrayList<String> result = new ArrayList<>(matches.size());
148119
for (String match : matches) {
@@ -751,4 +722,62 @@ private static class NotRepresentableException extends EvalException {
751722
super(msg);
752723
}
753724
}
725+
726+
@Override
727+
public Sequence<?> subpackages(
728+
Sequence<?> include, Sequence<?> exclude, boolean allowEmpty, StarlarkThread thread)
729+
throws EvalException, InterruptedException {
730+
BazelStarlarkContext.from(thread).checkLoadingPhase("native.subpackages");
731+
PackageContext context = getContext(thread);
732+
733+
List<String> includes = Type.STRING_LIST.convert(include, "'subpackages' argument");
734+
List<String> excludes = Type.STRING_LIST.convert(exclude, "'subpackages' argument");
735+
736+
List<String> matches =
737+
runGlobOperation(
738+
context, thread, includes, excludes, Globber.Operation.SUBPACKAGES, allowEmpty);
739+
matches.sort(naturalOrder());
740+
741+
return StarlarkList.copyOf(thread.mutability(), matches);
742+
}
743+
744+
private List<String> runGlobOperation(
745+
PackageContext context,
746+
StarlarkThread thread,
747+
List<String> includes,
748+
List<String> excludes,
749+
Globber.Operation operation,
750+
boolean allowEmpty)
751+
throws EvalException, InterruptedException {
752+
try {
753+
Globber.Token globToken = context.globber.runAsync(includes, excludes, operation, allowEmpty);
754+
return context.globber.fetchUnsorted(globToken);
755+
} catch (IOException e) {
756+
logger.atWarning().withCause(e).log(
757+
"Exception processing includes=%s, excludes=%s)", includes, excludes);
758+
String errorMessage =
759+
String.format(
760+
"error globbing [%s]%s op=%s: %s",
761+
Joiner.on(", ").join(includes),
762+
excludes.isEmpty() ? "" : " - [" + Joiner.on(", ").join(excludes) + "]",
763+
operation,
764+
e.getMessage());
765+
Location loc = thread.getCallerLocation();
766+
Event error =
767+
Package.error(
768+
loc,
769+
errorMessage,
770+
// If there are other IOExceptions that can result from user error, they should be
771+
// tested for here. Currently FileNotFoundException is not one of those, because globs
772+
// only encounter that error in the presence of an inconsistent filesystem.
773+
e instanceof FileSymlinkException
774+
? Code.EVAL_GLOBS_SYMLINK_ERROR
775+
: Code.GLOB_IO_EXCEPTION);
776+
context.eventHandler.handle(error);
777+
context.pkgBuilder.setIOException(e, errorMessage, error.getProperty(DetailedExitCode.class));
778+
return ImmutableList.of();
779+
} catch (BadGlobException e) {
780+
throw new EvalException(e);
781+
}
782+
}
754783
}

src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ public void execute(
146146
List<String> globs = new ArrayList<>(); // unused
147147
if (PackageFactory.checkBuildSyntax(
148148
file,
149-
globs,
150-
globs,
149+
/*globs=*/ globs,
150+
/*globsWithDirs=*/ globs,
151+
/*subpackages=*/ globs,
151152
new HashMap<>(),
152153
error ->
153154
localReporter.handle(

0 commit comments

Comments
 (0)