Skip to content

Commit 09a4fef

Browse files
authored
Add package config to build step (#3495)
This adds a `packageConfig` getter to `BuildStep`, which can be used to read the language version of packages involved in the build. The package config exposed in the build step hides the actual files of packages on the file system. Instead, By default, the package config will be resolved from the current isolate in `runBuilder`. However, `build_runner_core` also constructs a config based on the `PackageGraph` which avoids loading the raw package config multiple times. Closes #3492.
1 parent d3a9ecf commit 09a4fef

File tree

12 files changed

+211
-23
lines changed

12 files changed

+211
-23
lines changed

build/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
## 2.3.2-dev
1+
## 2.4.0-dev
2+
3+
- Add `BuildStep.packageConfig` getter to resolve a package config of all
4+
packages involved in the current build.
25

36
## 2.3.1
47

build/lib/src/builder/build_step.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:convert';
66

77
import 'package:analyzer/dart/element/element.dart';
88
import 'package:meta/meta.dart';
9+
import 'package:package_config/package_config_types.dart';
910

1011
import '../analyzer/resolver.dart';
1112
import '../asset/id.dart';
@@ -118,6 +119,17 @@ abstract class BuildStep implements AssetReader, AssetWriter {
118119
/// `InvalidOutputException` when attempting to write an asset not part of
119120
/// the [allowedOutputs].
120121
Iterable<AssetId> get allowedOutputs;
122+
123+
/// Returns a [PackageConfig] resolvable from this build step.
124+
///
125+
/// The package config contains all packages involved in the build. Typically,
126+
/// this is the package config taken from the current isolate.
127+
///
128+
/// The returned package config does not use `file:/`-based URIs and can't be
129+
/// used to access package contents with `dart:io`. Instead, packages resolve
130+
/// to `asset:/` URIs that can be parsed with [AssetId.resolve] and read with
131+
/// [readAsBytes] or [readAsString].
132+
Future<PackageConfig> get packageConfig;
121133
}
122134

123135
abstract class StageTracker {

build/lib/src/builder/build_step_impl.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:analyzer/dart/element/element.dart';
1010
import 'package:async/async.dart';
1111
import 'package:crypto/crypto.dart';
1212
import 'package:glob/glob.dart';
13+
import 'package:package_config/package_config_types.dart';
1314

1415
import '../analyzer/resolver.dart';
1516
import '../asset/exceptions.dart';
@@ -60,14 +61,31 @@ class BuildStepImpl implements BuildStep {
6061

6162
final void Function(Iterable<AssetId>)? _reportUnusedAssets;
6263

63-
BuildStepImpl(this.inputId, Iterable<AssetId> expectedOutputs, this._reader,
64-
this._writer, this._resolvers, this._resourceManager,
64+
final Future<PackageConfig> Function() _resolvePackageConfig;
65+
Future<Result<PackageConfig>>? _resolvedPackageConfig;
66+
67+
BuildStepImpl(
68+
this.inputId,
69+
Iterable<AssetId> expectedOutputs,
70+
this._reader,
71+
this._writer,
72+
this._resolvers,
73+
this._resourceManager,
74+
this._resolvePackageConfig,
6575
{StageTracker? stageTracker,
6676
void Function(Iterable<AssetId>)? reportUnusedAssets})
6777
: allowedOutputs = UnmodifiableSetView(expectedOutputs.toSet()),
6878
_stageTracker = stageTracker ?? NoOpStageTracker.instance,
6979
_reportUnusedAssets = reportUnusedAssets;
7080

81+
@override
82+
Future<PackageConfig> get packageConfig async {
83+
final resolved =
84+
_resolvedPackageConfig ??= Result.capture(_resolvePackageConfig());
85+
86+
return (await resolved).asFuture;
87+
}
88+
7189
@override
7290
Resolver get resolver {
7391
if (_isComplete) throw BuildStepCompletedException();

build/lib/src/generate/run_builder.dart

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
4+
import 'dart:isolate';
5+
46
import 'package:logging/logging.dart';
7+
import 'package:package_config/package_config.dart';
58

69
import '../analyzer/resolver.dart';
710
import '../asset/id.dart';
@@ -28,22 +31,50 @@ import 'expected_outputs.dart';
2831
/// If [reportUnusedAssetsForInput] is provided then all calls to
2932
/// `BuildStep.reportUnusedAssets` in [builder] will be forwarded to this
3033
/// function with the associated primary input.
31-
Future<void> runBuilder(Builder builder, Iterable<AssetId> inputs,
32-
AssetReader reader, AssetWriter writer, Resolvers? resolvers,
33-
{Logger? logger,
34-
ResourceManager? resourceManager,
35-
StageTracker stageTracker = NoOpStageTracker.instance,
36-
void Function(AssetId input, Iterable<AssetId> assets)?
37-
reportUnusedAssetsForInput}) async {
34+
Future<void> runBuilder(
35+
Builder builder,
36+
Iterable<AssetId> inputs,
37+
AssetReader reader,
38+
AssetWriter writer,
39+
Resolvers? resolvers, {
40+
Logger? logger,
41+
ResourceManager? resourceManager,
42+
StageTracker stageTracker = NoOpStageTracker.instance,
43+
void Function(AssetId input, Iterable<AssetId> assets)?
44+
reportUnusedAssetsForInput,
45+
PackageConfig? packageConfig,
46+
}) async {
3847
var shouldDisposeResourceManager = resourceManager == null;
3948
final resources = resourceManager ?? ResourceManager();
4049
logger ??= Logger('runBuilder');
50+
51+
PackageConfig? transformedConfig;
52+
53+
Future<PackageConfig> loadPackageConfig() async {
54+
if (transformedConfig != null) return transformedConfig!;
55+
56+
var config = packageConfig;
57+
if (config == null) {
58+
final uri = await Isolate.packageConfig;
59+
60+
if (uri == null) {
61+
throw UnsupportedError(
62+
'Isolate running the build does not have a package config and no '
63+
'fallback has been provided');
64+
}
65+
66+
config = await loadPackageConfigUri(uri);
67+
}
68+
69+
return transformedConfig = config.transformToAssetUris();
70+
}
71+
4172
//TODO(nbosch) check overlapping outputs?
4273
Future<void> buildForInput(AssetId input) async {
4374
var outputs = expectedOutputs(builder, input);
4475
if (outputs.isEmpty) return;
4576
var buildStep = BuildStepImpl(
46-
input, outputs, reader, writer, resolvers, resources,
77+
input, outputs, reader, writer, resolvers, resources, loadPackageConfig,
4778
stageTracker: stageTracker,
4879
reportUnusedAssets: reportUnusedAssetsForInput == null
4980
? null
@@ -62,3 +93,28 @@ Future<void> runBuilder(Builder builder, Iterable<AssetId> inputs,
6293
await resources.beforeExit();
6394
}
6495
}
96+
97+
extension on Package {
98+
static final _lib = Uri.parse('lib/');
99+
100+
Package transformToAssetUris() {
101+
return Package(
102+
name,
103+
Uri(scheme: 'asset', pathSegments: [name, '']),
104+
packageUriRoot: _lib,
105+
extraData: extraData,
106+
languageVersion: languageVersion,
107+
);
108+
}
109+
}
110+
111+
extension on PackageConfig {
112+
PackageConfig transformToAssetUris() {
113+
return PackageConfig(
114+
[
115+
for (final package in packages) package.transformToAssetUris(),
116+
],
117+
extraData: extraData,
118+
);
119+
}
120+
}

build/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: build
2-
version: 2.3.2-dev
2+
version: 2.4.0-dev
33
description: A package for authoring build_runner compatible code generators.
44
repository: https://github.com/dart-lang/build/tree/master/build
55

@@ -14,6 +14,7 @@ dependencies:
1414
glob: ^2.0.0
1515
logging: ^1.0.0
1616
meta: ^1.3.0
17+
package_config: ^2.1.0
1718
path: ^1.8.0
1819

1920
dev_dependencies:

build/test/builder/build_step_impl_test.dart

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:build/src/builder/build_step.dart';
1010
import 'package:build/src/builder/build_step_impl.dart';
1111
import 'package:build_resolvers/build_resolvers.dart';
1212
import 'package:build_test/build_test.dart';
13+
import 'package:package_config/package_config.dart';
1314
import 'package:test/test.dart';
1415

1516
void main() {
@@ -34,7 +35,7 @@ void main() {
3435
primary = makeAssetId();
3536
outputs = List.generate(5, (index) => makeAssetId());
3637
buildStep = BuildStepImpl(primary, outputs, reader, writer,
37-
AnalyzerResolvers(), resourceManager);
38+
AnalyzerResolvers(), resourceManager, _unsupported);
3839
});
3940

4041
test('doesnt allow non-expected outputs', () {
@@ -87,8 +88,15 @@ void main() {
8788
};
8889
addAssets(inputs, writer);
8990
var outputId = AssetId.parse('$primary.copy');
90-
var buildStep = BuildStepImpl(primary, [outputId], reader, writer,
91-
AnalyzerResolvers(), resourceManager);
91+
var buildStep = BuildStepImpl(
92+
primary,
93+
[outputId],
94+
reader,
95+
writer,
96+
AnalyzerResolvers(),
97+
resourceManager,
98+
_unsupported,
99+
);
92100

93101
await builder.build(buildStep);
94102
await buildStep.complete();
@@ -112,8 +120,8 @@ void main() {
112120
addAssets(inputs, writer);
113121

114122
var primary = makeAssetId('a|web/a.dart');
115-
var buildStep = BuildStepImpl(
116-
primary, [], reader, writer, AnalyzerResolvers(), resourceManager);
123+
var buildStep = BuildStepImpl(primary, [], reader, writer,
124+
AnalyzerResolvers(), resourceManager, _unsupported);
117125
var resolver = buildStep.resolver;
118126

119127
var aLib = await resolver.libraryFor(primary);
@@ -143,7 +151,7 @@ void main() {
143151
outputId = makeAssetId('a|test.txt');
144152
outputContent = '$outputId';
145153
buildStep = BuildStepImpl(primary, [outputId], StubAssetReader(),
146-
assetWriter, AnalyzerResolvers(), resourceManager);
154+
assetWriter, AnalyzerResolvers(), resourceManager, _unsupported);
147155
});
148156

149157
test('Completes only after writes finish', () async {
@@ -191,7 +199,7 @@ void main() {
191199
primary = makeAssetId();
192200
output = makeAssetId();
193201
buildStep = BuildStepImpl(primary, [output], reader, writer,
194-
AnalyzerResolvers(), resourceManager,
202+
AnalyzerResolvers(), resourceManager, _unsupported,
195203
stageTracker: NoOpStageTracker.instance);
196204
});
197205

@@ -205,8 +213,8 @@ void main() {
205213
var reader = StubAssetReader();
206214
var writer = StubAssetWriter();
207215
var unused = <AssetId>{};
208-
var buildStep = BuildStepImpl(
209-
makeAssetId(), [], reader, writer, AnalyzerResolvers(), resourceManager,
216+
var buildStep = BuildStepImpl(makeAssetId(), [], reader, writer,
217+
AnalyzerResolvers(), resourceManager, _unsupported,
210218
reportUnusedAssets: unused.addAll);
211219
var reported = [
212220
makeAssetId(),
@@ -234,3 +242,7 @@ class SlowAssetWriter implements AssetWriter {
234242
{Encoding encoding = utf8}) =>
235243
_writeCompleter.future;
236244
}
245+
246+
Future<PackageConfig> _unsupported() {
247+
return Future.error(UnsupportedError('stub'));
248+
}

build/test/generate/run_builder_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66

77
import 'package:build/build.dart';
88
import 'package:build_test/build_test.dart';
9+
import 'package:package_config/package_config_types.dart';
910
import 'package:test/test.dart';
1011

1112
void main() {
@@ -67,6 +68,48 @@ void main() {
6768
expect(resourceDisposed, true);
6869
});
6970
});
71+
72+
group('can resolve package config', () {
73+
setUp(() {
74+
writer.assets[makeAssetId('build|lib/foo.txt')] = [1, 2, 3];
75+
76+
builder = TestBuilder(extraWork: (buildStep, __) async {
77+
final config = await buildStep.packageConfig;
78+
79+
final buildPackage =
80+
config.packages.singleWhere((p) => p.name == 'build');
81+
expect(buildPackage.root, Uri.parse('asset:build/'));
82+
expect(buildPackage.packageUriRoot, Uri.parse('asset:build/lib/'));
83+
expect(buildPackage.languageVersion, LanguageVersion(2, 18));
84+
85+
final resolvedBuildUri =
86+
config.resolve(Uri.parse('package:build/foo.txt'))!;
87+
expect(
88+
await buildStep.canRead(AssetId.resolve(resolvedBuildUri)), isTrue);
89+
});
90+
});
91+
92+
test('from default', () async {
93+
await runBuilder(builder, inputs.keys, reader, writer, null);
94+
});
95+
96+
test('when provided', () async {
97+
await runBuilder(
98+
builder,
99+
inputs.keys,
100+
reader,
101+
writer,
102+
null,
103+
packageConfig: PackageConfig([
104+
Package(
105+
'build',
106+
Uri.file('/foo/bar/'),
107+
languageVersion: LanguageVersion(2, 18),
108+
),
109+
]),
110+
);
111+
});
112+
});
70113
}
71114

72115
class TrackingResourceManager extends ResourceManager {

build_runner_core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 7.2.8-dev
22

33
- Raise the minimum SDK constraint to 2.18.
4+
- Optimize `BuildStep.packageConfig`
45

56
## 7.2.7
67

build_runner_core/lib/src/generate/build_impl.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ class _SingleBuild {
536536
stageTracker: tracker,
537537
reportUnusedAssetsForInput: (_, assets) =>
538538
unusedAssets.addAll(assets),
539+
packageConfig: _packageGraph.asPackageConfig,
539540
).catchError((void _) {
540541
// Errors tracked through the logger
541542
}));

build_runner_core/lib/src/package_graph/package_graph.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ class PackageGraph {
2828
/// All [PackageNode]s indexed by package name.
2929
final Map<String, PackageNode> allPackages;
3030

31+
/// A [PackageConfig] representation of this package graph.
32+
final PackageConfig asPackageConfig;
33+
3134
PackageGraph._(this.root, Map<String, PackageNode> allPackages)
32-
: allPackages = Map.unmodifiable(
35+
: asPackageConfig = _packagesToConfig(allPackages.values),
36+
allPackages = Map.unmodifiable(
3337
Map<String, PackageNode>.from(allPackages)
3438
..putIfAbsent(r'$sdk', () => _sdkPackageNode)) {
3539
if (!root.isRoot) {
@@ -113,6 +117,22 @@ class PackageGraph {
113117
static Future<PackageGraph> forThisPackage() =>
114118
PackageGraph.forPath(p.current);
115119

120+
static PackageConfig _packagesToConfig(Iterable<PackageNode> packages) {
121+
final relativeLib = Uri.parse('lib/');
122+
123+
return PackageConfig([
124+
for (final package in packages)
125+
if (package.name != _sdkPackageNode.name)
126+
Package(
127+
package.name,
128+
Uri.file(
129+
package.path.endsWith('/') ? package.path : '${package.path}/'),
130+
languageVersion: package.languageVersion,
131+
packageUriRoot: relativeLib,
132+
),
133+
]);
134+
}
135+
116136
/// Shorthand to get a package by name.
117137
PackageNode? operator [](String packageName) => allPackages[packageName];
118138

0 commit comments

Comments
 (0)