Skip to content

Commit 01590aa

Browse files
authored
Refactor native asset integration into flutter tools (flutter#158932)
Currently the `NativeAsset` target in flutter tools is responsible for two things: * performing the dart build (in the app as well as all transitive pub dependencies) * taking output (shared libraries) from this build and copying them around This intermingling of responsibilities leads to more complex code and potentially unnecessary work: If the source code changed (e.g. `.c` files change) we have to run the dart build again. But doing so may result in the same shared libraries (e.g. adding comments to the `.c` code). Currently we're going to copy the shared libraries despite them having not changed, which then may cause upstream things to be dirtied (if it's based on timestamp of files) and re-built. Instead this PR splits this `NativeAsset` into the two orthogonal pieces * `DartBuild` target that is responsible for the dart build * `InstallCodeAssets` that is responsible for copying shared libraries to the right place and producing a `native_assets.yaml`. This decoupling is also preparation for a future where a dart build can produce other kinds of assets (e.g. data assets) and is used in the web build as well. (The web build would use `DartBuild` but not `InstalCodeAssets`).
1 parent 32133ce commit 01590aa

File tree

12 files changed

+367
-210
lines changed

12 files changed

+367
-210
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ class KernelSnapshotNativeAssets extends Target {
289289

290290
@override
291291
List<Source> get inputs => <Source>[
292-
const Source.pattern('{BUILD_DIR}/native_assets.yaml'),
292+
const Source.pattern('{BUILD_DIR}/${InstallCodeAssets.nativeAssetsFilename}'),
293293
...const KernelSnapshotProgram().inputs,
294294
];
295295

@@ -302,15 +302,15 @@ class KernelSnapshotNativeAssets extends Target {
302302
List<String> get depfiles => const <String>[];
303303

304304
@override
305-
List<Target> get dependencies => <Target>[
306-
const NativeAssets(),
305+
List<Target> get dependencies => const <Target>[
306+
InstallCodeAssets(),
307307
];
308308

309309
static const String dillName = 'native_assets.dill';
310310

311311
@override
312312
Future<void> build(Environment environment) async {
313-
final File nativeAssetsFile = environment.buildDir.childFile('native_assets.yaml');
313+
final File nativeAssetsFile = environment.buildDir.childFile(InstallCodeAssets.nativeAssetsFilename);
314314
final File dillFile = environment.buildDir.childFile(dillName);
315315

316316
final YamlNode nativeAssetContents = loadYamlNode(await nativeAssetsFile.readAsString());

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

Lines changed: 113 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,43 @@
33
// found in the LICENSE file.
44

55
import 'package:meta/meta.dart';
6-
import 'package:native_assets_builder/native_assets_builder.dart';
76
import 'package:package_config/package_config_types.dart';
87

98
import '../../base/common.dart';
109
import '../../base/file_system.dart';
1110
import '../../build_info.dart';
11+
import '../../convert.dart';
1212
import '../../dart/package_map.dart';
1313
import '../../isolated/native_assets/native_assets.dart';
1414
import '../build_system.dart';
1515
import '../depfile.dart';
1616
import '../exceptions.dart';
1717
import 'common.dart';
1818

19-
/// Builds the right native assets for a Flutter app.
20-
///
21-
/// The build mode and target architecture can be changed from the
22-
/// native build project (Xcode etc.), so only `flutter assemble` has the
23-
/// information about build-mode and target architecture.
24-
/// Invocations of flutter_tools other than `flutter assemble` are dry runs.
25-
///
26-
/// This step needs to be consistent with the dry run invocations in `flutter
27-
/// run`s so that the kernel mapping of asset id to dylib lines up after hot
28-
/// restart.
29-
///
30-
/// [KernelSnapshot] depends on this target. We produce a native_assets.yaml
31-
/// here, and embed that mapping inside the kernel snapshot.
32-
///
33-
/// The build always produces a valid native_assets.yaml and a native_assets.d
34-
/// even if there are no native assets. This way the caching logic won't try to
35-
/// rebuild.
36-
class NativeAssets extends Target {
37-
const NativeAssets({
19+
/// Runs the dart build of the app.
20+
abstract class DartBuild extends Target {
21+
const DartBuild({
3822
@visibleForTesting FlutterNativeAssetsBuildRunner? buildRunner,
3923
}) : _buildRunner = buildRunner;
4024

4125
final FlutterNativeAssetsBuildRunner? _buildRunner;
4226

4327
@override
4428
Future<void> build(Environment environment) async {
45-
final String? nativeAssetsEnvironment = environment.defines[kNativeAssets];
4629
final FileSystem fileSystem = environment.fileSystem;
47-
final Uri nativeAssetsFileUri = environment.buildDir.childFile('native_assets.yaml').uri;
30+
final String? nativeAssetsEnvironment = environment.defines[kNativeAssets];
4831

4932
final DartBuildResult result;
5033
if (nativeAssetsEnvironment == 'false') {
5134
result = const DartBuildResult.empty();
52-
await writeNativeAssetsYaml(KernelAssets(), nativeAssetsFileUri, fileSystem);
5335
} else {
54-
final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
55-
if (targetPlatformEnvironment == null) {
56-
throw MissingDefineException(kTargetPlatform, name);
57-
}
58-
final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment);
59-
final Uri projectUri = environment.projectDir.uri;
36+
final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name);
6037

6138
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
6239
fileSystem.file(environment.packageConfigPath),
6340
logger: environment.logger,
6441
);
42+
final Uri projectUri = environment.projectDir.uri;
6543
final FlutterNativeAssetsBuildRunner buildRunner = _buildRunner ??
6644
FlutterNativeAssetsBuildRunnerImpl(
6745
projectUri,
@@ -70,62 +48,154 @@ class NativeAssets extends Target {
7048
fileSystem,
7149
environment.logger,
7250
);
73-
74-
(result, _) = await runFlutterSpecificDartBuild(
51+
result = await runFlutterSpecificDartBuild(
7552
environmentDefines: environment.defines,
7653
buildRunner: buildRunner,
7754
targetPlatform: targetPlatform,
7855
projectUri: projectUri,
79-
nativeAssetsYamlUri : nativeAssetsFileUri,
8056
fileSystem: fileSystem,
8157
);
8258
}
8359

60+
final File dartBuildResultJsonFile = environment.buildDir.childFile(dartBuildResultFilename);
61+
if (!dartBuildResultJsonFile.parent.existsSync()) {
62+
dartBuildResultJsonFile.parent.createSync(recursive: true);
63+
}
64+
dartBuildResultJsonFile.writeAsStringSync(json.encode(result.toJson()));
65+
8466
final Depfile depfile = Depfile(
8567
<File>[
8668
for (final Uri dependency in result.dependencies) fileSystem.file(dependency),
8769
],
8870
<File>[
89-
fileSystem.file(nativeAssetsFileUri),
71+
fileSystem.file(dartBuildResultJsonFile),
9072
],
9173
);
92-
final File outputDepfile = environment.buildDir.childFile('native_assets.d');
74+
final File outputDepfile = environment.buildDir.childFile(depFilename);
9375
if (!outputDepfile.parent.existsSync()) {
9476
outputDepfile.parent.createSync(recursive: true);
9577
}
9678
environment.depFileService.writeToFile(depfile, outputDepfile);
97-
if (!await fileSystem.file(nativeAssetsFileUri).exists()) {
98-
throwToolExit("${nativeAssetsFileUri.path} doesn't exist.");
99-
}
10079
if (!await outputDepfile.exists()) {
10180
throwToolExit("${outputDepfile.path} doesn't exist.");
10281
}
10382
}
10483

10584
@override
106-
List<String> get depfiles => <String>[
107-
'native_assets.d',
85+
List<String> get depfiles => const <String>[depFilename];
86+
87+
@override
88+
List<Source> get inputs => const <Source>[
89+
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart'),
90+
// If different packages are resolved, different native assets might need to be built.
91+
Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config_subset'),
92+
// TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263
93+
];
94+
95+
@override
96+
String get name => 'dart_build';
97+
98+
@override
99+
List<Source> get outputs => const <Source>[
100+
Source.pattern('{BUILD_DIR}/$dartBuildResultFilename'),
108101
];
109102

103+
/// Dependent build [Target]s can use this to consume the result of the
104+
/// [DartBuild] target.
105+
static Future<DartBuildResult> loadBuildResult(Environment environment) async {
106+
final File dartBuildResultJsonFile = environment.buildDir.childFile(DartBuild.dartBuildResultFilename);
107+
return DartBuildResult.fromJson(json.decode(dartBuildResultJsonFile.readAsStringSync()) as Map<String, Object?>);
108+
}
109+
110+
static const String dartBuildResultFilename = 'dart_build_result.json';
111+
static const String depFilename = 'dart_build.d';
112+
}
113+
114+
class DartBuildForNative extends DartBuild {
115+
const DartBuildForNative({@visibleForTesting super.buildRunner});
116+
110117
@override
111118
List<Target> get dependencies => const <Target>[
112-
// In AOT, depends on tree-shaking information (resources.json) from compiling dart.
113119
KernelSnapshotProgram(),
114120
];
121+
}
122+
123+
/// Installs the code assets from a [DartBuild] Flutter app.
124+
///
125+
/// The build mode and target architecture can be changed from the
126+
/// native build project (Xcode etc.), so only `flutter assemble` has the
127+
/// information about build-mode and target architecture.
128+
/// Invocations of flutter_tools other than `flutter assemble` are dry runs.
129+
///
130+
/// This step needs to be consistent with the dry run invocations in `flutter
131+
/// run`s so that the kernel mapping of asset id to dylib lines up after hot
132+
/// restart.
133+
class InstallCodeAssets extends Target {
134+
const InstallCodeAssets();
135+
136+
@override
137+
Future<void> build(Environment environment) async {
138+
final Uri projectUri = environment.projectDir.uri;
139+
final FileSystem fileSystem = environment.fileSystem;
140+
final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name);
141+
142+
// We fetch the result from the [DartBuild].
143+
final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment);
144+
145+
// And install/copy the code assets to the right place and create a
146+
// native_asset.yaml that can be used by the final AOT compilation.
147+
final Uri nativeAssetsFileUri = environment.buildDir.childFile(nativeAssetsFilename).uri;
148+
await installCodeAssets(dartBuildResult: dartBuildResult, environmentDefines: environment.defines,
149+
targetPlatform: targetPlatform, projectUri: projectUri, fileSystem: fileSystem,
150+
nativeAssetsFileUri: nativeAssetsFileUri);
151+
assert(await fileSystem.file(nativeAssetsFileUri).exists());
152+
153+
final Depfile depfile = Depfile(
154+
<File>[
155+
for (final Uri file in dartBuildResult.filesToBeBundled) fileSystem.file(file),
156+
],
157+
<File>[
158+
fileSystem.file(nativeAssetsFileUri),
159+
],
160+
);
161+
final File outputDepfile = environment.buildDir.childFile(depFilename);
162+
environment.depFileService.writeToFile(depfile, outputDepfile);
163+
if (!await outputDepfile.exists()) {
164+
throwToolExit("${outputDepfile.path} doesn't exist.");
165+
}
166+
}
167+
168+
@override
169+
List<String> get depfiles => <String>[depFilename];
170+
171+
@override
172+
List<Target> get dependencies => const <Target>[
173+
DartBuildForNative(),
174+
];
115175

116176
@override
117177
List<Source> get inputs => const <Source>[
118178
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart'),
119179
// If different packages are resolved, different native assets might need to be built.
120180
Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config_subset'),
121-
// TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263
122181
];
123182

124183
@override
125-
String get name => 'native_assets';
184+
String get name => 'install_code_assets';
126185

127186
@override
128187
List<Source> get outputs => const <Source>[
129-
Source.pattern('{BUILD_DIR}/native_assets.yaml'),
188+
Source.pattern('{BUILD_DIR}/$nativeAssetsFilename'),
130189
];
190+
191+
static const String nativeAssetsFilename = 'native_assets.yaml';
192+
static const String depFilename = 'install_code_assets.d';
193+
}
194+
195+
TargetPlatform _getTargetPlatformFromEnvironment(Environment environment, String name) {
196+
final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
197+
if (targetPlatformEnvironment == null) {
198+
throw MissingDefineException(kTargetPlatform, name);
199+
}
200+
return getTargetPlatformForName(targetPlatformEnvironment);
131201
}

0 commit comments

Comments
 (0)