Skip to content

Commit e410b7c

Browse files
authored
[native_assets] Use kernel concatenation (#147158)
Uses kernel concatenation to concatenate the `program.dill` and `native_assets.dill`. This will enable compiling `native_assets.dill` after `program.dill` so that tree-shaking information can be used. Issue: * flutter/flutter#146270 ## Implementation choices * We can either run the frontend_server twice as a process invocation (current PR), or keep the process running after the dart compilation and then shut it down after native assets compilation to kernel. Keeping the process running would require more coordination between different `Target`s. * We can either chose to only do separate kernel file compilation in release (AOT) builds, or do it both for JIT and AOT (current PR). Doing it differently in JIT/AOT means more conditionals in lib/src/build_system/targets/, doing it single-shot in JIT might be more performant though. Update: Both of these are mitigated by not running the kernel compiler process if there are no native assets at all. ## Implementation notes This only updates `flutter assemble`. The kernel compilation calls in `flutter run` do not require kernel concatenation. The native-assets-kernel-mapping in `flutter run` contains results from `--dry-run` invocations of `hook/build.dart` (and in the future `hook/link.dart`). These `--dry-run` invocations do not get access to tree-shaking information, so the `link` hook to be introduced later can be run before Dart compilation. ## Tests * Updated the `Target`s tests to reflect the new implementation. ## Related CLs * frontend_server support: https://dart-review.googlesource.com/c/sdk/+/363567 * Same PR for Dart standalone: https://dart-review.googlesource.com/c/sdk/+/362384
1 parent d2631b5 commit e410b7c

File tree

4 files changed

+269
-99
lines changed

4 files changed

+269
-99
lines changed

packages/flutter_tools/lib/src/build_system/targets/common.dart

Lines changed: 156 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'package:package_config/package_config.dart';
6+
import 'package:yaml/yaml.dart';
67

78
import '../../artifacts.dart';
89
import '../../base/build.dart';
@@ -121,15 +122,17 @@ class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
121122
/// even though it is not listed as an input. Pub inserts a timestamp into
122123
/// the file which causes unnecessary rebuilds, so instead a subset of the contents
123124
/// are used an input instead.
124-
class KernelSnapshot extends Target {
125-
const KernelSnapshot();
125+
///
126+
/// This kernel snapshot is concatenated with the [KernelSnapshotNativeAssets]
127+
/// inside [KernelSnapshot] byte-wise to create the combined kernel snapshot.
128+
class KernelSnapshotProgram extends Target {
129+
const KernelSnapshotProgram();
126130

127131
@override
128-
String get name => 'kernel_snapshot';
132+
String get name => 'kernel_snapshot_program';
129133

130134
@override
131135
List<Source> get inputs => const <Source>[
132-
Source.pattern('{BUILD_DIR}/native_assets.yaml'),
133136
Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
134137
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
135138
Source.artifact(Artifact.platformKernelDill),
@@ -139,7 +142,9 @@ class KernelSnapshot extends Target {
139142
];
140143

141144
@override
142-
List<Source> get outputs => const <Source>[];
145+
List<Source> get outputs => const <Source>[
146+
// TODO(mosuem): Should output resources.json. https://github.com/flutter/flutter/issues/146263
147+
];
143148

144149
@override
145150
List<String> get depfiles => <String>[
@@ -148,11 +153,12 @@ class KernelSnapshot extends Target {
148153

149154
@override
150155
List<Target> get dependencies => const <Target>[
151-
NativeAssets(),
152156
GenerateLocalizationsTarget(),
153157
DartPluginRegistrantTarget(),
154158
];
155159

160+
static const String dillName = 'program.dill';
161+
156162
@override
157163
Future<void> build(Environment environment) async {
158164
final KernelCompiler compiler = KernelCompiler(
@@ -186,13 +192,6 @@ class KernelSnapshot extends Target {
186192
final List<String>? fileSystemRoots = environment.defines[kFileSystemRoots]?.split(',');
187193
final String? fileSystemScheme = environment.defines[kFileSystemScheme];
188194

189-
final File nativeAssetsFile = environment.buildDir.childFile('native_assets.yaml');
190-
final String nativeAssets = nativeAssetsFile.path;
191-
if (!await nativeAssetsFile.exists()) {
192-
throwToolExit("$nativeAssets doesn't exist.");
193-
}
194-
environment.logger.printTrace('Embedding native assets mapping $nativeAssets in kernel.');
195-
196195
TargetModel targetModel = TargetModel.flutter;
197196
if (targetPlatform == TargetPlatform.fuchsia_x64 ||
198197
targetPlatform == TargetPlatform.fuchsia_arm64) {
@@ -242,6 +241,8 @@ class KernelSnapshot extends Target {
242241
logger: environment.logger,
243242
);
244243

244+
final String dillPath = environment.buildDir.childFile(dillName).path;
245+
245246
final CompilerOutput? output = await compiler.compile(
246247
sdkRoot: environment.artifacts.getArtifactPath(
247248
Artifact.flutterPatchedSdkPath,
@@ -252,9 +253,8 @@ class KernelSnapshot extends Target {
252253
buildMode: buildMode,
253254
trackWidgetCreation: trackWidgetCreation && buildMode != BuildMode.release,
254255
targetModel: targetModel,
255-
outputFilePath: environment.buildDir.childFile('app.dill').path,
256-
initializeFromDill: buildMode.isPrecompiled ? null :
257-
environment.buildDir.childFile('app.dill').path,
256+
outputFilePath: dillPath,
257+
initializeFromDill: buildMode.isPrecompiled ? null : dillPath,
258258
packagesPath: packagesFile.path,
259259
linkPlatformKernelIn: forceLinkPlatform || buildMode.isPrecompiled,
260260
mainPath: targetFileAbsolute,
@@ -268,6 +268,109 @@ class KernelSnapshot extends Target {
268268
buildDir: environment.buildDir,
269269
targetOS: targetOS,
270270
checkDartPluginRegistry: environment.generateDartPluginRegistry,
271+
);
272+
if (output == null || output.errorCount != 0) {
273+
throw Exception();
274+
}
275+
}
276+
}
277+
278+
/// Generate a kernel snapshot of the native assets mapping for resolving
279+
/// `@Native` assets at runtime.
280+
///
281+
/// This kernel snapshot is concatenated to the [KernelSnapshotProgram]
282+
/// inside [KernelSnapshot] to create the combined kernel snapshot.
283+
class KernelSnapshotNativeAssets extends Target {
284+
const KernelSnapshotNativeAssets();
285+
286+
@override
287+
String get name => 'kernel_snapshot_native_assets';
288+
289+
@override
290+
List<Source> get inputs => <Source>[
291+
const Source.pattern('{BUILD_DIR}/native_assets.yaml'),
292+
...const KernelSnapshotProgram().inputs,
293+
];
294+
295+
@override
296+
List<Source> get outputs => const <Source>[];
297+
298+
@override
299+
List<String> get depfiles => const <String>[];
300+
301+
@override
302+
List<Target> get dependencies => <Target>[
303+
const NativeAssets(),
304+
];
305+
306+
static const String dillName = 'native_assets.dill';
307+
308+
@override
309+
Future<void> build(Environment environment) async {
310+
final File nativeAssetsFile = environment.buildDir.childFile('native_assets.yaml');
311+
final File dillFile = environment.buildDir.childFile(dillName);
312+
313+
final YamlNode nativeAssetContents = loadYamlNode(await nativeAssetsFile.readAsString());
314+
final Object? nativeAssetsInYaml = (nativeAssetContents as Map<Object?, Object?>)['native-assets'];
315+
if (nativeAssetsInYaml is! Map || nativeAssetsInYaml.isEmpty) {
316+
// Write an empty file to make concatenation a no-op.
317+
// Write the file out to disk for caching.
318+
await dillFile.writeAsBytes(<int>[]);
319+
return;
320+
}
321+
322+
final KernelCompiler compiler = KernelCompiler(
323+
fileSystem: environment.fileSystem,
324+
logger: environment.logger,
325+
processManager: environment.processManager,
326+
artifacts: environment.artifacts,
327+
fileSystemRoots: <String>[],
328+
);
329+
final String? buildModeEnvironment = environment.defines[kBuildMode];
330+
if (buildModeEnvironment == null) {
331+
throw MissingDefineException(kBuildMode, 'kernel_snapshot');
332+
}
333+
final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
334+
if (targetPlatformEnvironment == null) {
335+
throw MissingDefineException(kTargetPlatform, 'kernel_snapshot');
336+
}
337+
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
338+
final File packagesFile = environment.projectDir
339+
.childDirectory('.dart_tool')
340+
.childFile('package_config.json');
341+
342+
final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment);
343+
344+
final String? frontendServerStarterPath = environment.defines[kFrontendServerStarterPath];
345+
346+
final String nativeAssets = nativeAssetsFile.path;
347+
if (!await nativeAssetsFile.exists()) {
348+
throwToolExit("$nativeAssets doesn't exist.");
349+
}
350+
environment.logger.printTrace('Embedding native assets mapping $nativeAssets in kernel.');
351+
352+
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
353+
packagesFile,
354+
logger: environment.logger,
355+
);
356+
357+
final String dillPath = dillFile.path;
358+
359+
final CompilerOutput? output = await compiler.compile(
360+
sdkRoot: environment.artifacts.getArtifactPath(
361+
Artifact.flutterPatchedSdkPath,
362+
platform: targetPlatform,
363+
mode: buildMode,
364+
),
365+
aot: buildMode.isPrecompiled,
366+
buildMode: buildMode,
367+
trackWidgetCreation: false,
368+
outputFilePath: dillPath,
369+
packagesPath: packagesFile.path,
370+
frontendServerStarterPath: frontendServerStarterPath,
371+
packageConfig: packageConfig,
372+
buildDir: environment.buildDir,
373+
dartDefines: <String>[],
271374
nativeAssets: nativeAssets,
272375
);
273376
if (output == null || output.errorCount != 0) {
@@ -276,6 +379,43 @@ class KernelSnapshot extends Target {
276379
}
277380
}
278381

382+
class KernelSnapshot extends Target {
383+
const KernelSnapshot();
384+
385+
@override
386+
String get name => 'kernel_snapshot';
387+
388+
@override
389+
List<Target> get dependencies => const <Target>[
390+
KernelSnapshotProgram(),
391+
KernelSnapshotNativeAssets(),
392+
];
393+
394+
@override
395+
List<Source> get inputs => <Source>[];
396+
397+
@override
398+
List<Source> get outputs => <Source>[];
399+
400+
static const String dillName = 'app.dill';
401+
402+
@override
403+
Future<void> build(Environment environment) async {
404+
final File programDill = environment.buildDir.childFile(
405+
KernelSnapshotProgram.dillName,
406+
);
407+
final File nativeAssetsDill = environment.buildDir.childFile(
408+
KernelSnapshotNativeAssets.dillName,
409+
);
410+
final File dill = environment.buildDir.childFile(dillName);
411+
await programDill.copy(dill.path);
412+
await dill.writeAsBytes(
413+
await nativeAssetsDill.readAsBytes(),
414+
mode: FileMode.append,
415+
);
416+
}
417+
}
418+
279419
/// Supports compiling a dart kernel file to an ELF binary.
280420
abstract class AotElfBase extends Target {
281421
const AotElfBase();

packages/flutter_tools/lib/src/build_system/targets/native_assets.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,17 @@ class NativeAssets extends Target {
371371
];
372372

373373
@override
374-
List<Target> get dependencies => <Target>[];
374+
List<Target> get dependencies => const <Target>[
375+
// In AOT, depends on tree-shaking information (resources.json) from compiling dart.
376+
KernelSnapshotProgram(),
377+
];
375378

376379
@override
377380
List<Source> get inputs => const <Source>[
378381
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart'),
379382
// If different packages are resolved, different native assets might need to be built.
380383
Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
384+
// TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263
381385
];
382386

383387
@override

packages/flutter_tools/lib/src/compile.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,14 @@ class KernelCompiler {
247247
sdkRoot = '$sdkRoot/';
248248
}
249249
String? mainUri;
250-
final File mainFile = _fileSystem.file(mainPath);
251-
final Uri mainFileUri = mainFile.uri;
252-
if (packagesPath != null) {
253-
mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
250+
if (mainPath != null) {
251+
final File mainFile = _fileSystem.file(mainPath);
252+
final Uri mainFileUri = mainFile.uri;
253+
if (packagesPath != null) {
254+
mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
255+
}
256+
mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
254257
}
255-
mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
256258
if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) {
257259
_fileSystem.file(outputFilePath).createSync(recursive: true);
258260
}
@@ -359,7 +361,8 @@ class KernelCompiler {
359361
// See: https://github.com/flutter/flutter/issues/103994
360362
'--verbosity=error',
361363
...?extraFrontEndOptions,
362-
mainUri,
364+
if (mainUri != null) mainUri
365+
else '--native-assets-only',
363366
];
364367

365368
_logger.printTrace(command.join(' '));

0 commit comments

Comments
 (0)