/
module_library.dart
243 lines (215 loc) · 8.78 KB
/
module_library.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:build/build.dart';
import 'platform.dart';
/// A Dart library within a module.
///
/// Modules can be computed based on library dependencies (imports and exports)
/// and parts.
class ModuleLibrary {
/// The AssetId of the original Dart source file.
final AssetId id;
/// Whether this library can be imported.
///
/// This will be false if the source file is a "part of", or imports code that
/// can't be used outside the SDK.
final bool isImportable;
/// Whether this library is an entrypoint.
///
/// True if the library is in `lib/` but not `lib/src`, or if it is outside of
/// `lib/` and contains a `main` method. Always false if this is not an
/// importable library.
final bool isEntryPoint;
/// Deps that are imported with a conditional import.
///
/// Keys are the stringified ast node for the conditional, and the default
/// import is under the magic `$default` key.
final List<Map<String, AssetId>> conditionalDeps;
/// The IDs of libraries that are imported or exported by this library.
final Set<AssetId> _deps;
/// The "part" files for this library.
final Set<AssetId> parts;
/// The `dart:` libraries that this library directly depends on.
final Set<String> sdkDeps;
/// Whether this library has a `main` function.
final bool hasMain;
ModuleLibrary._(this.id,
{required this.isEntryPoint,
required Set<AssetId> deps,
required this.parts,
required this.conditionalDeps,
required this.sdkDeps,
required this.hasMain})
: _deps = deps,
isImportable = true;
ModuleLibrary._nonImportable(this.id)
: isImportable = false,
isEntryPoint = false,
_deps = const {},
parts = const {},
conditionalDeps = const [],
sdkDeps = const {},
hasMain = false;
factory ModuleLibrary._fromCompilationUnit(
AssetId id, bool isEntryPoint, CompilationUnit parsed) {
var deps = <AssetId>{};
var parts = <AssetId>{};
var sdkDeps = <String>{};
var conditionalDeps = <Map<String, AssetId>>[];
for (var directive in parsed.directives) {
if (directive is! UriBasedDirective) continue;
var path = directive.uri.stringValue;
if (path == null) continue;
List<Configuration>? conditionalDirectiveConfigurations;
if (directive is ImportDirective && directive.configurations.isNotEmpty) {
conditionalDirectiveConfigurations = directive.configurations;
} else if (directive is ExportDirective &&
directive.configurations.isNotEmpty) {
conditionalDirectiveConfigurations = directive.configurations;
}
var uri = Uri.parse(path);
if (uri.isScheme('dart-ext')) {
// TODO: What should we do for native extensions?
continue;
}
if (uri.scheme == 'dart') {
if (conditionalDirectiveConfigurations != null) {
_checkValidConditionalImport(uri, id, directive);
}
sdkDeps.add(uri.path);
continue;
}
var linkedId = AssetId.resolve(uri, from: id);
if (directive is PartDirective) {
parts.add(linkedId);
continue;
}
if (conditionalDirectiveConfigurations != null) {
var conditions = <String, AssetId>{r'$default': linkedId};
for (var condition in conditionalDirectiveConfigurations) {
var uriString = condition.uri.stringValue;
var parsedUri = uriString == null ? null : Uri.parse(uriString);
_checkValidConditionalImport(parsedUri, id, directive);
parsedUri = parsedUri!;
conditions[condition.name.toSource()] =
AssetId.resolve(parsedUri, from: id);
}
conditionalDeps.add(conditions);
} else {
deps.add(linkedId);
}
}
return ModuleLibrary._(id,
isEntryPoint: isEntryPoint,
deps: deps,
parts: parts,
sdkDeps: sdkDeps,
conditionalDeps: conditionalDeps,
hasMain: _hasMainMethod(parsed));
}
static void _checkValidConditionalImport(
Uri? parsedUri, AssetId id, UriBasedDirective node) {
if (parsedUri == null) {
throw ArgumentError(
'Unsupported conditional import with non-constant uri found in $id:'
'\n\n${node.toSource()}');
} else if (parsedUri.scheme == 'dart') {
throw ArgumentError(
'Unsupported conditional import of `$parsedUri` found in $id:\n\n'
'${node.toSource()}\n\nThis environment does not support direct '
'conditional imports of `dart:` libraries. Instead you must create '
'a separate library which unconditionally imports (or exports) the '
'`dart:` library that you want to use, and conditionally import (or '
'export) that library.');
}
}
/// Parse the directives from [source] and compute the library information.
static ModuleLibrary fromSource(AssetId id, String source) {
final isLibDir = id.path.startsWith('lib/');
final parsed = parseString(content: source, throwIfDiagnostics: false).unit;
// Packages within the SDK but published might have libraries that can't be
// used outside the SDK.
if (parsed.directives.any((d) =>
d is UriBasedDirective &&
d.uri.stringValue?.startsWith('dart:_') == true &&
id.package != 'dart_internal')) {
return ModuleLibrary._nonImportable(id);
}
if (_isPart(parsed)) {
return ModuleLibrary._nonImportable(id);
}
final isEntryPoint =
(isLibDir && !id.path.startsWith('lib/src/')) || _hasMainMethod(parsed);
return ModuleLibrary._fromCompilationUnit(id, isEntryPoint, parsed);
}
/// Parses the output of [serialize] back into a [ModuleLibrary].
///
/// Importable libraries can be round tripped to a String. Non-importable
/// libraries should not be printed or parsed.
factory ModuleLibrary.deserialize(AssetId id, String encoded) {
var json = jsonDecode(encoded);
return ModuleLibrary._(id,
isEntryPoint: json['isEntrypoint'] as bool,
deps: _deserializeAssetIds(json['deps'] as Iterable),
parts: _deserializeAssetIds(json['parts'] as Iterable),
sdkDeps: Set.of((json['sdkDeps'] as Iterable).cast<String>()),
conditionalDeps:
(json['conditionalDeps'] as Iterable).map((conditions) {
return Map.of((conditions as Map<String, dynamic>)
.map((k, v) => MapEntry(k, AssetId.parse(v as String))));
}).toList(),
hasMain: json['hasMain'] as bool);
}
String serialize() => jsonEncode({
'isEntrypoint': isEntryPoint,
'deps': _deps.map((id) => id.toString()).toList(),
'parts': parts.map((id) => id.toString()).toList(),
'conditionalDeps': conditionalDeps
.map((conditions) =>
conditions.map((k, v) => MapEntry(k, v.toString())))
.toList(),
'sdkDeps': sdkDeps.toList(),
'hasMain': hasMain,
});
List<AssetId> depsForPlatform(DartPlatform platform) {
AssetId depForConditions(Map<String, AssetId> conditions) {
var selectedImport = conditions[r'$default']!;
for (var condition in conditions.keys) {
if (condition == r'$default') continue;
if (!condition.startsWith('dart.library.')) {
throw UnsupportedError(
'$condition not supported for config specific imports. Only the '
'dart.library.<name> constants are supported.');
}
var library = condition.substring('dart.library.'.length);
if (platform.supportsLibrary(library)) {
selectedImport = conditions[condition]!;
break;
}
}
return selectedImport;
}
return [
..._deps,
for (var conditions in conditionalDeps) depForConditions(conditions)
];
}
}
Set<AssetId> _deserializeAssetIds(Iterable serlialized) =>
Set.from(serlialized.map((decoded) => AssetId.parse(decoded as String)));
bool _isPart(CompilationUnit dart) =>
dart.directives.any((directive) => directive is PartOfDirective);
/// Allows two or fewer arguments to `main` so that entrypoints intended for
/// use with `spawnUri` get counted.
//
// TODO: This misses the case where a Dart file doesn't contain main(),
// but has a part that does, or it exports a `main` from another library.
bool _hasMainMethod(CompilationUnit dart) => dart.declarations.any((node) =>
node is FunctionDeclaration &&
node.name.lexeme == 'main' &&
node.functionExpression.parameters != null &&
node.functionExpression.parameters!.parameters.length <= 2);