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
7import 'package:code_assets/code_assets.dart';
8import 'package:hooks/hooks.dart';
9import 'package:hooks_runner/hooks_runner.dart';
10import 'package:logging/logging.dart' as logging;
11import 'package:package_config/package_config_types.dart';
12
13import '../../base/common.dart';
14import '../../base/file_system.dart';
15import '../../base/logger.dart';
16import '../../base/platform.dart';
17import '../../build_info.dart';
18import '../../build_system/exceptions.dart';
19import '../../cache.dart';
20import '../../convert.dart';
21import '../../features.dart';
22import '../../globals.dart' as globals;
23import '../../macos/xcode.dart' as xcode;
24import 'android/native_assets.dart';
25import 'ios/native_assets.dart';
26import 'linux/native_assets.dart';
27import 'macos/native_assets.dart';
28import 'macos/native_assets_host.dart';
29import '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.
35final 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.
88class 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.
101Future<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
142Future<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].
182abstract 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.
206class 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
331Future<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
348String _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.
375bool _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
386Future<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.
409Future<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.
432Uri nativeAssetsBuildUri(Uri projectUri, OS os) {
433 final String buildDir = getBuildDirectory();
434 return projectUri.resolve('$buildDir/native_assets/$os/');
435}
436
437Map<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
447KernelAsset _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
476Map<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
499Future<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.
573Future<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
671List<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
679List<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
712Architecture _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
735Future<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
756Never _throwNativeAssetsBuildFailed() {
757 throwToolExit('Building native assets failed. See the logs for more details.');
758}
759
760Never _throwNativeAssetsLinkFailed() {
761 throwToolExit('Linking native assets failed. See the logs for more details.');
762}
763
764OS 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
801List<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
830String? _emptyToNull(String? input) {
831 if (input == null || input.isEmpty) {
832 return null;
833 }
834 return input;
835}
836
837extension OSArchitectures on OS {
838 Set<Architecture> get architectures => _osTargets[this]!;
839}
840
841const _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
863BuildMode _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