Skip to content

Add "triggers" to quickly decide when to not run builders. #4084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 12, 2025

Conversation

davidmorgan
Copy link
Contributor

@davidmorgan davidmorgan commented Jul 8, 2025

For #4083

Before/after incremental builds with a locally-configured annotation trigger for built_value, this with the --mostly-no-codegen option so most files should not run the builder.

before

5s built_value_generator:built_value on 1001 inputs: 2 same, 999 no-op; spent 3s analyzing                                                                                                                                                                   
1s source_gen:combining_builder on 1001 inputs: 999 skipped, 2 same; spent 1s tracking                                                                                                                                                                       
                                                                                                                                                                                                                                                             
Built with build_runner in 8s; wrote 4 outputs.

after

2s built_value_generator:built_value on 1001 inputs: 999 skipped, 1 not triggered, 1 same; spent 1s analyzing                                                                                                                                                
1s source_gen:combining_builder on 1001 inputs: 1000 skipped, 1 same; spent 1s tracking                                                                                                                                                                      
                                                                                                                                                                                                                                                             
Built with build_runner in 5s; wrote 2 outputs.

999 have changed from no-op to skipped: previously the builder would run and analyze all files, now build_runner has noticed that only a primary input change can affect triggering for 999 files and so they are skipped.

Copy link

github-actions bot commented Jul 8, 2025

PR Health

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

@davidmorgan davidmorgan force-pushed the run-heuristics branch 3 times, most recently from 466580e to 5deeaae Compare July 14, 2025 08:47
@davidmorgan davidmorgan force-pushed the run-heuristics branch 6 times, most recently from b575b09 to 0551fa2 Compare July 21, 2025 07:53
@davidmorgan davidmorgan marked this pull request as ready for review July 21, 2025 07:58
@davidmorgan davidmorgan force-pushed the run-heuristics branch 3 times, most recently from b7baf18 to 48f8008 Compare August 8, 2025 15:38
@davidmorgan davidmorgan changed the title Add option to skip based on primary input content. Add "triggers" to quickly decide when to not run builders. Aug 8, 2025
@davidmorgan davidmorgan requested a review from jensjoha August 8, 2025 16:12
Copy link

@jensjoha jensjoha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an awful lot of manual config that's bound to go wrong at some point.
We can't do something similar without manual config?


@override
bool triggersOnPrimaryInput(String source) {
return source.contains('package:$import');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically an import string can be split into multiple lines, e.g.

import 'd' /* hello */ 'a'
//there
'r'
// I'm
't'
// a
':'
// perfectly
'i'
// valid
'o'
// import
''
// !
;

(and I have seen than --- not as absurdly as I did above but as I recall to make it fit on lines or something)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hesitant to do full parsing simply because there is no convenient place to dedupe the parsing work; but I do think it's better and the way to go in the end.

Filed #4128 to figure out a way to save parsing multiple times, and changed the code to do parsing so annotation + import checks are both (hopefully) now correct.


@override
bool triggersOnPrimaryInput(String source) {
return source.contains('@$annotation');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe @ myAnnotation is valid too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by switch to parsing.

Also, I realized that annotations should be checked for in all compilation units, it's not like imports where they can only be in the main library. So, I added finding and loading all the parts for the check, and test coverage for that.

for (final entry in triggersByBuilder.entries) {
final builderName = entry.key;

if (!builderName.contains(_builderNamePattern)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this be found below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the checks below are just on the triggers, not the builder names.

/// message.
///
/// The only supported trigger is [ImportBuildTrigger].
static (BuildTrigger?, String?) tryParse(String trigger) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be private?
If it has to be public I think I'd prefer a "proper return type" rather than a record.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks; made private.

Copy link
Contributor Author

@davidmorgan davidmorgan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, please take another look.

Re: config-based: the fact that it's config based rather than code means that the builder package, the user package and any additional package in the build can all contribute triggers.

Usually, only the builder package will want to define triggers, which would work in code; but then if you want to add a new way to trigger, e.g. to re-export an annotation, you're stuck.

My expectation is that for most builders, just a trigger defined with the builder works 95% of the time, and then a small number of advanced users defining new ways to run the builder can do what they want for the remaining 5%. One important case is one builder that triggers another, as freezed does with json_serializable ... that should work.

There's a suggestion to add a lint to point out cases where a trigger should be defined #4086

for (final entry in triggersByBuilder.entries) {
final builderName = entry.key;

if (!builderName.contains(_builderNamePattern)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the checks below are just on the triggers, not the builder names.

/// message.
///
/// The only supported trigger is [ImportBuildTrigger].
static (BuildTrigger?, String?) tryParse(String trigger) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks; made private.


@override
bool triggersOnPrimaryInput(String source) {
return source.contains('package:$import');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hesitant to do full parsing simply because there is no convenient place to dedupe the parsing work; but I do think it's better and the way to go in the end.

Filed #4128 to figure out a way to save parsing multiple times, and changed the code to do parsing so annotation + import checks are both (hopefully) now correct.


@override
bool triggersOnPrimaryInput(String source) {
return source.contains('@$annotation');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by switch to parsing.

Also, I realized that annotations should be checked for in all compilation units, it's not like imports where they can only be in the main library. So, I added finding and loading all the parts for the check, and test coverage for that.

@davidmorgan davidmorgan requested a review from jensjoha August 11, 2025 11:57
name = '$name.${metadata.constructorName}';
}
if (annotation == name) return true;
final periodIndex = name.indexOf('.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we end up comparing to metadata.constructorName or could name (before line 216) have dots in it where we want to remove the first part? Could there be a comment explaining? (and either compare directly to metadata.constructorName or make sure we don't depending on what we're actually trying to achieve)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, added comment + a bit of test coverage on the awkward case. (Import prefixes and class names can't actually be distinguished).

@davidmorgan davidmorgan merged commit f0a289e into dart-lang:master Aug 12, 2025
75 checks passed
@davidmorgan davidmorgan deleted the run-heuristics branch August 12, 2025 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants