Skip to content

Commit 9e3fff3

Browse files
committed
Address review comments.
1 parent 1329b6e commit 9e3fff3

File tree

6 files changed

+319
-28
lines changed

6 files changed

+319
-28
lines changed

build_config/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ triggers:
273273
274274
An `annotation` trigger causes the builder to run if an annotation is used.
275275
So, `- annotation MyAnnotation` is a check for `@MyAnnotation` being used.
276+
A part file included from a library is also checked for the annotation.
276277

277278
An `import` trigger says that the builder runs if there is a direct import
278279
of the specified library. This might be useful if a builder can run on code

build_runner_core/lib/src/generate/build.dart

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import 'dart:async';
66
import 'dart:convert';
77

8+
import 'package:analyzer/dart/analysis/utilities.dart';
9+
import 'package:analyzer/dart/ast/ast.dart';
810
import 'package:build/build.dart';
911
// ignore: implementation_imports
1012
import 'package:build/src/internal.dart';
@@ -589,14 +591,47 @@ class Build {
589591
return false;
590592
}
591593
final primaryInputSource = await readerWriter.readAsString(primaryInput);
594+
final compilationUnit = _parseCompilationUnit(primaryInputSource);
595+
List<CompilationUnit>? compilationUnits;
592596
for (final trigger in buildTriggers) {
593-
if (trigger.triggersOnPrimaryInput(primaryInputSource)) {
594-
return true;
597+
if (trigger.checksParts) {
598+
compilationUnits ??= await _readAndParseCompilationUnits(
599+
readerWriter,
600+
primaryInput,
601+
compilationUnit,
602+
);
603+
if (trigger.triggersOn(compilationUnits)) return true;
604+
} else {
605+
if (trigger.triggersOn([compilationUnit])) return true;
595606
}
596607
}
597608
return false;
598609
}
599610

