1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | // Logic for native assets shared between all host OSes. |
6 | |
7 | import 'package:code_assets/code_assets.dart' ; |
8 | import 'package:hooks/hooks.dart' ; |
9 | import 'package:hooks_runner/hooks_runner.dart' ; |
10 | import 'package:logging/logging.dart' as logging; |
11 | import 'package:package_config/package_config_types.dart' ; |
12 | |
13 | import '../../base/common.dart'; |
14 | import '../../base/file_system.dart'; |
15 | import '../../base/logger.dart'; |
16 | import '../../base/platform.dart'; |
17 | import '../../build_info.dart'; |
18 | import '../../build_system/exceptions.dart'; |
19 | import '../../cache.dart'; |
20 | import '../../convert.dart'; |
21 | import '../../features.dart'; |
22 | import '../../globals.dart' as globals; |
23 | import '../../macos/xcode.dart' as xcode; |
24 | import 'android/native_assets.dart'; |
25 | import 'ios/native_assets.dart'; |
26 | import 'linux/native_assets.dart'; |
27 | import 'macos/native_assets.dart'; |
28 | import 'macos/native_assets_host.dart'; |
29 | import 'windows/native_assets.dart'; |
30 | |
31 | /// The assets produced by a Dart build and the dependencies of those assets. |
32 | /// |
33 | /// If any of the dependencies change, then the Dart build should be performed |
34 | /// again. |
35 | final class DartBuildResult { |
36 | const DartBuildResult(this.codeAssets, this.dependencies); |
37 | |
38 | const DartBuildResult.empty() |
39 | : codeAssets = const <FlutterCodeAsset>[], |
40 | dependencies = const <Uri>[]; |
41 | |
42 | factory DartBuildResult.fromJson(Map<String, Object?> json) { |
43 | final dependencies = <Uri>[ |
44 | for (final Object? encodedUri in json['dependencies' ]! as List<Object?>) |
45 | Uri.parse(encodedUri! as String), |
46 | ]; |
47 | final codeAssets = <FlutterCodeAsset>[ |
48 | for (final Object? json in json['code_assets' ]! as List<Object?>) |
49 | FlutterCodeAsset( |
50 | codeAsset: CodeAsset.fromEncoded( |
51 | EncodedAsset.fromJson( |
52 | (json! as Map<String, Object?>)['asset' ]! as Map<String, Object?>, |
53 | ), |
54 | ), |
55 | target: Target.fromString((json as Map<String, Object?>)['target' ]! as String), |
56 | ), |
57 | ]; |
58 | return DartBuildResult(codeAssets, dependencies); |
59 | } |
60 | |
61 | final List<FlutterCodeAsset> codeAssets; |
62 | final List<Uri> dependencies; |
63 | |
64 | Map<String, Object?> toJson() => <String, Object?>{ |
65 | 'dependencies' : <Object?>[for (final Uri dep in dependencies) dep.toString()], |
66 | 'code_assets' : <Object?>[ |
67 | for (final FlutterCodeAsset code in codeAssets) |
68 | <String, Object>{ |
69 | 'asset' : code.codeAsset.encode().toJson(), |
70 | 'target' : code.target.toString(), |
71 | }, |
72 | ], |
73 | }; |
74 | |
75 | /// The files that eventually should be bundled with the app. |
76 | List<Uri> get filesToBeBundled => <Uri>[ |
77 | for (final FlutterCodeAsset code in codeAssets) |
78 | if (code.codeAsset.linkMode is DynamicLoadingBundled) code.codeAsset.file!, |
79 | ]; |
80 | } |
81 | |
82 | /// A [CodeAsset] for a specific [target]. |
83 | /// |
84 | /// Flutter builds [CodeAsset]s for multiple architectures (on MacOS and iOS). |
85 | /// This class distinguishes the (otherwise identical) [codeAsset]s on different |
86 | /// [target]s. These are then later combined into a single [KernelAsset] before |
87 | /// being added to the native assets manifest. |
88 | class FlutterCodeAsset { |
89 | FlutterCodeAsset({required this.codeAsset, required this.target}); |
90 | |
91 | final CodeAsset codeAsset; |
92 | final Target target; |
93 | |
94 | @override |
95 | String toString() => |
96 | 'FlutterCodeAsset(codeAsset: ${codeAsset.id} ${codeAsset.file}, target: $target)' ; |
97 | } |
98 | |
99 | /// Invokes the build of all transitive Dart packages and prepares code assets |
100 | /// to be included in the native build. |
101 | Future<DartBuildResult> runFlutterSpecificDartBuild({ |
102 | required Map<String, String> environmentDefines, |
103 | required FlutterNativeAssetsBuildRunner buildRunner, |
104 | required TargetPlatform targetPlatform, |
105 | required Uri projectUri, |
106 | required FileSystem fileSystem, |
107 | }) async { |
108 | final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); |
109 | final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); |
110 | final Directory buildDir = fileSystem.directory(buildUri); |
111 | |
112 | final flutterTester = targetPlatform == TargetPlatform.tester; |
113 | |
114 | if (!await buildDir.exists()) { |
115 | // Ensure the folder exists so the native build system can copy it even |
116 | // if there's no native assets. |
117 | await buildDir.create(recursive: true); |
118 | } |
119 | |
120 | if (!await _nativeBuildRequired(buildRunner)) { |
121 | return const DartBuildResult.empty(); |
122 | } |
123 | |
124 | final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); |
125 | final List<Architecture> architectures = flutterTester |
126 | ? <Architecture>[Architecture.current] |
127 | : _architecturesForOS(targetPlatform, targetOS, environmentDefines); |
128 | final DartBuildResult result = architectures.isEmpty |
129 | ? const DartBuildResult.empty() |
130 | : await _runDartBuild( |
131 | environmentDefines: environmentDefines, |
132 | buildRunner: buildRunner, |
133 | architectures: architectures, |
134 | projectUri: projectUri, |
135 | linkingEnabled: _nativeAssetsLinkingEnabled(buildMode), |
136 | fileSystem: fileSystem, |
137 | targetOS: targetOS, |
138 | ); |
139 | return result; |
140 | } |
141 | |
142 | Future<void> installCodeAssets({ |
143 | required DartBuildResult dartBuildResult, |
144 | required Map<String, String> environmentDefines, |
145 | required TargetPlatform targetPlatform, |
146 | required Uri projectUri, |
147 | required FileSystem fileSystem, |
148 | required Uri nativeAssetsFileUri, |
149 | }) async { |
150 | final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); |
151 | final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); |
152 | final flutterTester = targetPlatform == TargetPlatform.tester; |
153 | final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); |
154 | |
155 | final String? codesignIdentity = environmentDefines[kCodesignIdentity]; |
156 | final Map<FlutterCodeAsset, KernelAsset> assetTargetLocations = assetTargetLocationsForOS( |
157 | targetOS, |
158 | dartBuildResult.codeAssets, |
159 | flutterTester, |
160 | buildUri, |
161 | ); |
162 | await _copyNativeCodeAssetsForOS( |
163 | targetOS, |
164 | buildUri, |
165 | buildMode, |
166 | fileSystem, |
167 | assetTargetLocations, |
168 | codesignIdentity, |
169 | flutterTester, |
170 | ); |
171 | await _writeNativeAssetsJson( |
172 | assetTargetLocations.values.toList(), |
173 | nativeAssetsFileUri, |
174 | fileSystem, |
175 | ); |
176 | } |
177 | |
178 | /// Programmatic API to be used by Dart launchers to invoke native builds. |
179 | /// |
180 | /// It enables mocking `package:hooks_runner` package. |
181 | /// It also enables mocking native toolchain discovery via [cCompilerConfig]. |
182 | abstract interface class FlutterNativeAssetsBuildRunner { |
183 | /// All packages in the transitive dependencies that have a `build.dart`. |
184 | Future<List<String>> packagesWithNativeAssets(); |
185 | |
186 | /// Runs all [packagesWithNativeAssets] `build.dart`. |
187 | Future<BuildResult?> build({ |
188 | required List<ProtocolExtension> extensions, |
189 | required bool linkingEnabled, |
190 | }); |
191 | |
192 | /// Runs all [packagesWithNativeAssets] `link.dart`. |
193 | Future<LinkResult?> link({ |
194 | required List<ProtocolExtension> extensions, |
195 | required BuildResult buildResult, |
196 | }); |
197 | |
198 | /// The C compiler config to use for compilation. |
199 | Future<CCompilerConfig?> get cCompilerConfig; |
200 | |
201 | /// The NDK compiler to use to use for compilation for Android. |
202 | Future<CCompilerConfig?> get ndkCCompilerConfig; |
203 | } |
204 | |
205 | /// Uses `package:hooks_runner` for its implementation. |
206 | class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunner { |
207 | FlutterNativeAssetsBuildRunnerImpl( |
208 | this.packageConfigPath, |
209 | this.packageConfig, |
210 | this.fileSystem, |
211 | this.logger, |
212 | this.runPackageName, |
213 | this.pubspecPath, { |
214 | required this.includeDevDependencies, |
215 | }); |
216 | |
217 | final String pubspecPath; |
218 | final String packageConfigPath; |
219 | final PackageConfig packageConfig; |
220 | final FileSystem fileSystem; |
221 | final Logger logger; |
222 | final String runPackageName; |
223 | |
224 | /// Include the dev dependencies of [runPackageName]. |
225 | final bool includeDevDependencies; |
226 | |
227 | late final _logger = logging.Logger('' ) |
228 | ..onRecord.listen((logging.LogRecord record) { |
229 | final int levelValue = record.level.value; |
230 | final String message = record.message; |
231 | if (levelValue >= logging.Level.SEVERE.value) { |
232 | logger.printError(message); |
233 | } else if (levelValue >= logging.Level.WARNING.value) { |
234 | logger.printWarning(message); |
235 | } else if (levelValue >= logging.Level.INFO.value) { |
236 | logger.printTrace(message); |
237 | } else { |
238 | logger.printTrace(message); |
239 | } |
240 | }); |
241 | |
242 | // Flutter wraps the Dart executable to update it in place |
243 | // ($FLUTTER_ROOT/bin/dart). However, since this is a Dart process invocation |
244 | // in a Flutter process invocation, it should not try to update in place, so |
245 | // use the Dart standalone executable |
246 | // ($FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart). |
247 | late final Uri _dartExecutable = fileSystem |
248 | .directory(Cache.flutterRoot) |
249 | .uri |
250 | .resolve('bin/cache/dart-sdk/bin/dart' ); |
251 | |
252 | late final packageLayout = PackageLayout.fromPackageConfig( |
253 | fileSystem, |
254 | packageConfig, |
255 | Uri.file(packageConfigPath), |
256 | runPackageName, |
257 | includeDevDependencies: includeDevDependencies, |
258 | ); |
259 | |
260 | late final _buildRunner = NativeAssetsBuildRunner( |
261 | logger: _logger, |
262 | dartExecutable: _dartExecutable, |
263 | fileSystem: fileSystem, |
264 | packageLayout: packageLayout, |
265 | userDefines: UserDefines(workspacePubspec: Uri.file(pubspecPath)), |
266 | ); |
267 | |
268 | @override |
269 | Future<List<String>> packagesWithNativeAssets() async { |
270 | // It suffices to only check for build hooks. If no packages have a build |
271 | // hook. Then no build hook will output any assets for any link hook, and |
272 | // thus the link hooks will never be run. |
273 | return _buildRunner.packagesWithBuildHooks(); |
274 | } |
275 | |
276 | @override |
277 | Future<BuildResult?> build({ |
278 | required List<ProtocolExtension> extensions, |
279 | required bool linkingEnabled, |
280 | }) async { |
281 | final Result<BuildResult, HooksRunnerFailure> result = await _buildRunner.build( |
282 | linkingEnabled: linkingEnabled, |
283 | extensions: extensions, |
284 | ); |
285 | if (result.isSuccess) { |
286 | return result.success; |
287 | } else { |
288 | return null; |
289 | } |
290 | } |
291 | |
292 | @override |
293 | Future<LinkResult?> link({ |
294 | required List<ProtocolExtension> extensions, |
295 | required BuildResult buildResult, |
296 | }) async { |
297 | final Result<LinkResult, HooksRunnerFailure> result = await _buildRunner.link( |
298 | extensions: extensions, |
299 | buildResult: buildResult, |
300 | ); |
301 | if (result.isSuccess) { |
302 | return result.success; |
303 | } else { |
304 | return null; |
305 | } |
306 | } |
307 | |
308 | @override |
309 | late final Future<CCompilerConfig?> cCompilerConfig = () { |
310 | if (globals.platform.isMacOS || globals.platform.isIOS) { |
311 | return cCompilerConfigMacOS(); |
312 | } |
313 | if (globals.platform.isLinux) { |
314 | return cCompilerConfigLinux(); |
315 | } |
316 | if (globals.platform.isWindows) { |
317 | return cCompilerConfigWindows(); |
318 | } |
319 | if (globals.platform.isAndroid) { |
320 | throwToolExit('Should use ndkCCompilerConfig for Android.' ); |
321 | } |
322 | throwToolExit('Unknown target OS.' ); |
323 | }(); |
324 | |
325 | @override |
326 | late final Future<CCompilerConfig> ndkCCompilerConfig = () { |
327 | return cCompilerConfigAndroid(); |
328 | }(); |
329 | } |
330 | |
331 | Future<Uri> _writeNativeAssetsJson( |
332 | List<KernelAsset> assets, |
333 | Uri nativeAssetsJsonUri, |
334 | FileSystem fileSystem, |
335 | ) async { |
336 | globals.logger.printTrace('Writing native assets json to $nativeAssetsJsonUri.' ); |
337 | final String nativeAssetsDartContents = _toNativeAssetsJsonFile(assets); |
338 | final File nativeAssetsFile = fileSystem.file(nativeAssetsJsonUri); |
339 | final Directory parentDirectory = nativeAssetsFile.parent; |
340 | if (!await parentDirectory.exists()) { |
341 | await parentDirectory.create(recursive: true); |
342 | } |
343 | await nativeAssetsFile.writeAsString(nativeAssetsDartContents); |
344 | globals.logger.printTrace('Writing ${nativeAssetsFile.path} done.' ); |
345 | return nativeAssetsFile.uri; |
346 | } |
347 | |
348 | String _toNativeAssetsJsonFile(List<KernelAsset> kernelAssets) { |
349 | final assetsPerTarget = <Target, List<KernelAsset>>{}; |
350 | for (final asset in kernelAssets) { |
351 | assetsPerTarget.putIfAbsent(asset.target, () => <KernelAsset>[]).add(asset); |
352 | } |
353 | |
354 | const formatVersionKey = 'format-version' ; |
355 | const nativeAssetsKey = 'native-assets' ; |
356 | |
357 | // See assets/native_assets.cc in the engine for the expected format. |
358 | final jsonContents = <String, Object>{ |
359 | formatVersionKey: const <int>[1, 0, 0], |
360 | nativeAssetsKey: <String, Map<String, List<String>>>{ |
361 | for (final MapEntry<Target, List<KernelAsset>> entry in assetsPerTarget.entries) |
362 | entry.key.toString(): <String, List<String>>{ |
363 | for (final KernelAsset e in entry.value) e.id: e.path.toJson(), |
364 | }, |
365 | }, |
366 | }; |
367 | |
368 | return jsonEncode(jsonContents); |
369 | } |
370 | |
371 | /// Whether link hooks should be run. |
372 | /// |
373 | /// Link hooks should only be run for AOT Dart builds, which is the non-debug |
374 | /// modes in Flutter. |
375 | bool _nativeAssetsLinkingEnabled(BuildMode buildMode) { |
376 | switch (buildMode) { |
377 | case BuildMode.debug: |
378 | return false; |
379 | case BuildMode.jitRelease: |
380 | case BuildMode.profile: |
381 | case BuildMode.release: |
382 | return true; |
383 | } |
384 | } |
385 | |
386 | Future<bool> _nativeBuildRequired(FlutterNativeAssetsBuildRunner buildRunner) async { |
387 | final List<String> packagesWithNativeAssets = await buildRunner.packagesWithNativeAssets(); |
388 | if (packagesWithNativeAssets.isEmpty) { |
389 | globals.logger.printTrace( |
390 | 'No packages with native assets. Skipping native assets compilation.' , |
391 | ); |
392 | return false; |
393 | } |
394 | |
395 | if (!featureFlags.isNativeAssetsEnabled) { |
396 | final String packageNames = packagesWithNativeAssets.join(' ' ); |
397 | throwToolExit( |
398 | 'Package(s) $packageNames require the native assets feature to be enabled. ' |
399 | 'Enable using `flutter config --enable-native-assets`.' , |
400 | ); |
401 | } |
402 | return true; |
403 | } |
404 | |
405 | /// Ensures that either this project has no native assets, or that native assets |
406 | /// are supported on that operating system. |
407 | /// |
408 | /// Exits the tool if the above condition is not satisfied. |
409 | Future<void> ensureNoNativeAssetsOrOsIsSupported( |
410 | Uri workingDirectory, |
411 | String os, |
412 | FileSystem fileSystem, |
413 | FlutterNativeAssetsBuildRunner buildRunner, |
414 | ) async { |
415 | final List<String> packagesWithNativeAssets = await buildRunner.packagesWithNativeAssets(); |
416 | if (packagesWithNativeAssets.isEmpty) { |
417 | globals.logger.printTrace( |
418 | 'No packages with native assets. Skipping native assets compilation.' , |
419 | ); |
420 | return; |
421 | } |
422 | final String packageNames = packagesWithNativeAssets.join(' ' ); |
423 | throwToolExit( |
424 | 'Package(s) $packageNames require the native assets feature. ' |
425 | 'This feature has not yet been implemented for ` $os`. ' |
426 | 'For more info see https://github.com/flutter/flutter/issues/129757.', |
427 | ); |
428 | } |
429 | |
430 | /// This should be the same for different archs, debug/release, etc. |
431 | /// It should work for all macOS. |
432 | Uri nativeAssetsBuildUri(Uri projectUri, OS os) { |
433 | final String buildDir = getBuildDirectory(); |
434 | return projectUri.resolve(' $buildDir/native_assets/ $os/' ); |
435 | } |
436 | |
437 | Map<FlutterCodeAsset, KernelAsset> _assetTargetLocationsWindowsLinux( |
438 | List<FlutterCodeAsset> assets, |
439 | Uri? absolutePath, |
440 | ) { |
441 | return <FlutterCodeAsset, KernelAsset>{ |
442 | for (final FlutterCodeAsset asset in assets) |
443 | asset: _targetLocationSingleArchitecture(asset, absolutePath), |
444 | }; |
445 | } |
446 | |
447 | KernelAsset _targetLocationSingleArchitecture(FlutterCodeAsset asset, Uri? absolutePath) { |
448 | final LinkMode linkMode = asset.codeAsset.linkMode; |
449 | final KernelAssetPath kernelAssetPath; |
450 | switch (linkMode) { |
451 | case DynamicLoadingSystem _: |
452 | kernelAssetPath = KernelAssetSystemPath(linkMode.uri); |
453 | case LookupInExecutable _: |
454 | kernelAssetPath = KernelAssetInExecutable(); |
455 | case LookupInProcess _: |
456 | kernelAssetPath = KernelAssetInProcess(); |
457 | case DynamicLoadingBundled _: |
458 | final String fileName = asset.codeAsset.file!.pathSegments.last; |
459 | Uri uri; |
460 | if (absolutePath != null) { |
461 | // Flutter tester needs full host paths. |
462 | uri = absolutePath.resolve(fileName); |
463 | } else { |
464 | // Flutter Desktop needs "absolute" paths inside the app. |
465 | // "relative" in the context of native assets would be relative to the |
466 | // kernel or aot snapshot. |
467 | uri = Uri(path: fileName); |
468 | } |
469 | kernelAssetPath = KernelAssetAbsolutePath(uri); |
470 | default: |
471 | throw Exception('Unsupported asset link mode ${linkMode.runtimeType} in asset $asset' ); |
472 | } |
473 | return KernelAsset(id: asset.codeAsset.id, target: asset.target, path: kernelAssetPath); |
474 | } |
475 | |
476 | Map<FlutterCodeAsset, KernelAsset> assetTargetLocationsForOS( |
477 | OS targetOS, |
478 | List<FlutterCodeAsset> codeAssets, |
479 | bool flutterTester, |
480 | Uri buildUri, |
481 | ) { |
482 | switch (targetOS) { |
483 | case OS.windows: |
484 | case OS.linux: |
485 | final Uri? absolutePath = flutterTester ? buildUri : null; |
486 | return _assetTargetLocationsWindowsLinux(codeAssets, absolutePath); |
487 | case OS.macOS: |
488 | final Uri? absolutePath = flutterTester ? buildUri : null; |
489 | return assetTargetLocationsMacOS(codeAssets, absolutePath); |
490 | case OS.iOS: |
491 | return assetTargetLocationsIOS(codeAssets); |
492 | case OS.android: |
493 | return assetTargetLocationsAndroid(codeAssets); |
494 | default: |
495 | throw UnimplementedError('This should be unreachable.' ); |
496 | } |
497 | } |
498 | |
499 | Future<void> _copyNativeCodeAssetsForOS( |
500 | OS targetOS, |
501 | Uri buildUri, |
502 | BuildMode buildMode, |
503 | FileSystem fileSystem, |
504 | Map<FlutterCodeAsset, KernelAsset> assetTargetLocations, |
505 | String? codesignIdentity, |
506 | bool flutterTester, |
507 | ) async { |
508 | // We only have to copy code assets that are bundled within the app. |
509 | // If a code asset that use a linking mode of [LookupInProcess], |
510 | // [LookupInExecutable] or [DynamicLoadingSystem] do not have anything to |
511 | // bundle as part of the app. |
512 | assetTargetLocations = <FlutterCodeAsset, KernelAsset>{ |
513 | for (final FlutterCodeAsset codeAsset in assetTargetLocations.keys) |
514 | if (codeAsset.codeAsset.linkMode is DynamicLoadingBundled) |
515 | codeAsset: assetTargetLocations[codeAsset]!, |
516 | }; |
517 | |
518 | if (assetTargetLocations.isEmpty) { |
519 | return; |
520 | } |
521 | |
522 | globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.' ); |
523 | final List<FlutterCodeAsset> codeAssets = assetTargetLocations.keys.toList(); |
524 | switch (targetOS) { |
525 | case OS.windows: |
526 | case OS.linux: |
527 | assert(codesignIdentity == null); |
528 | await _copyNativeCodeAssetsToBundleOnWindowsLinux( |
529 | buildUri, |
530 | assetTargetLocations, |
531 | buildMode, |
532 | fileSystem, |
533 | ); |
534 | case OS.macOS: |
535 | if (flutterTester) { |
536 | await copyNativeCodeAssetsMacOSFlutterTester( |
537 | buildUri, |
538 | fatAssetTargetLocationsMacOS(codeAssets, buildUri), |
539 | codesignIdentity, |
540 | buildMode, |
541 | fileSystem, |
542 | ); |
543 | } else { |
544 | await copyNativeCodeAssetsMacOS( |
545 | buildUri, |
546 | fatAssetTargetLocationsMacOS(codeAssets, null), |
547 | codesignIdentity, |
548 | buildMode, |
549 | fileSystem, |
550 | ); |
551 | } |
552 | case OS.iOS: |
553 | await copyNativeCodeAssetsIOS( |
554 | buildUri, |
555 | fatAssetTargetLocationsIOS(codeAssets), |
556 | codesignIdentity, |
557 | buildMode, |
558 | fileSystem, |
559 | ); |
560 | case OS.android: |
561 | assert(codesignIdentity == null); |
562 | await copyNativeCodeAssetsAndroid(buildUri, assetTargetLocations, fileSystem); |
563 | default: |
564 | throw StateError('This should be unreachable.' ); |
565 | } |
566 | globals.logger.printTrace('Copying native assets done.' ); |
567 | } |
568 | |
569 | /// Invokes the build of all transitive Dart packages. |
570 | /// |
571 | /// This will invoke `hook/build.dart` and `hook/link.dart` (if applicable) for |
572 | /// all transitive dart packages that define such hooks. |
573 | Future<DartBuildResult> _runDartBuild({ |
574 | required Map<String, String> environmentDefines, |
575 | required FlutterNativeAssetsBuildRunner buildRunner, |
576 | required List<Architecture> architectures, |
577 | required Uri projectUri, |
578 | required FileSystem fileSystem, |
579 | required OS? targetOS, |
580 | required bool linkingEnabled, |
581 | }) async { |
582 | final architectureString = architectures.length == 1 |
583 | ? architectures.single.toString() |
584 | : architectures.toList().toString(); |
585 | |
586 | globals.logger.printTrace('Building native assets for $targetOS $architectureString.' ); |
587 | final codeAssets = <FlutterCodeAsset>[]; |
588 | final dependencies = <Uri>{}; |
589 | |
590 | final EnvironmentType? environmentType; |
591 | if (targetOS == OS.iOS) { |
592 | final String? sdkRoot = environmentDefines[kSdkRoot]; |
593 | if (sdkRoot == null) { |
594 | throw MissingDefineException(kSdkRoot, 'native_assets' ); |
595 | } |
596 | environmentType = xcode.environmentTypeFromSdkroot(sdkRoot, fileSystem); |
597 | } else { |
598 | environmentType = null; |
599 | } |
600 | |
601 | final CCompilerConfig? cCompilerConfig = targetOS == OS.android |
602 | ? await buildRunner.ndkCCompilerConfig |
603 | : await buildRunner.cCompilerConfig; |
604 | |
605 | final String? codesignIdentity = environmentDefines[kCodesignIdentity]; |
606 | assert(codesignIdentity == null || targetOS == OS.iOS || targetOS == OS.macOS); |
607 | |
608 | final AndroidCodeConfig? androidConfig = targetOS == OS.android |
609 | ? AndroidCodeConfig(targetNdkApi: targetAndroidNdkApi(environmentDefines)) |
610 | : null; |
611 | final IOSCodeConfig? iosConfig = targetOS == OS.iOS |
612 | ? IOSCodeConfig(targetVersion: targetIOSVersion, targetSdk: getIOSSdk(environmentType!)) |
613 | : null; |
614 | final MacOSCodeConfig? macOSConfig = targetOS == OS.macOS |
615 | ? MacOSCodeConfig(targetVersion: targetMacOSVersion) |
616 | : null; |
617 | for (final architecture in architectures) { |
618 | final target = Target.fromArchitectureAndOS(architecture, targetOS!); |
619 | final BuildResult? buildResult = await buildRunner.build( |
620 | extensions: <ProtocolExtension>[ |
621 | CodeAssetExtension( |
622 | targetArchitecture: architecture, |
623 | linkModePreference: LinkModePreference.dynamic, |
624 | cCompiler: cCompilerConfig, |
625 | targetOS: targetOS, |
626 | android: androidConfig, |
627 | iOS: iosConfig, |
628 | macOS: macOSConfig, |
629 | ), |
630 | ], |
631 | linkingEnabled: linkingEnabled, |
632 | ); |
633 | if (buildResult == null) { |
634 | _throwNativeAssetsBuildFailed(); |
635 | } |
636 | dependencies.addAll(buildResult.dependencies); |
637 | codeAssets.addAll(_filterCodeAssets(buildResult.encodedAssets, target)); |
638 | if (linkingEnabled) { |
639 | final LinkResult? linkResult = await buildRunner.link( |
640 | extensions: <ProtocolExtension>[ |
641 | CodeAssetExtension( |
642 | targetArchitecture: architecture, |
643 | linkModePreference: LinkModePreference.dynamic, |
644 | cCompiler: cCompilerConfig, |
645 | targetOS: targetOS, |
646 | android: androidConfig, |
647 | iOS: iosConfig, |
648 | macOS: macOSConfig, |
649 | ), |
650 | ], |
651 | buildResult: buildResult, |
652 | ); |
653 | if (linkResult == null) { |
654 | _throwNativeAssetsLinkFailed(); |
655 | } |
656 | codeAssets.addAll(_filterCodeAssets(linkResult.encodedAssets, target)); |
657 | dependencies.addAll(linkResult.dependencies); |
658 | } |
659 | } |
660 | if (codeAssets.isNotEmpty) { |
661 | globals.logger.printTrace( |
662 | 'Note: You are using the dart build hooks feature which is currently ' |
663 | 'in preview. Please see ' |
664 | 'https://dart.dev/interop/c-interop#native-assets for more details.', |
665 | );
|
666 | }
|
667 | globals.logger.printTrace('Building native assets for $targetOS $architectureString done.' );
|
668 | return DartBuildResult(codeAssets, dependencies.toList());
|
669 | }
|
670 |
|
671 | List<FlutterCodeAsset> _filterCodeAssets(List<EncodedAsset> assets, Target target) => assets
|
672 | .where((EncodedAsset asset) => asset.isCodeAsset)
|
673 | .map<FlutterCodeAsset>(
|
674 | (EncodedAsset encodedAsset) =>
|
675 | FlutterCodeAsset(codeAsset: encodedAsset.asCodeAsset, target: target),
|
676 | )
|
677 | .toList();
|
678 |
|
679 | List<Architecture> _architecturesForOS(
|
680 | TargetPlatform targetPlatform,
|
681 | OS targetOS,
|
682 | Map<String, String> environmentDefines,
|
683 | ) {
|
684 | switch (targetOS) {
|
685 | case OS.linux:
|
686 | return <Architecture>[_getNativeArchitecture(targetPlatform)];
|
687 | case OS.windows:
|
688 | return <Architecture>[_getNativeArchitecture(targetPlatform)];
|
689 | case OS.macOS:
|
690 | final List<DarwinArch> darwinArchs =
|
691 | _emptyToNull(
|
692 | environmentDefines[kDarwinArchs],
|
693 | )?.split(' ' ).map(getDarwinArchForName).toList() ??
|
694 | <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64];
|
695 | return darwinArchs.map(getNativeMacOSArchitecture).toList();
|
696 | case OS.android:
|
697 | final String? androidArchsEnvironment = environmentDefines[kAndroidArchs];
|
698 | final List<AndroidArch> androidArchs = _androidArchs(targetPlatform, androidArchsEnvironment);
|
699 | return androidArchs.map(getNativeAndroidArchitecture).toList();
|
700 | case OS.iOS:
|
701 | final List<DarwinArch> iosArchs =
|
702 | _emptyToNull(environmentDefines[kIosArchs])?.split(' ' ).map(getIOSArchForName).toList() ??
|
703 | <DarwinArch>[DarwinArch.arm64];
|
704 | return iosArchs.map(getNativeIOSArchitecture).toList();
|
705 | default:
|
706 | // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757
|
707 | // Write the file we claim to have in the [outputs].
|
708 | return <Architecture>[];
|
709 | }
|
710 | }
|
711 |
|
712 | Architecture _getNativeArchitecture(TargetPlatform targetPlatform) {
|
713 | switch (targetPlatform) {
|
714 | case TargetPlatform.linux_x64:
|
715 | case TargetPlatform.windows_x64:
|
716 | return Architecture.x64;
|
717 | case TargetPlatform.linux_arm64:
|
718 | case TargetPlatform.windows_arm64:
|
719 | return Architecture.arm64;
|
720 | case TargetPlatform.android:
|
721 | case TargetPlatform.ios:
|
722 | case TargetPlatform.darwin:
|
723 | case TargetPlatform.fuchsia_arm64:
|
724 | case TargetPlatform.fuchsia_x64:
|
725 | case TargetPlatform.tester:
|
726 | case TargetPlatform.web_javascript:
|
727 | case TargetPlatform.android_arm:
|
728 | case TargetPlatform.android_arm64:
|
729 | case TargetPlatform.android_x64:
|
730 | case TargetPlatform.unsupported:
|
731 | throw Exception('Unknown targetPlatform: $targetPlatform.' );
|
732 | }
|
733 | }
|
734 |
|
735 | Future<void> _copyNativeCodeAssetsToBundleOnWindowsLinux(
|
736 | Uri buildUri,
|
737 | Map<FlutterCodeAsset, KernelAsset> assetTargetLocations,
|
738 | BuildMode buildMode,
|
739 | FileSystem fileSystem,
|
740 | ) async {
|
741 | assert(assetTargetLocations.isNotEmpty);
|
742 |
|
743 | final Directory buildDir = fileSystem.directory(buildUri.toFilePath());
|
744 | if (!buildDir.existsSync()) {
|
745 | buildDir.createSync(recursive: true);
|
746 | }
|
747 | for (final MapEntry<FlutterCodeAsset, KernelAsset> assetMapping in assetTargetLocations.entries) {
|
748 | final Uri source = assetMapping.key.codeAsset.file!;
|
749 | final Uri target = (assetMapping.value.path as KernelAssetAbsolutePath).uri;
|
750 | final Uri targetUri = buildUri.resolveUri(target);
|
751 | final String targetFullPath = targetUri.toFilePath();
|
752 | await fileSystem.file(source).copy(targetFullPath);
|
753 | }
|
754 | }
|
755 |
|
756 | Never _throwNativeAssetsBuildFailed() {
|
757 | throwToolExit('Building native assets failed. See the logs for more details.' );
|
758 | }
|
759 |
|
760 | Never _throwNativeAssetsLinkFailed() {
|
761 | throwToolExit('Linking native assets failed. See the logs for more details.' );
|
762 | }
|
763 |
|
764 | OS getNativeOSFromTargetPlatform(TargetPlatform platform) {
|
765 | switch (platform) {
|
766 | case TargetPlatform.ios:
|
767 | return OS.iOS;
|
768 | case TargetPlatform.darwin:
|
769 | return OS.macOS;
|
770 | case TargetPlatform.linux_x64:
|
771 | case TargetPlatform.linux_arm64:
|
772 | return OS.linux;
|
773 | case TargetPlatform.windows_x64:
|
774 | case TargetPlatform.windows_arm64:
|
775 | return OS.windows;
|
776 | case TargetPlatform.fuchsia_arm64:
|
777 | case TargetPlatform.fuchsia_x64:
|
778 | return OS.fuchsia;
|
779 | case TargetPlatform.android:
|
780 | case TargetPlatform.android_arm:
|
781 | case TargetPlatform.android_arm64:
|
782 | case TargetPlatform.android_x64:
|
783 | return OS.android;
|
784 | case TargetPlatform.tester:
|
785 | if (const LocalPlatform().isMacOS) {
|
786 | return OS.macOS;
|
787 | } else if (const LocalPlatform().isLinux) {
|
788 | return OS.linux;
|
789 | } else if (const LocalPlatform().isWindows) {
|
790 | return OS.windows;
|
791 | } else {
|
792 | throw StateError('Unknown operating system' );
|
793 | }
|
794 | case TargetPlatform.web_javascript:
|
795 | throw StateError('No dart builds for web yet.' );
|
796 | case TargetPlatform.unsupported:
|
797 | TargetPlatform.throwUnsupportedTarget();
|
798 | }
|
799 | }
|
800 |
|
801 | List<AndroidArch> _androidArchs(TargetPlatform targetPlatform, String? androidArchsEnvironment) {
|
802 | switch (targetPlatform) {
|
803 | case TargetPlatform.android_arm:
|
804 | return <AndroidArch>[AndroidArch.armeabi_v7a];
|
805 | case TargetPlatform.android_arm64:
|
806 | return <AndroidArch>[AndroidArch.arm64_v8a];
|
807 | case TargetPlatform.android_x64:
|
808 | return <AndroidArch>[AndroidArch.x86_64];
|
809 | case TargetPlatform.android:
|
810 | if (androidArchsEnvironment == null) {
|
811 | throw MissingDefineException(kAndroidArchs, 'native_assets' );
|
812 | }
|
813 | return androidArchsEnvironment.split(' ' ).map(getAndroidArchForName).toList();
|
814 | case TargetPlatform.darwin:
|
815 | case TargetPlatform.fuchsia_arm64:
|
816 | case TargetPlatform.fuchsia_x64:
|
817 | case TargetPlatform.ios:
|
818 | case TargetPlatform.linux_arm64:
|
819 | case TargetPlatform.linux_x64:
|
820 | case TargetPlatform.tester:
|
821 | case TargetPlatform.web_javascript:
|
822 | case TargetPlatform.windows_x64:
|
823 | case TargetPlatform.windows_arm64:
|
824 | throwToolExit('Unsupported Android target platform: $targetPlatform.' );
|
825 | case TargetPlatform.unsupported:
|
826 | TargetPlatform.throwUnsupportedTarget();
|
827 | }
|
828 | }
|
829 |
|
830 | String? _emptyToNull(String? input) {
|
831 | if (input == null || input.isEmpty) {
|
832 | return null;
|
833 | }
|
834 | return input;
|
835 | }
|
836 |
|
837 | extension OSArchitectures on OS {
|
838 | Set<Architecture> get architectures => _osTargets[this]!;
|
839 | }
|
840 |
|
841 | const _osTargets = <OS, Set<Architecture>>{
|
842 | OS.android: <Architecture>{
|
843 | Architecture.arm,
|
844 | Architecture.arm64,
|
845 | Architecture.ia32,
|
846 | Architecture.x64,
|
847 | Architecture.riscv64,
|
848 | },
|
849 | OS.fuchsia: <Architecture>{Architecture.arm64, Architecture.x64},
|
850 | OS.iOS: <Architecture>{Architecture.arm, Architecture.arm64, Architecture.x64},
|
851 | OS.linux: <Architecture>{
|
852 | Architecture.arm,
|
853 | Architecture.arm64,
|
854 | Architecture.ia32,
|
855 | Architecture.riscv32,
|
856 | Architecture.riscv64,
|
857 | Architecture.x64,
|
858 | },
|
859 | OS.macOS: <Architecture>{Architecture.arm64, Architecture.x64},
|
860 | OS.windows: <Architecture>{Architecture.arm64, Architecture.ia32, Architecture.x64},
|
861 | };
|
862 |
|
863 | BuildMode _getBuildMode(Map<String, String> environmentDefines, bool isFlutterTester) {
|
864 | if (isFlutterTester) {
|
865 | return BuildMode.debug;
|
866 | }
|
867 | final String? environmentBuildMode = environmentDefines[kBuildMode];
|
868 | if (environmentBuildMode == null) {
|
869 | throw MissingDefineException(kBuildMode, 'native_assets' );
|
870 | }
|
871 | return BuildMode.fromCliName(environmentBuildMode);
|
872 | }
|
873 |
|