Skip to content

Commit 63bc14b

Browse files
authored
Implement native analysis_test call. (#15940)
The call defines a rule and a target on the BUILD thread. Since target names need to be unique so is the rule class name. This removes boilerplate in analysis testing. This mechanism replaces rule.name mechanism, which will be removed. If this becomes the default for analysis tests, rule call may be further simplified. The implementation is safeguarded by experimental flag. PiperOrigin-RevId: 462309501 Change-Id: I918ddcc8efd3b27f822998bcaa454e467a98b7ea
1 parent 32cc8e6 commit 63bc14b

File tree

5 files changed

+373
-13
lines changed

5 files changed

+373
-13
lines changed

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java

+79-1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import com.google.errorprone.annotations.FormatMethod;
101101
import java.util.Collection;
102102
import java.util.Map;
103+
import java.util.regex.Pattern;
103104
import javax.annotation.Nullable;
104105
import net.starlark.java.eval.Debug;
105106
import net.starlark.java.eval.Dict;
@@ -111,6 +112,7 @@
111112
import net.starlark.java.eval.StarlarkCallable;
112113
import net.starlark.java.eval.StarlarkFunction;
113114
import net.starlark.java.eval.StarlarkInt;
115+
import net.starlark.java.eval.StarlarkList;
114116
import net.starlark.java.eval.StarlarkThread;
115117
import net.starlark.java.eval.Tuple;
116118
import net.starlark.java.syntax.Identifier;
@@ -138,6 +140,7 @@ public Label load(String from) throws Exception {
138140
}
139141
}
140142
});
143+
private static final Pattern RULE_NAME_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
141144

142145
// TODO(bazel-team): Remove the code duplication (BaseRuleClasses and this class).
143146
/** Parent rule class for non-executable non-test Starlark rules. */
@@ -282,7 +285,7 @@ public Provider provider(String doc, Object fields, StarlarkThread thread) throw
282285