611+
/// TODO(davidmorgan): cache parse results, share with deps parsing and
612+
/// builder parsing.
613+
static CompilationUnit _parseCompilationUnit(String content) {
614+
return parseString(content: content, throwIfDiagnostics: false).unit;
615+
}
616+
617+
static Future<List<CompilationUnit>> _readAndParseCompilationUnits(
618+
AssetReader reader,
619+
AssetId id,
620+
CompilationUnit compilationUnit,
621+
) async {
622+
final result = [compilationUnit];
623+
for (var directive in compilationUnit.directives) {
624+
if (directive is! PartDirective) continue;
625+
final partId = AssetId.resolve(
626+
Uri.parse(directive.uri.stringValue!),
627+
from: id,
628+
);
629+
if (!await reader.canRead(partId)) continue;
630+
result.add(_parseCompilationUnit(await reader.readAsString(partId)));
631+
}
632+
return result;
633+
}
634+
600635
Future<Iterable<AssetId>> _runPostBuildPhase(
601636
int phaseNum,
602637
PostBuildPhase phase,

build_runner_core/lib/src/package_graph/build_triggers.dart

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:convert';
66

7+
import 'package:analyzer/dart/ast/ast.dart';
78
import 'package:build_config/build_config.dart';
89
import 'package:built_collection/built_collection.dart';
910
import 'package:built_value/built_value.dart';
@@ -80,7 +81,7 @@ class BuildTriggers {
8081
/// Parses [triggers], or throws a descriptive exception on failure.
8182
///
8283
/// [triggers] is an `Object` read directly from yaml, to be valid it should
83-
/// be a list of strings that parse with [BuildTrigger.tryParse].
84+
/// be a list of strings that parse with [BuildTrigger._tryParse].
8485
static (Iterable<BuildTrigger>, List<String>) parseList({
8586
required Object triggers,
8687
}) {
@@ -96,7 +97,7 @@ class BuildTriggers {
9697
BuildTrigger? trigger;
9798
String? warning;
9899
if (triggerString is String) {
99-
(trigger, warning) = BuildTrigger.tryParse(triggerString);
100+
(trigger, warning) = BuildTrigger._tryParse(triggerString);
100101
}
101102
if (trigger != null) {
102103
result.add(trigger);
@@ -124,13 +125,13 @@ class BuildTriggers {
124125
}
125126

126127
/// A build trigger: a heuristic that possibly skips running a build step based
127-
/// on the primary input source.
128+
/// on the parsed primary input.
128129
abstract class BuildTrigger {
129130
/// Parses a trigger, returning `null` on failure, optionally with a warning
130131
/// message.
131132
///
132133
/// The only supported trigger is [ImportBuildTrigger].
133-
static (BuildTrigger?, String?) tryParse(String trigger) {
134+
static (BuildTrigger?, String?) _tryParse(String trigger) {
134135
if (trigger.startsWith('import ')) {
135136
trigger = trigger.substring('import '.length);
136137
final result = ImportBuildTrigger(trigger);
@@ -143,18 +144,21 @@ abstract class BuildTrigger {
143144
return (null, 'Invalid trigger: `$trigger`');
144145
}
145146

146-
bool triggersOnPrimaryInput(String source);
147+
/// Whether the trigger matches on any of [compilationUnits].
148+
///
149+
/// This will be called either with the primary input compilation unit or all
150+
/// compilation units (parts) depending on [checksParts].
151+
bool triggersOn(List<CompilationUnit> compilationUnits);
152+
153+
/// Whether [triggersOn] should be called with all compilation units, not just
154+
/// the primary input.
155+
bool get checksParts;
147156
}
148157

149158
// Note: `built_value` generated toString is relied on for digests, and
150159
// equality for testing.
151160

152161
/// A build trigger that checks for a direct import.
153-
///
154-
/// TODO(davidmorgan): this currently over-matches, it just checks for a
155-
/// matching substring. It's a heuristic so over matching is okay, but it might
156-
/// be better to actually parse directives and do a correct match because
157-
/// directives are needed when resolving source.
158162
abstract class ImportBuildTrigger
159163
implements
160164
Built<ImportBuildTrigger, ImportBuildTriggerBuilder>,
@@ -166,18 +170,28 @@ abstract class ImportBuildTrigger
166170
factory ImportBuildTrigger(String import) =>
167171
_$ImportBuildTrigger._(import: import);
168172

173+
@memoized
174+
String get packageImport => 'package:$import';
175+
169176
@override
170-
bool triggersOnPrimaryInput(String source) {
171-
return source.contains('package:$import');
177+
bool get checksParts => false;
178+
179+
@override
180+
bool triggersOn(List<CompilationUnit> compilationUnits) {
181+
for (final compilationUnit in compilationUnits) {
182+
for (final directive in compilationUnit.directives) {
183+
if (directive is! ImportDirective) continue;
184+
if (directive.uri.stringValue == packageImport) return true;
185+
}
186+
}
187+
return false;
172188
}
173189

174190
String? get warning =>
175191
import.contains(_regexp) ? null : 'Invalid import trigger: `$import`';
176192
}
177193

178194
/// A build trigger that checks for an annotation.
179-
///
180-
/// TODO(davidmorgan): like `ImportBuildTrigger` this over-matches.
181195
abstract class AnnotationBuildTrigger
182196
implements
183197
Built<AnnotationBuildTrigger, AnnotationBuildTriggerBuilder>,
@@ -190,8 +204,27 @@ abstract class AnnotationBuildTrigger
190204
_$AnnotationBuildTrigger._(annotation: annotation);
191205

192206
@override
193-
bool triggersOnPrimaryInput(String source) {
194-
return source.contains('@$annotation');
207+
bool get checksParts => true;
208+
209+
@override
210+
bool triggersOn(List<CompilationUnit> compilationUnits) {
211+
for (final compilationUnit in compilationUnits) {
212+
for (final declaration in compilationUnit.declarations) {
213+
for (final metadata in declaration.metadata) {
214+
var name = metadata.name.name;
215+
if (metadata.constructorName != null) {
216+
name = '$name.${metadata.constructorName}';
217+
}
218+
if (annotation == name) return true;
219+
final periodIndex = name.indexOf('.');
220+
if (periodIndex != -1) {
221+
name = name.substring(periodIndex + 1);
222+
if (annotation == name) return true;
223+
}
224+
}
225+
}
226+
}
227+
return false;
195228
}
196229

197230
String? get warning =>

build_runner_core/lib/src/package_graph/build_triggers.g.dart

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build_runner_core/test/invalidation/build_yaml_invalidation_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ triggers:
6767
- import trigger/trigger.dart
6868
''');
6969
tester.importGraph({
70-
'a.1': ['package:trigger/trigger.dart'],
70+
'a.1': ['package:trigger/trigger'],
7171
});
7272
});
7373

@@ -122,7 +122,7 @@ triggers:
122122
''');
123123
await tester.build();
124124
tester.importGraph({
125-
'a.1': ['package:trigger/trigger.dart'],
125+
'a.1': ['package:trigger/trigger'],
126126
});
127127
expect(await tester.build(change: 'a.1'), Result(written: ['a.2']));
128128
});
@@ -138,7 +138,7 @@ targets:
138138
run_only_if_triggered: true
139139
''');
140140
tester.importGraph({
141-
'a.1': ['package:trigger/trigger.dart'],
141+
'a.1': ['package:trigger/trigger'],
142142
});
143143
await tester.build();
144144
expect(
@@ -230,7 +230,7 @@ triggers:
230230
- import trigger/trigger.dart
231231
''');
232232
tester.importGraph({
233-
'a.1': ['package:trigger/trigger.dart'],
233+
'a.1': ['package:trigger/trigger'],
234234
});
235235
await tester.build();
236236
tester.importGraph({'a.1': []});
@@ -252,7 +252,7 @@ triggers:
252252
- import trigger/trigger.dart
253253
''');
254254
tester.importGraph({
255-
'a.1': ['package:trigger/trigger.dart'],
255+
'a.1': ['package:trigger/trigger'],
256256
});
257257
await tester.build();
258258
expect(

0 commit comments

Comments
 (0)