| 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 |
|