283286
// TODO(bazel-team): implement attribute copy and other rule properties
284287
@Override
285-
public StarlarkCallable rule(
288+
public StarlarkRuleFunction rule(
286289
StarlarkFunction implementation,
287290
Boolean test,
288291
Object attrs,
@@ -507,6 +510,81 @@ public StarlarkCallable rule(
507510
return starlarkRuleFunction;
508511
}
509512

513+
@Override
514+
public void analysisTest(
515+
String name,
516+
StarlarkFunction implementation,
517+
Object attrs,
518+
Sequence<?> fragments,
519+
Sequence<?> toolchains,
520+
Object attrValuesApi,
521+
StarlarkThread thread)
522+
throws EvalException, InterruptedException {
523+
if (!RULE_NAME_PATTERN.matcher(name).matches()) {
524+
throw Starlark.errorf("'name' is limited to Starlark identifiers, got %s", name);
525+
}
526+
Dict<String, Object> attrValues =
527+
Dict.cast(attrValuesApi, String.class, Object.class, "attr_values");
528+
if (attrValues.containsKey("name")) {
529+
throw Starlark.errorf("'name' cannot be set or overridden in 'attr_values'");
530+
}
531+
532+
StarlarkRuleFunction starlarkRuleFunction =
533+
rule(
534+
implementation,
535+
/*test=*/ true,
536+
attrs,
537+
/*implicitOutputs=*/ Starlark.NONE,
538+
/*executable=*/ false,
539+
/*outputToGenfiles=*/ false,
540+
/*fragments=*/ fragments,
541+
/*hostFragments=*/ StarlarkList.empty(),
542+
/*starlarkTestable=*/ false,
543+
/*toolchains=*/ toolchains,
544+
/*useToolchainTransition=*/ false,
545+
/*doc=*/ "",
546+
/*providesArg=*/ StarlarkList.empty(),
547+
/*execCompatibleWith=*/ StarlarkList.empty(),
548+
/*analysisTest=*/ Boolean.TRUE,
549+
/*buildSetting=*/ Starlark.NONE,
550+
/*cfg=*/ Starlark.NONE,
551+
/*execGroups=*/ Starlark.NONE,
552+
/*compileOneFiletype=*/ Starlark.NONE,
553+
/*name=*/ Starlark.NONE,
554+
thread);
555+
556+
// Export the rule
557+
// Because exporting can raise multiple errors, we need to accumulate them here into a single
558+
// EvalException. This is a code smell because any non-ERROR events will be lost, and any
559+
// location information in the events will be overwritten by the location of this rule's
560+
// definition.
561+
// However, this is currently fine because StarlarkRuleFunction#export only creates events that
562+
// are ERRORs and that have the rule definition as their location.
563+
// TODO(brandjon): Instead of accumulating events here, consider registering the rule in the
564+
// BazelStarlarkContext, and exporting such rules after module evaluation in
565+
// BzlLoadFunction#execAndExport.
566+
PackageContext pkgContext = thread.getThreadLocal(PackageContext.class);
567+
StoredEventHandler handler = new StoredEventHandler();
568+
starlarkRuleFunction.export(
569+
handler, pkgContext.getLabel(), name + "_test"); // export in BUILD thread
570+
if (handler.hasErrors()) {
571+
StringBuilder errors =
572+
handler.getEvents().stream()
573+
.filter(e -> e.getKind() == EventKind.ERROR)
574+
.reduce(
575+
new StringBuilder(),
576+
(sb, ev) -> sb.append("\n").append(ev.getMessage()),
577+
StringBuilder::append);
578+
throw Starlark.errorf("Errors in exporting %s: %s", name, errors.toString());
579+
}
580+
581+
// Instantiate the target
582+
Dict.Builder<String, Object> args = Dict.builder();
583+
args.put("name", name);
584+
args.putAll(attrValues);
585+
starlarkRuleFunction.call(thread, Tuple.of(), args.buildImmutable());
586+
}
587+
510588
/**
511589
* Returns the module (file) of the outermost enclosing Starlark function on the call stack or
512590
* null if none of the active calls are functions defined in Starlark.

src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java

+14
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,18 @@ public class BuildLanguageOptions extends OptionsBase implements Serializable {
297297
+ " and 1 cpu.")
298298
public boolean experimentalActionResourceSet;
299299

300+
301+
@Option(
302+
name = "experimental_analysis_test_call",
303+
defaultValue = "true",
304+
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
305+
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS, OptionEffectTag.BUILD_FILE_SEMANTICS},
306+
metadataTags = {
307+
OptionMetadataTag.EXPERIMENTAL,
308+
},
309+
help = "If set to true, analysis_test native call is available.")
310+
public boolean experimentalAnalysisTestCall;
311+
300312
@Option(
301313
name = "incompatible_struct_has_no_methods",
302314
defaultValue = "false",
@@ -586,6 +598,7 @@ public StarlarkSemantics toStarlarkSemantics() {
586598
.setBool(
587599
INCOMPATIBLE_EXISTING_RULES_IMMUTABLE_VIEW, incompatibleExistingRulesImmutableView)
588600
.setBool(EXPERIMENTAL_ACTION_RESOURCE_SET, experimentalActionResourceSet)
601+
.setBool(EXPERIMENTAL_ANALYSIS_TEST_CALL, experimentalAnalysisTestCall)
589602
.setBool(EXPERIMENTAL_GOOGLE_LEGACY_API, experimentalGoogleLegacyApi)
590603
.setBool(EXPERIMENTAL_NINJA_ACTIONS, experimentalNinjaActions)
591604
.setBool(EXPERIMENTAL_PLATFORMS_API, experimentalPlatformsApi)
@@ -662,6 +675,7 @@ public StarlarkSemantics toStarlarkSemantics() {
662675
public static final String EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT =
663676
"-experimental_sibling_repository_layout";
664677
public static final String EXPERIMENTAL_ACTION_RESOURCE_SET = "+experimental_action_resource_set";
678+
public static final String EXPERIMENTAL_ANALYSIS_TEST_CALL = "+experimental_analysis_test_call";
665679
public static final String INCOMPATIBLE_ALLOW_TAGS_PROPAGATION =
666680
"-incompatible_allow_tags_propagation";
667681
public static final String INCOMPATIBLE_ALWAYS_CHECK_DEPSET_ELEMENTS =

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java

+83-12
Original file line numberDiff line numberDiff line change
@@ -359,18 +359,18 @@ public interface StarlarkRuleFunctionsApi<FileApiT extends FileApi> {
359359
positional = false,
360360
allowedTypes = {@ParamType(type = String.class), @ParamType(type = NoneType.class)},
361361
doc =
362-
"The name of this rule, as understood by Bazel and reported in contexts such as"
363-
+ " logging, <code>native.existing_rule(...)[kind]</code>, and <code>bazel"
364-
+ " query</code>. Usually this is the same as the Starlark identifier that gets"
365-
+ " bound to this rule; for instance a rule called <code>foo_library</code>"
366-
+ " would typically be declared as <code>foo_library = rule(...)</code> and"
367-
+ " instantiated in a BUILD file as <code>foo_library(...)</code>.<p>If this"
368-
+ " parameter is omitted, the rule's name is set to the name of the first"
369-
+ " Starlark global variable to be bound to this rule within its declaring .bzl"
370-
+ " module. Thus, <code>foo_library = rule(...)</code> need not specify this"
371-
+ " parameter if the name is <code>foo_library</code>.<p>Specifying an explicit"
372-
+ " name for a rule does not change where you are allowed to instantiate the"
373-
+ " rule."),
362+
"Deprecated: do not use.<p>The name of this rule, as understood by Bazel and"
363+
+ " reported in contexts such as logging,"
364+
+ " <code>native.existing_rule(...)[kind]</code>, and <code>bazel query</code>."
365+
+ " Usually this is the same as the Starlark identifier that gets bound to this"
366+
+ " rule; for instance a rule called <code>foo_library</code> would typically"
367+
+ " be declared as <code>foo_library = rule(...)</code> and instantiated in a"
368+
+ " BUILD file as <code>foo_library(...)</code>.<p>If this parameter is"
369+
+ " omitted, the rule's name is set to the name of the first Starlark global"
370+
+ " variable to be bound to this rule within its declaring .bzl module. Thus,"
371+
+ " <code>foo_library = rule(...)</code> need not specify this parameter if the"
372+
+ " name is <code>foo_library</code>.<p>Specifying an explicit name for a rule"
373+
+ " does not change where you are allowed to instantiate the rule."),
374374
},
375375
useStarlarkThread = true)
376376
StarlarkCallable rule(
@@ -397,6 +397,77 @@ StarlarkCallable rule(
397397
StarlarkThread thread)
398398
throws EvalException;
399399

400+
@StarlarkMethod(
401+
name = "analysis_test",
402+
doc =
403+
"Creates a new analysis test target. <p>The number of transitive dependencies of the test"
404+
+ " are limited. The limit is controlled by"
405+
+ " <code>--analysis_testing_deps_limit</code> flag.",
406+
parameters = {
407+
@Param(
408+
name = "name",
409+
named = true,
410+
doc =
411+
"Name of the target. It should be a Starlark identifier, matching pattern"
412+
+ " '[A-Za-z_][A-Za-z0-9_]*'."),
413+
@Param(
414+
name = "implementation",
415+
named = true,
416+
doc =
417+
"The Starlark function implementing this analysis test. It must have exactly one"
418+
+ " parameter: <a href=\"ctx.html\">ctx</a>. The function is called during the"
419+
+ " analysis phase. It can access the attributes declared by <code>attrs</code>"
420+
+ " and populated via <code>attr_values</code>. The implementation function may"
421+
+ " not register actions. Instead, it must register a pass/fail result"
422+
+ " via providing <a"
423+
+ " href='AnalysisTestResultInfo.html'>AnalysisTestResultInfo</a>."),
424+
@Param(
425+
name = "attrs",
426+
allowedTypes = {
427+
@ParamType(type = Dict.class),
428+
@ParamType(type = NoneType.class),
429+
},
430+
named = true,
431+
defaultValue = "None",
432+
doc =
433+
"Dictionary declaring the attributes. See the <a href=\"rule.html\">rule</a> call."
434+
+ "Attributes are allowed to use configuration transitions defined using <a "
435+
+ " href=\"#analysis_test_transition\">analysis_test_transition</a>."),
436+
@Param(
437+
name = "fragments",
438+
allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
439+
named = true,
440+
defaultValue = "[]",
441+
doc =
442+
"List of configuration fragments that are available to the implementation of the"
443+
+ " analysis test."),
444+
@Param(
445+
name = TOOLCHAINS_PARAM,
446+
allowedTypes = {@ParamType(type = Sequence.class, generic1 = Object.class)},
447+
named = true,
448+
defaultValue = "[]",
449+
doc =
450+
"The set of toolchains the test requires. See the <a href=\"#rule\">rule</a>"
451+
+ " call."),
452+
@Param(
453+
name = "attr_values",
454+
allowedTypes = {@ParamType(type = Dict.class, generic1 = String.class)},
455+
named = true,
456+
defaultValue = "{}",
457+
doc = "Dictionary of attribute values to pass to the implementation."),
458+
},
459+
useStarlarkThread = true,
460+
enableOnlyWithFlag = BuildLanguageOptions.EXPERIMENTAL_ANALYSIS_TEST_CALL)
461+
void analysisTest(
462+
String name,
463+
StarlarkFunction implementation,
464+
Object attrs,
465+
Sequence<?> fragments,
466+
Sequence<?> toolchains,
467+
Object argsValue,
468+
StarlarkThread thread)
469+
throws EvalException, InterruptedException;
470+
400471
@StarlarkMethod(
401472
name = "aspect",
402473
doc =

src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkRuleFunctionsApi.java

+11
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,17 @@ public StarlarkCallable rule(
166166
return functionIdentifier;
167167
}
168168

169+
@Override
170+
public void analysisTest(
171+
String name,
172+
StarlarkFunction implementation,
173+
Object attrs,
174+
Sequence<?> fragments,
175+
Sequence<?> toolchains,
176+
Object argsValue,
177+
StarlarkThread thread)
178+
throws EvalException, InterruptedException {}
179+
169180
@Override
170181
public Label label(String labelString, StarlarkThread thread) throws EvalException {
171182
try {

0 commit comments

Comments
 (0)