From 4dfa688ec498844428b09653abc01dee8061777d Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:46:22 +0000 Subject: [PATCH] Reverts "[flutter_tools] Cleanup of native asset related code (removes around 50% of the native asset related code) (#155430)" (#155713) Reverts: flutter/flutter#155430 Initiated by: eyebrowsoffire Reason for reverting: Postsubmit failures closing the tree. See the following examples: https://ci.chromium.org/ui/p/flutter/builders/prod/Mac_ios%20native_assets_ios/5738/overview https://ci.chromium.org/ui/p/flutter/builders/prod/Mac_arm64_mokey%20native_assets_android/583/overview https://ci.chromium.org/ui/p/flutter/builders/prod/Linux_pixel_7pro%20native_assets_android/4075/overview https://ci.chromium.org/u Original PR Author: mkustermann Reviewed By: {bkonyi, dcharkes} This change reverts the following previous change: tl;dr Removes 50% (>1650 locs) of native asset related code in `packages/flutter_tools` Before this PR the invocation of dart build/link/dry-run was implemented per OS. This lead to very large code duplication of almost identical, but sligthly different code. It also led to similarly duplicated test code. Almost the entire dart build/link/dry-run implementation is identical across OSes. There's small variations: - configuration of the build (e.g. android/macos/ios version, ios sdk, ...) - determining target locations & copying the final shared libraries This PR unifies the implementation by reducing the code to basically two main functions: * `runFlutterSpecificDartBuild` which is responsible for - obtain flutter configuration - perform dart build (& link) - determine target location & install binaries * `runFlutterSpecificDartDryRunOnPlatforms` which is responsible for a similar (but not same): - obtain flutter configuration - perform dart dry run - determine target location these two functions will call out to helpers for the OS specific functionality: * `_assetTargetLocationsForOS` for determining the location of the code assets * `_copyNativeCodeAssetsForOS` for copying the code assets (and possibly overriting the install name, etc) => Since we get rid of the code duplication across OSes and have only a single code path for the build/link/dry-run, we can also remove the duplicated tests that were pretty much identical across OSes. We also harden the building code by adding asserts, e.g. * the dry fun functionality should never be used by `flutter test` * the `build/native_assets//native_assets.yaml` should only be used by `flutter test` and the dry-run of `flutter run` => We change the tests to also comply with these invariants (so the tests are not testing things that cannot happen in reality) We also rename `{,Flutter}NativeAssetsBuildRunner` to disambiguate it from the `package:native_asset_builder`'s `NativeAssetsBuildRunner`. We also reorganize the main code to make it readable from top-down and make members private where they can be. --- .../build_system/targets/native_assets.dart | 312 +++++- .../native_assets/android/native_assets.dart | 162 ++- .../native_assets/ios/native_assets.dart | 199 +++- .../native_assets/linux/native_assets.dart | 58 ++ .../native_assets/macos/native_assets.dart | 226 ++++- .../macos/native_assets_host.dart | 18 +- .../isolated/native_assets/native_assets.dart | 931 ++++++++---------- .../native_assets/test/native_assets.dart | 77 +- .../native_assets/windows/native_assets.dart | 60 ++ .../isolated/android/native_assets_test.dart | 284 +++++- .../targets/native_assets_test.dart | 24 +- .../fake_native_assets_build_runner.dart | 39 +- .../test/general.shard/isolated/hot_test.dart | 8 +- .../isolated/ios/native_assets_test.dart | 261 ++++- .../isolated/linux/native_assets_test.dart | 348 ++++++- .../isolated/macos/native_assets_test.dart | 358 +++++-- .../isolated/native_assets_test.dart | 308 ------ .../isolated/resident_runner_test.dart | 2 +- .../isolated/windows/native_assets_test.dart | 304 +++++- 19 files changed, 2813 insertions(+), 1166 deletions(-) delete mode 100644 packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index 12efadc5db..fae9b56cf5 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -3,14 +3,22 @@ // found in the LICENSE file. import 'package:meta/meta.dart'; -import 'package:native_assets_builder/native_assets_builder.dart'; +import 'package:native_assets_builder/native_assets_builder.dart' hide NativeAssetsBuildRunner; import 'package:package_config/package_config_types.dart'; +import '../../android/gradle_utils.dart'; import '../../base/common.dart'; import '../../base/file_system.dart'; +import '../../base/platform.dart'; import '../../build_info.dart'; import '../../dart/package_map.dart'; +import '../../isolated/native_assets/android/native_assets.dart'; +import '../../isolated/native_assets/ios/native_assets.dart'; +import '../../isolated/native_assets/linux/native_assets.dart'; +import '../../isolated/native_assets/macos/native_assets.dart'; import '../../isolated/native_assets/native_assets.dart'; +import '../../isolated/native_assets/windows/native_assets.dart'; +import '../../macos/xcode.dart'; import '../build_system.dart'; import '../depfile.dart'; import '../exceptions.dart'; @@ -35,21 +43,20 @@ import 'common.dart'; /// rebuild. class NativeAssets extends Target { const NativeAssets({ - @visibleForTesting FlutterNativeAssetsBuildRunner? buildRunner, + @visibleForTesting NativeAssetsBuildRunner? buildRunner, }) : _buildRunner = buildRunner; - final FlutterNativeAssetsBuildRunner? _buildRunner; + final NativeAssetsBuildRunner? _buildRunner; @override Future build(Environment environment) async { final String? nativeAssetsEnvironment = environment.defines[kNativeAssets]; + final List dependencies; final FileSystem fileSystem = environment.fileSystem; - final Uri nativeAssetsFileUri = environment.buildDir.childFile('native_assets.yaml').uri; - - final DartBuildResult result; + final File nativeAssetsFile = environment.buildDir.childFile('native_assets.yaml'); if (nativeAssetsEnvironment == 'false') { - result = const DartBuildResult.empty(); - await writeNativeAssetsYaml(KernelAssets(), nativeAssetsFileUri, fileSystem); + dependencies = []; + await writeNativeAssetsYaml(KernelAssets(), environment.buildDir.uri, fileSystem); } else { final String? targetPlatformEnvironment = environment.defines[kTargetPlatform]; if (targetPlatformEnvironment == null) { @@ -62,8 +69,8 @@ class NativeAssets extends Target { fileSystem.file(environment.packageConfigPath), logger: environment.logger, ); - final FlutterNativeAssetsBuildRunner buildRunner = _buildRunner ?? - FlutterNativeAssetsBuildRunnerImpl( + final NativeAssetsBuildRunner buildRunner = _buildRunner ?? + NativeAssetsBuildRunnerImpl( projectUri, environment.packageConfigPath, packageConfig, @@ -71,22 +78,102 @@ class NativeAssets extends Target { environment.logger, ); - (result, _) = await runFlutterSpecificDartBuild( - environmentDefines: environment.defines, - buildRunner: buildRunner, - targetPlatform: targetPlatform, - projectUri: projectUri, - nativeAssetsYamlUri : nativeAssetsFileUri, - fileSystem: fileSystem, - ); + switch (targetPlatform) { + case TargetPlatform.ios: + dependencies = await _buildIOS( + environment, + projectUri, + fileSystem, + buildRunner, + ); + case TargetPlatform.darwin: + dependencies = await _buildMacOS( + environment, + projectUri, + fileSystem, + buildRunner, + ); + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_x64: + dependencies = await _buildLinux( + environment, + targetPlatform, + projectUri, + fileSystem, + buildRunner, + ); + case TargetPlatform.windows_arm64: + case TargetPlatform.windows_x64: + dependencies = await _buildWindows( + environment, + targetPlatform, + projectUri, + fileSystem, + buildRunner, + ); + case TargetPlatform.tester: + if (const LocalPlatform().isMacOS) { + (_, dependencies) = await buildNativeAssetsMacOS( + buildMode: BuildMode.debug, + projectUri: projectUri, + codesignIdentity: environment.defines[kCodesignIdentity], + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + flutterTester: true, + ); + } else if (const LocalPlatform().isLinux) { + (_, dependencies) = await buildNativeAssetsLinux( + buildMode: BuildMode.debug, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + flutterTester: true, + ); + } else if (const LocalPlatform().isWindows) { + (_, dependencies) = await buildNativeAssetsWindows( + buildMode: BuildMode.debug, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + flutterTester: true, + ); + } else { + // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 + // Write the file we claim to have in the [outputs]. + await writeNativeAssetsYaml(KernelAssets(), environment.buildDir.uri, fileSystem); + dependencies = []; + } + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.android_x86: + case TargetPlatform.android: + (_, dependencies) = await _buildAndroid( + environment, + targetPlatform, + projectUri, + fileSystem, + buildRunner, + ); + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.web_javascript: + // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 + // Write the file we claim to have in the [outputs]. + await writeNativeAssetsYaml(KernelAssets(), environment.buildDir.uri, fileSystem); + dependencies = []; + } } final Depfile depfile = Depfile( [ - for (final Uri dependency in result.dependencies) fileSystem.file(dependency), + for (final Uri dependency in dependencies) fileSystem.file(dependency), ], [ - fileSystem.file(nativeAssetsFileUri), + nativeAssetsFile, ], ); final File outputDepfile = environment.buildDir.childFile('native_assets.d'); @@ -94,14 +181,188 @@ class NativeAssets extends Target { outputDepfile.parent.createSync(recursive: true); } environment.depFileService.writeToFile(depfile, outputDepfile); - if (!await fileSystem.file(nativeAssetsFileUri).exists()) { - throwToolExit("${nativeAssetsFileUri.path} doesn't exist."); + if (!await nativeAssetsFile.exists()) { + throwToolExit("${nativeAssetsFile.path} doesn't exist."); } if (!await outputDepfile.exists()) { throwToolExit("${outputDepfile.path} doesn't exist."); } } + Future> _buildWindows( + Environment environment, + TargetPlatform targetPlatform, + Uri projectUri, + FileSystem fileSystem, + NativeAssetsBuildRunner buildRunner, + ) async { + final String? environmentBuildMode = environment.defines[kBuildMode]; + if (environmentBuildMode == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode); + final (_, List dependencies) = await buildNativeAssetsWindows( + targetPlatform: targetPlatform, + buildMode: buildMode, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + return dependencies; + } + + Future> _buildLinux( + Environment environment, + TargetPlatform targetPlatform, + Uri projectUri, + FileSystem fileSystem, + NativeAssetsBuildRunner buildRunner, + ) async { + final String? environmentBuildMode = environment.defines[kBuildMode]; + if (environmentBuildMode == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode); + final (_, List dependencies) = await buildNativeAssetsLinux( + targetPlatform: targetPlatform, + buildMode: buildMode, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + return dependencies; + } + + Future> _buildMacOS( + Environment environment, + Uri projectUri, + FileSystem fileSystem, + NativeAssetsBuildRunner buildRunner, + ) async { + final List darwinArchs = + _emptyToNull(environment.defines[kDarwinArchs]) + ?.split(' ') + .map(getDarwinArchForName) + .toList() ?? + [DarwinArch.x86_64, DarwinArch.arm64]; + final String? environmentBuildMode = environment.defines[kBuildMode]; + if (environmentBuildMode == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode); + final (_, List dependencies) = await buildNativeAssetsMacOS( + darwinArchs: darwinArchs, + buildMode: buildMode, + projectUri: projectUri, + codesignIdentity: environment.defines[kCodesignIdentity], + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + return dependencies; + } + + Future> _buildIOS( + Environment environment, + Uri projectUri, + FileSystem fileSystem, + NativeAssetsBuildRunner buildRunner, + ) { + final List iosArchs = + _emptyToNull(environment.defines[kIosArchs]) + ?.split(' ') + .map(getIOSArchForName) + .toList() ?? + [DarwinArch.arm64]; + final String? environmentBuildMode = environment.defines[kBuildMode]; + if (environmentBuildMode == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode); + final String? sdkRoot = environment.defines[kSdkRoot]; + if (sdkRoot == null) { + throw MissingDefineException(kSdkRoot, name); + } + final EnvironmentType environmentType = + environmentTypeFromSdkroot(sdkRoot, environment.fileSystem)!; + return buildNativeAssetsIOS( + environmentType: environmentType, + darwinArchs: iosArchs, + buildMode: buildMode, + projectUri: projectUri, + codesignIdentity: environment.defines[kCodesignIdentity], + fileSystem: fileSystem, + buildRunner: buildRunner, + yamlParentDirectory: environment.buildDir.uri, + ); + } + + Future<(Uri? nativeAssetsYaml, List dependencies)> _buildAndroid( + Environment environment, + TargetPlatform targetPlatform, + Uri projectUri, + FileSystem fileSystem, + NativeAssetsBuildRunner buildRunner) { + final String? androidArchsEnvironment = environment.defines[kAndroidArchs]; + final List androidArchs = _androidArchs( + targetPlatform, + androidArchsEnvironment, + ); + final int targetAndroidNdkApi = + int.parse(environment.defines[kMinSdkVersion] ?? minSdkVersion); + final String? environmentBuildMode = environment.defines[kBuildMode]; + if (environmentBuildMode == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode); + return buildNativeAssetsAndroid( + buildMode: buildMode, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + androidArchs: androidArchs, + targetAndroidNdkApi: targetAndroidNdkApi, + ); + } + + List _androidArchs( + TargetPlatform targetPlatform, + String? androidArchsEnvironment, + ) { + switch (targetPlatform) { + case TargetPlatform.android_arm: + return [AndroidArch.armeabi_v7a]; + case TargetPlatform.android_arm64: + return [AndroidArch.arm64_v8a]; + case TargetPlatform.android_x64: + return [AndroidArch.x86_64]; + case TargetPlatform.android_x86: + return [AndroidArch.x86]; + case TargetPlatform.android: + if (androidArchsEnvironment == null) { + throw MissingDefineException(kAndroidArchs, name); + } + return androidArchsEnvironment + .split(' ') + .map(getAndroidArchForName) + .toList(); + case TargetPlatform.darwin: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.ios: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: + throwToolExit('Unsupported Android target platform: $targetPlatform.'); + } + } + @override List get depfiles => [ 'native_assets.d', @@ -129,3 +390,10 @@ class NativeAssets extends Target { Source.pattern('{BUILD_DIR}/native_assets.yaml'), ]; } + +String? _emptyToNull(String? input) { + if (input == null || input.isEmpty) { + return null; + } + return input; +} diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart index c5fde536ce..1a8b2fcf6b 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart @@ -2,24 +2,145 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:native_assets_builder/native_assets_builder.dart'; -import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_builder/native_assets_builder.dart' + hide NativeAssetsBuildRunner; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import '../../../android/android_sdk.dart'; -import '../../../android/gradle_utils.dart'; import '../../../base/common.dart'; import '../../../base/file_system.dart'; -import '../../../build_info.dart' hide BuildMode; +import '../../../build_info.dart'; import '../../../globals.dart' as globals; +import '../native_assets.dart'; -int targetAndroidNdkApi(Map environmentDefines) { - return int.parse(environmentDefines[kMinSdkVersion] ?? minSdkVersion); +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file. +Future dryRunNativeAssetsAndroid({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, +}) async { + if (!await nativeBuildRequired(buildRunner)) { + return null; + } + + final Uri buildUri_ = nativeAssetsBuildUri(projectUri, OSImpl.android); + final Iterable nativeAssetPaths = + await dryRunNativeAssetsAndroidInternal( + fileSystem, + projectUri, + buildRunner, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(nativeAssetPaths), + buildUri_, + fileSystem, + ); + return nativeAssetsUri; } -Future copyNativeCodeAssetsAndroid( +Future> dryRunNativeAssetsAndroidInternal( + FileSystem fileSystem, + Uri projectUri, + NativeAssetsBuildRunner buildRunner, +) async { + const OSImpl targetOS = OSImpl.android; + + globals.logger.printTrace('Dry running native assets for $targetOS.'); + final BuildDryRunResult buildDryRunResult = await buildRunner.buildDryRun( + linkModePreference: LinkModePreferenceImpl.dynamic, + targetOS: targetOS, + workingDirectory: projectUri, + includeParentEnvironment: true, + ); + ensureNativeAssetsBuildDryRunSucceed(buildDryRunResult); + // No link hooks in JIT mode. + final List nativeAssets = buildDryRunResult.assets; + globals.logger.printTrace('Dry running native assets for $targetOS done.'); + final Map assetTargetLocations = + _assetTargetLocations(nativeAssets); + return assetTargetLocations.values; +} + +/// Builds native assets. +Future<(Uri? nativeAssetsYaml, List dependencies)> + buildNativeAssetsAndroid({ + required NativeAssetsBuildRunner buildRunner, + required Iterable androidArchs, + required Uri projectUri, + required BuildMode buildMode, + String? codesignIdentity, + Uri? yamlParentDirectory, + required FileSystem fileSystem, + required int targetAndroidNdkApi, +}) async { + const OSImpl targetOS = OSImpl.android; + final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOS); + if (!await nativeBuildRequired(buildRunner)) { + final Uri nativeAssetsYaml = await writeNativeAssetsYaml( + KernelAssets(), + yamlParentDirectory ?? buildUri_, + fileSystem, + ); + return (nativeAssetsYaml, []); + } + + final List targets = androidArchs.map(_getNativeTarget).toList(); + final BuildModeImpl buildModeCli = + nativeAssetsBuildMode(buildMode); + final bool linkingEnabled = buildModeCli == BuildModeImpl.release; + + globals.logger + .printTrace('Building native assets for $targets $buildModeCli.'); + final List nativeAssets = []; + final Set dependencies = {}; + for (final Target target in targets) { + final BuildResult buildResult = await buildRunner.build( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.ndkCCompilerConfigImpl, + targetAndroidNdkApi: targetAndroidNdkApi, + linkingEnabled: linkingEnabled, + ); + ensureNativeAssetsBuildSucceed(buildResult); + nativeAssets.addAll(buildResult.assets); + dependencies.addAll(buildResult.dependencies); + if (linkingEnabled) { + final LinkResult linkResult = await buildRunner.link( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.ndkCCompilerConfigImpl, + targetAndroidNdkApi: targetAndroidNdkApi, + buildResult: buildResult, + ); + ensureNativeAssetsLinkSucceed(linkResult); + nativeAssets.addAll(linkResult.assets); + dependencies.addAll(linkResult.dependencies); + } + } + globals.logger.printTrace('Building native assets for $targets done.'); + final Map assetTargetLocations = + _assetTargetLocations(nativeAssets); + await _copyNativeAssetsAndroid(buildUri_, assetTargetLocations, fileSystem); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(assetTargetLocations.values), + yamlParentDirectory ?? buildUri_, + fileSystem); + return (nativeAssetsUri, dependencies.toList()); +} + +Future _copyNativeAssetsAndroid( Uri buildUri, - Map assetTargetLocations, + Map assetTargetLocations, FileSystem fileSystem, ) async { if (assetTargetLocations.isNotEmpty) { @@ -33,7 +154,7 @@ Future copyNativeCodeAssetsAndroid( final Uri archUri = buildUri.resolve('jniLibs/lib/$jniArchDir/'); await fileSystem.directory(archUri).create(recursive: true); } - for (final MapEntry assetMapping + for (final MapEntry assetMapping in assetTargetLocations.entries) { final Uri source = assetMapping.key.file!; final Uri target = (assetMapping.value.path as KernelAssetAbsolutePath).uri; @@ -50,7 +171,7 @@ Future copyNativeCodeAssetsAndroid( } /// Get the [Target] for [androidArch]. -Target getNativeAndroidTarget(AndroidArch androidArch) { +Target _getNativeTarget(AndroidArch androidArch) { return switch (androidArch) { AndroidArch.armeabi_v7a => Target.androidArm, AndroidArch.arm64_v8a => Target.androidArm64, @@ -71,27 +192,27 @@ AndroidArch _getAndroidArch(Target target) { }; } -Map assetTargetLocationsAndroid( - List nativeAssets) { - return { - for (final NativeCodeAssetImpl asset in nativeAssets) +Map _assetTargetLocations( + List nativeAssets) { + return { + for (final AssetImpl asset in nativeAssets) asset: _targetLocationAndroid(asset), }; } /// Converts the `path` of [asset] as output from a `build.dart` invocation to /// the path used inside the Flutter app bundle. -KernelAsset _targetLocationAndroid(NativeCodeAssetImpl asset) { - final LinkMode linkMode = asset.linkMode; +KernelAsset _targetLocationAndroid(AssetImpl asset) { + final LinkModeImpl linkMode = (asset as NativeCodeAssetImpl).linkMode; final KernelAssetPath kernelAssetPath; switch (linkMode) { - case DynamicLoadingSystem _: + case DynamicLoadingSystemImpl _: kernelAssetPath = KernelAssetSystemPath(linkMode.uri); - case LookupInExecutable _: + case LookupInExecutableImpl _: kernelAssetPath = KernelAssetInExecutable(); - case LookupInProcess _: + case LookupInProcessImpl _: kernelAssetPath = KernelAssetInProcess(); - case DynamicLoadingBundled _: + case DynamicLoadingBundledImpl _: final String fileName = asset.file!.pathSegments.last; kernelAssetPath = KernelAssetAbsolutePath(Uri(path: fileName)); default: @@ -113,6 +234,7 @@ KernelAsset _targetLocationAndroid(NativeCodeAssetImpl asset) { /// Should only be invoked if a native assets build is performed. If the native /// assets feature is disabled, or none of the packages have native assets, a /// missing NDK is okay. +@override Future cCompilerConfigAndroid() async { final AndroidSdk? androidSdk = AndroidSdk.locateAndroidSdk(); if (androidSdk == null) { diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart index 20b5cb7c89..427ccc45fd 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart @@ -2,20 +2,147 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:native_assets_builder/native_assets_builder.dart'; -import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_builder/native_assets_builder.dart' + hide NativeAssetsBuildRunner; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import '../../../base/file_system.dart'; -import '../../../build_info.dart' hide BuildMode; -import '../../../build_info.dart' as build_info; +import '../../../build_info.dart'; import '../../../globals.dart' as globals; import '../macos/native_assets_host.dart'; +import '../native_assets.dart'; -// TODO(dcharkes): Fetch minimum iOS version from somewhere. https://github.com/flutter/flutter/issues/145104 -const int targetIOSVersion = 12; +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file and +/// the Xcode project. +Future dryRunNativeAssetsIOS({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + required FileSystem fileSystem, +}) async { + if (!await nativeBuildRequired(buildRunner)) { + return null; + } -IOSSdkImpl getIOSSdk(EnvironmentType environmentType) { + final Uri buildUri = nativeAssetsBuildUri(projectUri, OSImpl.iOS); + final Iterable assetTargetLocations = await dryRunNativeAssetsIOSInternal( + fileSystem, + projectUri, + buildRunner, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(assetTargetLocations), + buildUri, + fileSystem, + ); + return nativeAssetsUri; +} + +Future> dryRunNativeAssetsIOSInternal( + FileSystem fileSystem, + Uri projectUri, + NativeAssetsBuildRunner buildRunner, +) async { + const OSImpl targetOS = OSImpl.iOS; + globals.logger.printTrace('Dry running native assets for $targetOS.'); + final BuildDryRunResult buildDryRunResult = await buildRunner.buildDryRun( + linkModePreference: LinkModePreferenceImpl.dynamic, + targetOS: targetOS, + workingDirectory: projectUri, + includeParentEnvironment: true, + ); + ensureNativeAssetsBuildDryRunSucceed(buildDryRunResult); + // No link hooks in JIT. + final List nativeAssets = buildDryRunResult.assets; + globals.logger.printTrace('Dry running native assets for $targetOS done.'); + return _assetTargetLocations(nativeAssets).values; +} + +/// Builds native assets. +Future> buildNativeAssetsIOS({ + required NativeAssetsBuildRunner buildRunner, + required List darwinArchs, + required EnvironmentType environmentType, + required Uri projectUri, + required BuildMode buildMode, + String? codesignIdentity, + required Uri yamlParentDirectory, + required FileSystem fileSystem, +}) async { + if (!await nativeBuildRequired(buildRunner)) { + await writeNativeAssetsYaml(KernelAssets(), yamlParentDirectory, fileSystem); + return []; + } + + final List targets = darwinArchs.map(_getNativeTarget).toList(); + final BuildModeImpl buildModeCli = nativeAssetsBuildMode(buildMode); + final bool linkingEnabled = buildModeCli == BuildModeImpl.release; + + const OSImpl targetOS = OSImpl.iOS; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + final IOSSdkImpl iosSdk = _getIOSSdkImpl(environmentType); + + globals.logger.printTrace('Building native assets for $targets $buildModeCli.'); + final List nativeAssets = []; + final Set dependencies = {}; + for (final Target target in targets) { + final BuildResult buildResult = await buildRunner.build( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + targetIOSSdkImpl: iosSdk, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + // TODO(dcharkes): Fetch minimum iOS version from somewhere. https://github.com/flutter/flutter/issues/145104 + targetIOSVersion: 12, + linkingEnabled: linkingEnabled, + ); + ensureNativeAssetsBuildSucceed(buildResult); + nativeAssets.addAll(buildResult.assets); + dependencies.addAll(buildResult.dependencies); + if (linkingEnabled) { + final LinkResult linkResult = await buildRunner.link( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + targetIOSSdkImpl: iosSdk, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + buildResult: buildResult, + // TODO(dcharkes): Fetch minimum iOS version from somewhere. https://github.com/flutter/flutter/issues/145104 + targetIOSVersion: 12, + ); + ensureNativeAssetsLinkSucceed(linkResult); + nativeAssets.addAll(linkResult.assets); + dependencies.addAll(linkResult.dependencies); + } + } + globals.logger.printTrace('Building native assets for $targets done.'); + final Map> fatAssetTargetLocations = + _fatAssetTargetLocations(nativeAssets); + await _copyNativeAssetsIOS( + buildUri, + fatAssetTargetLocations, + codesignIdentity, + buildMode, + fileSystem, + ); + + final Map assetTargetLocations = + _assetTargetLocations(nativeAssets); + await writeNativeAssetsYaml( + KernelAssets(assetTargetLocations.values), + yamlParentDirectory, + fileSystem, + ); + return dependencies.toList(); +} + +IOSSdkImpl _getIOSSdkImpl(EnvironmentType environmentType) { return switch (environmentType) { EnvironmentType.physical => IOSSdkImpl.iPhoneOS, EnvironmentType.simulator => IOSSdkImpl.iPhoneSimulator, @@ -23,7 +150,7 @@ IOSSdkImpl getIOSSdk(EnvironmentType environmentType) { } /// Extract the [Target] from a [DarwinArch]. -Target getNativeIOSTarget(DarwinArch darwinArch) { +Target _getNativeTarget(DarwinArch darwinArch) { return switch (darwinArch) { DarwinArch.armv7 => Target.iOSArm, DarwinArch.arm64 => Target.iOSArm64, @@ -31,13 +158,13 @@ Target getNativeIOSTarget(DarwinArch darwinArch) { }; } -Map> fatAssetTargetLocationsIOS( - List nativeAssets) { +Map> _fatAssetTargetLocations( + List nativeAssets) { final Set alreadyTakenNames = {}; - final Map> result = - >{}; + final Map> result = + >{}; final Map idToPath = {}; - for (final NativeCodeAssetImpl asset in nativeAssets) { + for (final AssetImpl asset in nativeAssets) { // Use same target path for all assets with the same id. final KernelAssetPath path = idToPath[asset.id] ?? _targetLocationIOS( @@ -45,24 +172,23 @@ Map> fatAssetTargetLocationsIOS( alreadyTakenNames, ).path; idToPath[asset.id] = path; - result[path] ??= []; + result[path] ??= []; result[path]!.add(asset); } return result; } -Map assetTargetLocationsIOS( - List nativeAssets) { +Map _assetTargetLocations( + List nativeAssets) { final Set alreadyTakenNames = {}; final Map idToPath = {}; - final Map result = - {}; - for (final NativeCodeAssetImpl asset in nativeAssets) { - final KernelAssetPath path = - idToPath[asset.id] ?? _targetLocationIOS(asset, alreadyTakenNames).path; + final Map result = {}; + for (final AssetImpl asset in nativeAssets) { + final KernelAssetPath path = idToPath[asset.id] ?? + _targetLocationIOS(asset, alreadyTakenNames).path; idToPath[asset.id] = path; result[asset] = KernelAsset( - id: asset.id, + id: (asset as NativeCodeAssetImpl).id, target: Target.fromArchitectureAndOS(asset.architecture!, asset.os), path: path, ); @@ -70,18 +196,17 @@ Map assetTargetLocationsIOS( return result; } -KernelAsset _targetLocationIOS( - NativeCodeAssetImpl asset, Set alreadyTakenNames) { - final LinkMode linkMode = asset.linkMode; - final KernelAssetPath kernelAssetPath; +KernelAsset _targetLocationIOS(AssetImpl asset, Set alreadyTakenNames) { +final LinkModeImpl linkMode = (asset as NativeCodeAssetImpl).linkMode; +final KernelAssetPath kernelAssetPath; switch (linkMode) { - case DynamicLoadingSystem _: + case DynamicLoadingSystemImpl _: kernelAssetPath = KernelAssetSystemPath(linkMode.uri); - case LookupInExecutable _: + case LookupInExecutableImpl _: kernelAssetPath = KernelAssetInExecutable(); - case LookupInProcess _: + case LookupInProcessImpl _: kernelAssetPath = KernelAssetInProcess(); - case DynamicLoadingBundled _: + case DynamicLoadingBundledImpl _: final String fileName = asset.file!.pathSegments.last; kernelAssetPath = KernelAssetAbsolutePath(frameworkUri( fileName, @@ -111,11 +236,11 @@ KernelAsset _targetLocationIOS( /// /// Code signing is also done here, so that it doesn't have to be done in /// in xcode_backend.dart. -Future copyNativeCodeAssetsIOS( +Future _copyNativeAssetsIOS( Uri buildUri, - Map> assetTargetLocations, + Map> assetTargetLocations, String? codesignIdentity, - build_info.BuildMode buildMode, + BuildMode buildMode, FileSystem fileSystem, ) async { if (assetTargetLocations.isNotEmpty) { @@ -125,12 +250,11 @@ Future copyNativeCodeAssetsIOS( final Map oldToNewInstallNames = {}; final List<(File, String, Directory)> dylibs = <(File, String, Directory)>[]; - for (final MapEntry> assetMapping + for (final MapEntry> assetMapping in assetTargetLocations.entries) { final Uri target = (assetMapping.key as KernelAssetAbsolutePath).uri; final List sources = [ - for (final NativeCodeAssetImpl source in assetMapping.value) - fileSystem.file(source.file) + for (final AssetImpl source in assetMapping.value) fileSystem.file(source.file) ]; final Uri targetUri = buildUri.resolveUri(target); final File dylibFile = fileSystem.file(targetUri); @@ -141,8 +265,7 @@ Future copyNativeCodeAssetsIOS( await lipoDylibs(dylibFile, sources); final String dylibFileName = dylibFile.basename; - final String newInstallName = - '@rpath/$dylibFileName.framework/$dylibFileName'; + final String newInstallName = '@rpath/$dylibFileName.framework/$dylibFileName'; final Set oldInstallNames = await getInstallNamesDylib(dylibFile); for (final String oldInstallName in oldInstallNames) { oldToNewInstallNames[oldInstallName] = newInstallName; diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/linux/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/linux/native_assets.dart index 815146e96f..ee4be081dc 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/linux/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/linux/native_assets.dart @@ -2,12 +2,70 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:native_assets_builder/native_assets_builder.dart' + hide NativeAssetsBuildRunner; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import '../../../base/common.dart'; import '../../../base/file_system.dart'; import '../../../base/io.dart'; +import '../../../build_info.dart'; import '../../../globals.dart' as globals; +import '../native_assets.dart'; + +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file. +Future dryRunNativeAssetsLinux({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, +}) { + return dryRunNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + projectUri: projectUri, + flutterTester: flutterTester, + fileSystem: fileSystem, + os: OSImpl.linux, + ); +} + +Future> dryRunNativeAssetsLinuxInternal( + FileSystem fileSystem, + Uri projectUri, + bool flutterTester, + NativeAssetsBuildRunner buildRunner, +) { + return dryRunNativeAssetsSingleArchitectureInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + OSImpl.linux, + ); +} + +Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsLinux({ + required NativeAssetsBuildRunner buildRunner, + TargetPlatform? targetPlatform, + required Uri projectUri, + required BuildMode buildMode, + bool flutterTester = false, + Uri? yamlParentDirectory, + required FileSystem fileSystem, +}) { + return buildNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + targetPlatform: targetPlatform, + projectUri: projectUri, + buildMode: buildMode, + flutterTester: flutterTester, + yamlParentDirectory: yamlParentDirectory, + fileSystem: fileSystem, + ); +} /// Flutter expects `clang++` to be on the path on Linux hosts. /// diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart index 6878797a2c..e6d681aa45 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart @@ -2,21 +2,179 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:native_assets_builder/native_assets_builder.dart'; -import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_builder/native_assets_builder.dart' + hide NativeAssetsBuildRunner; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import '../../../base/file_system.dart'; -import '../../../build_info.dart' hide BuildMode; -import '../../../build_info.dart' as build_info; +import '../../../build_info.dart'; import '../../../globals.dart' as globals; +import '../native_assets.dart'; import 'native_assets_host.dart'; -// TODO(dcharkes): Fetch minimum MacOS version from somewhere. https://github.com/flutter/flutter/issues/145104 -const int targetMacOSVersion = 13; +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file and +/// the Xcode project. +Future dryRunNativeAssetsMacOS({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, +}) async { + if (!await nativeBuildRequired(buildRunner)) { + return null; + } + + final Uri buildUri = nativeAssetsBuildUri(projectUri, OSImpl.macOS); + final Iterable nativeAssetPaths = await dryRunNativeAssetsMacOSInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(nativeAssetPaths), + buildUri, + fileSystem, + ); + return nativeAssetsUri; +} + +Future> dryRunNativeAssetsMacOSInternal( + FileSystem fileSystem, + Uri projectUri, + bool flutterTester, + NativeAssetsBuildRunner buildRunner, +) async { + const OSImpl targetOS = OSImpl.macOS; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + + globals.logger.printTrace('Dry running native assets for $targetOS.'); + final BuildDryRunResult buildDryRunResult = await buildRunner.buildDryRun( + linkModePreference: LinkModePreferenceImpl.dynamic, + targetOS: targetOS, + workingDirectory: projectUri, + includeParentEnvironment: true, + ); + ensureNativeAssetsBuildDryRunSucceed(buildDryRunResult); + // No link hooks in JIT mode. + final List nativeAssets = buildDryRunResult.assets; + globals.logger.printTrace('Dry running native assets for $targetOS done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; + final Map assetTargetLocations = + _assetTargetLocations( + nativeAssets, + absolutePath, + ); + return assetTargetLocations.values; +} + +/// Builds native assets. +/// +/// If [darwinArchs] is omitted, the current target architecture is used. +/// +/// If [flutterTester] is true, absolute paths are emitted in the native +/// assets mapping. This can be used for JIT mode without sandbox on the host. +/// This is used in `flutter test` and `flutter run -d flutter-tester`. +Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsMacOS({ + required NativeAssetsBuildRunner buildRunner, + List? darwinArchs, + required Uri projectUri, + required BuildMode buildMode, + bool flutterTester = false, + String? codesignIdentity, + Uri? yamlParentDirectory, + required FileSystem fileSystem, +}) async { + const OSImpl targetOS = OSImpl.macOS; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + if (!await nativeBuildRequired(buildRunner)) { + final Uri nativeAssetsYaml = await writeNativeAssetsYaml( + KernelAssets(), + yamlParentDirectory ?? buildUri, + fileSystem, + ); + return (nativeAssetsYaml, []); + } + + final List targets = darwinArchs != null + ? darwinArchs.map(_getNativeTarget).toList() + : [Target.current]; + final BuildModeImpl buildModeCli = + nativeAssetsBuildMode(buildMode); + final bool linkingEnabled = buildModeCli == BuildModeImpl.release; + + globals.logger + .printTrace('Building native assets for $targets $buildModeCli.'); + final List nativeAssets = []; + final Set dependencies = {}; + for (final Target target in targets) { + final BuildResult buildResult = await buildRunner.build( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + // TODO(dcharkes): Fetch minimum MacOS version from somewhere. https://github.com/flutter/flutter/issues/145104 + targetMacOSVersion: 13, + linkingEnabled: linkingEnabled, + ); + ensureNativeAssetsBuildSucceed(buildResult); + nativeAssets.addAll(buildResult.assets); + dependencies.addAll(buildResult.dependencies); + if (linkingEnabled) { + final LinkResult linkResult = await buildRunner.link( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + buildResult: buildResult, + // TODO(dcharkes): Fetch minimum MacOS version from somewhere. https://github.com/flutter/flutter/issues/145104 + targetMacOSVersion: 13, + ); + ensureNativeAssetsLinkSucceed(linkResult); + nativeAssets.addAll(linkResult.assets); + dependencies.addAll(linkResult.dependencies); + } + } + globals.logger.printTrace('Building native assets for $targets done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; + final Map assetTargetLocations = + _assetTargetLocations(nativeAssets, absolutePath); + final Map> fatAssetTargetLocations = + _fatAssetTargetLocations(nativeAssets, absolutePath); + if (flutterTester) { + await _copyNativeAssetsMacOSFlutterTester( + buildUri, + fatAssetTargetLocations, + codesignIdentity, + buildMode, + fileSystem, + ); + } else { + await _copyNativeAssetsMacOS( + buildUri, + fatAssetTargetLocations, + codesignIdentity, + buildMode, + fileSystem, + ); + } + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(assetTargetLocations.values), + yamlParentDirectory ?? buildUri, + fileSystem, + ); + return (nativeAssetsUri, dependencies.toList()); +} /// Extract the [Target] from a [DarwinArch]. -Target getNativeMacOSTarget(DarwinArch darwinArch) { +Target _getNativeTarget(DarwinArch darwinArch) { return switch (darwinArch) { DarwinArch.arm64 => Target.macOSArm64, DarwinArch.x86_64 => Target.macOSX64, @@ -24,15 +182,15 @@ Target getNativeMacOSTarget(DarwinArch darwinArch) { }; } -Map> fatAssetTargetLocationsMacOS( - List nativeAssets, +Map> _fatAssetTargetLocations( + List nativeAssets, Uri? absolutePath, ) { final Set alreadyTakenNames = {}; - final Map> result = - >{}; + final Map> result = + >{}; final Map idToPath = {}; - for (final NativeCodeAssetImpl asset in nativeAssets) { + for (final AssetImpl asset in nativeAssets) { // Use same target path for all assets with the same id. final KernelAssetPath path = idToPath[asset.id] ?? _targetLocationMacOS( @@ -41,25 +199,25 @@ Map> fatAssetTargetLocationsMacOS( alreadyTakenNames, ).path; idToPath[asset.id] = path; - result[path] ??= []; + result[path] ??= []; result[path]!.add(asset); } return result; } -Map assetTargetLocationsMacOS( - List nativeAssets, +Map _assetTargetLocations( + List nativeAssets, Uri? absolutePath, ) { final Set alreadyTakenNames = {}; final Map idToPath = {}; - final Map result = {}; - for (final NativeCodeAssetImpl asset in nativeAssets) { + final Map result = {}; + for (final AssetImpl asset in nativeAssets) { final KernelAssetPath path = idToPath[asset.id] ?? _targetLocationMacOS(asset, absolutePath, alreadyTakenNames).path; idToPath[asset.id] = path; result[asset] = KernelAsset( - id: asset.id, + id: (asset as NativeCodeAssetImpl).id, target: Target.fromArchitectureAndOS(asset.architecture!, asset.os), path: path, ); @@ -68,20 +226,20 @@ Map assetTargetLocationsMacOS( } KernelAsset _targetLocationMacOS( - NativeCodeAssetImpl asset, + AssetImpl asset, Uri? absolutePath, Set alreadyTakenNames, ) { - final LinkMode linkMode = asset.linkMode; + final LinkModeImpl linkMode = (asset as NativeCodeAssetImpl).linkMode; final KernelAssetPath kernelAssetPath; switch (linkMode) { - case DynamicLoadingSystem _: + case DynamicLoadingSystemImpl _: kernelAssetPath = KernelAssetSystemPath(linkMode.uri); - case LookupInExecutable _: + case LookupInExecutableImpl _: kernelAssetPath = KernelAssetInExecutable(); - case LookupInProcess _: + case LookupInProcessImpl _: kernelAssetPath = KernelAssetInProcess(); - case DynamicLoadingBundled _: + case DynamicLoadingBundledImpl _: final String fileName = asset.file!.pathSegments.last; Uri uri; if (absolutePath != null) { @@ -121,11 +279,11 @@ KernelAsset _targetLocationMacOS( /// /// Code signing is also done here, so that it doesn't have to be done in /// in macos_assemble.sh. -Future copyNativeCodeAssetsMacOS( +Future _copyNativeAssetsMacOS( Uri buildUri, - Map> assetTargetLocations, + Map> assetTargetLocations, String? codesignIdentity, - build_info.BuildMode buildMode, + BuildMode buildMode, FileSystem fileSystem, ) async { if (assetTargetLocations.isNotEmpty) { @@ -136,11 +294,11 @@ Future copyNativeCodeAssetsMacOS( final Map oldToNewInstallNames = {}; final List<(File, String, Directory)> dylibs = <(File, String, Directory)>[]; - for (final MapEntry> assetMapping + for (final MapEntry> assetMapping in assetTargetLocations.entries) { final Uri target = (assetMapping.key as KernelAssetAbsolutePath).uri; final List sources = [ - for (final NativeCodeAssetImpl source in assetMapping.value) fileSystem.file(source.file), + for (final AssetImpl source in assetMapping.value) fileSystem.file(source.file), ]; final Uri targetUri = buildUri.resolveUri(target); final String name = targetUri.pathSegments.last; @@ -215,11 +373,11 @@ Future copyNativeCodeAssetsMacOS( /// so that the referenced library can be found the dynamic linker. /// /// Code signing is also done here. -Future copyNativeCodeAssetsMacOSFlutterTester( +Future _copyNativeAssetsMacOSFlutterTester( Uri buildUri, - Map> assetTargetLocations, + Map> assetTargetLocations, String? codesignIdentity, - build_info.BuildMode buildMode, + BuildMode buildMode, FileSystem fileSystem, ) async { if (assetTargetLocations.isNotEmpty) { @@ -230,11 +388,11 @@ Future copyNativeCodeAssetsMacOSFlutterTester( final Map oldToNewInstallNames = {}; final List<(File, String)> dylibs = <(File, String)>[]; - for (final MapEntry> assetMapping + for (final MapEntry> assetMapping in assetTargetLocations.entries) { final Uri target = (assetMapping.key as KernelAssetAbsolutePath).uri; final List sources = [ - for (final NativeCodeAssetImpl source in assetMapping.value) fileSystem.file(source.file), + for (final AssetImpl source in assetMapping.value) fileSystem.file(source.file), ]; final Uri targetUri = buildUri.resolveUri(target); final File dylibFile = fileSystem.file(targetUri); diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets_host.dart b/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets_host.dart index 503f9ae2a0..4ce136c74d 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets_host.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets_host.dart @@ -4,13 +4,13 @@ // Shared logic between iOS and macOS implementations of native assets. -import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_cli/native_assets_cli.dart' show Architecture; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import '../../../base/common.dart'; import '../../../base/file_system.dart'; import '../../../base/io.dart'; -import '../../../build_info.dart' as build_info; +import '../../../build_info.dart'; import '../../../convert.dart'; import '../../../globals.dart' as globals; @@ -99,13 +99,13 @@ Future setInstallNamesDylib( String newInstallName, Map oldToNewInstallNames, ) async { - final ProcessResult setInstallNamesResult = await globals.processManager.run( + final ProcessResult setInstallNamesResult = await globals.processManager.run( [ 'install_name_tool', '-id', newInstallName, - for (final MapEntry entry in oldToNewInstallNames - .entries) ...['-change', entry.key, entry.value], + for (final MapEntry entry in oldToNewInstallNames.entries) + ...['-change', entry.key, entry.value], dylibFile.path, ], ); @@ -135,16 +135,18 @@ Future> getInstallNamesDylib(File dylibFile) async { return { for (final List architectureSection - in parseOtoolArchitectureSections(installNameResult.stdout as String).values) + in parseOtoolArchitectureSections(installNameResult.stdout as String).values) // For each architecture, a separate install name is reported, which are // not necessarily the same. architectureSection.single, }; } + + Future codesignDylib( String? codesignIdentity, - build_info.BuildMode buildMode, + BuildMode buildMode, FileSystemEntity target, ) async { if (codesignIdentity == null || codesignIdentity.isEmpty) { @@ -155,7 +157,7 @@ Future codesignDylib( '--force', '--sign', codesignIdentity, - if (buildMode != build_info.BuildMode.release) ...[ + if (buildMode != BuildMode.release) ...[ // Mimic Xcode's timestamp codesigning behavior on non-release binaries. '--timestamp=none', ], diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index b28543ba16..99e1a6f522 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -5,7 +5,10 @@ // Logic for native assets shared between all host OSes. import 'package:logging/logging.dart' as logging; -import 'package:native_assets_builder/native_assets_builder.dart'; +import 'package:native_assets_builder/native_assets_builder.dart' + as native_assets_builder show NativeAssetsBuildRunner; +import 'package:native_assets_builder/native_assets_builder.dart' + hide NativeAssetsBuildRunner; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:package_config/package_config_types.dart'; @@ -15,11 +18,9 @@ import '../../base/file_system.dart'; import '../../base/logger.dart'; import '../../base/platform.dart'; import '../../build_info.dart' as build_info; -import '../../build_system/exceptions.dart'; import '../../cache.dart'; import '../../features.dart'; import '../../globals.dart' as globals; -import '../../macos/xcode.dart' as xcode; import '../../resident_runner.dart'; import '../../run_hot.dart'; import 'android/native_assets.dart'; @@ -29,159 +30,11 @@ import 'macos/native_assets.dart'; import 'macos/native_assets_host.dart'; import 'windows/native_assets.dart'; -/// The assets produced by a Dart build and the dependencies of those assets. -/// -/// If any of the dependencies change, then the Dart build should be performed -/// again. -final class DartBuildResult { - const DartBuildResult({required this.codeAssets, required this.dependencies}); - const DartBuildResult.empty() - : codeAssets = const [], - dependencies = const []; - - final List codeAssets; - final List dependencies; -} - -/// Invokes the build of all transitive Dart packages and prepares code assets -/// to be included in the native build. -Future<(DartBuildResult, Uri)> runFlutterSpecificDartBuild({ - required Map environmentDefines, - required FlutterNativeAssetsBuildRunner buildRunner, - required build_info.TargetPlatform targetPlatform, - required Uri projectUri, - Uri? nativeAssetsYamlUri, - required FileSystem fileSystem, -}) async { - final OS targetOS = _getNativeOSFromTargetPlatfrorm(targetPlatform); - final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); - final Directory buildDir = fileSystem.directory(buildUri); - - final bool flutterTester = targetPlatform == build_info.TargetPlatform.tester; - - if (nativeAssetsYamlUri == null) { - // Only `flutter test` uses the - // `build/native_assets//native_assets.yaml` file which uses absolute - // paths to the shared libraries. - // - // testCompilerBuildNativeAssets() passes `null` - assert(flutterTester); - nativeAssetsYamlUri ??= buildUri.resolve('native_assets.yaml'); - } - - if (!await buildDir.exists()) { - // Ensure the folder exists so the native build system can copy it even - // if there's no native assets. - await buildDir.create(recursive: true); - } - - if (!await _nativeBuildRequired(buildRunner)) { - await writeNativeAssetsYaml( - KernelAssets(), nativeAssetsYamlUri, fileSystem); - return (const DartBuildResult.empty(), nativeAssetsYamlUri); - } - - final build_info.BuildMode buildMode; - if (flutterTester) { - buildMode = build_info.BuildMode.debug; - } else { - final String? environmentBuildMode = - environmentDefines[build_info.kBuildMode]; - if (environmentBuildMode == null) { - throw MissingDefineException(build_info.kBuildMode, 'native_assets'); - } - buildMode = build_info.BuildMode.fromCliName(environmentBuildMode); - } - final List targets = flutterTester - ? [Target.current] - : _targetsForOS(targetPlatform, targetOS, environmentDefines); - final DartBuildResult result = targets.isEmpty - ? const DartBuildResult.empty() - : await _runDartBuild( - environmentDefines: environmentDefines, - buildRunner: buildRunner, - targets: targets, - projectUri: projectUri, - buildMode: _nativeAssetsBuildMode(buildMode), - fileSystem: fileSystem, - targetOS: targetOS); - - final String? codesignIdentity = - environmentDefines[build_info.kCodesignIdentity]; - - final Map assetTargetLocations = - _assetTargetLocationsForOS( - targetOS, result.codeAssets, flutterTester, buildUri); - await _copyNativeCodeAssetsForOS(targetOS, buildUri, buildMode, fileSystem, - assetTargetLocations, codesignIdentity, flutterTester); - final KernelAssets vmAssetMapping = - KernelAssets(assetTargetLocations.values.toList()); - await writeNativeAssetsYaml(vmAssetMapping, nativeAssetsYamlUri, fileSystem); - return (result, nativeAssetsYamlUri); -} - -Future runFlutterSpecificDartDryRunOnPlatforms({ - required Uri projectUri, - required FileSystem fileSystem, - required FlutterNativeAssetsBuildRunner buildRunner, - required List targetPlatforms, -}) async { - if (!await _nativeBuildRequired(buildRunner)) { - return null; - } - - final Map assetTargetLocations = - {}; - for (final build_info.TargetPlatform targetPlatform in targetPlatforms) { - // This dry-run functionality is only used in the `flutter run` - // implementation (not in `flutter build` or `flutter test`). - // - // Though we can end up with `flutterTester == true` if someone uses the - // `flutter-tester` device via `flutter run -d flutter-tester` (which mainly - // happens in tests) - final bool flutterTester = - targetPlatform == build_info.TargetPlatform.tester; - - final OSImpl targetOS = _getNativeOSFromTargetPlatfrorm(targetPlatform); - if (targetOS != OS.macOS && - targetOS != OS.windows && - targetOS != OS.linux) { - await ensureNoNativeAssetsOrOsIsSupported( - projectUri, - targetPlatform.toString(), - fileSystem, - buildRunner, - ); - } - - final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); - final DartBuildResult result = await _runDartDryRunBuild( - buildRunner: buildRunner, - projectUri: projectUri, - fileSystem: fileSystem, - targetOS: targetOS); - assetTargetLocations.addAll(_assetTargetLocationsForOS( - targetOS, result.codeAssets, flutterTester, buildUri)); - } - - final Uri buildUri = targetPlatforms.length == 1 - ? nativeAssetsBuildUri( - projectUri, _getNativeOSFromTargetPlatfrorm(targetPlatforms.single)) - : _buildUriMultiple(projectUri); - final Uri nativeAssetsYamlUri = buildUri.resolve('native_assets.yaml'); - await writeNativeAssetsYaml( - KernelAssets(assetTargetLocations.values.toList()), - nativeAssetsYamlUri, - fileSystem, - ); - return nativeAssetsYamlUri; -} - /// Programmatic API to be used by Dart launchers to invoke native builds. /// /// It enables mocking `package:native_assets_builder` package. /// It also enables mocking native toolchain discovery via [cCompilerConfig]. -abstract interface class FlutterNativeAssetsBuildRunner { +abstract class NativeAssetsBuildRunner { /// Whether the project has a `.dart_tools/package_config.json`. /// /// If there is no package config, [packagesWithNativeAssets], [build], and @@ -214,6 +67,15 @@ abstract interface class FlutterNativeAssetsBuildRunner { required bool linkingEnabled, }); + /// Runs all [packagesWithNativeAssets] `link.dart` in dry run. + Future linkDryRun({ + required bool includeParentEnvironment, + required LinkModePreferenceImpl linkModePreference, + required OSImpl targetOS, + required Uri workingDirectory, + required BuildDryRunResult buildDryRunResult, + }); + /// Runs all [packagesWithNativeAssets] `link.dart`. Future link({ required bool includeParentEnvironment, @@ -237,8 +99,8 @@ abstract interface class FlutterNativeAssetsBuildRunner { } /// Uses `package:native_assets_builder` for its implementation. -class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunner { - FlutterNativeAssetsBuildRunnerImpl( +class NativeAssetsBuildRunnerImpl implements NativeAssetsBuildRunner { + NativeAssetsBuildRunnerImpl( this.projectUri, this.packageConfigPath, this.packageConfig, @@ -269,7 +131,7 @@ class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunn late final Uri _dartExecutable = fileSystem.directory(Cache.flutterRoot).uri.resolve('bin/dart'); - late final NativeAssetsBuildRunner _buildRunner = NativeAssetsBuildRunner( + late final native_assets_builder.NativeAssetsBuildRunner _buildRunner = native_assets_builder.NativeAssetsBuildRunner( logger: _logger, dartExecutable: _dartExecutable, ); @@ -346,6 +208,29 @@ class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunn ); } + + @override + Future linkDryRun({ + required bool includeParentEnvironment, + required LinkModePreferenceImpl linkModePreference, + required OSImpl targetOS, + required Uri workingDirectory, + required BuildDryRunResult buildDryRunResult, + }) { + final PackageLayout packageLayout = PackageLayout.fromPackageConfig( + packageConfig, + Uri.file(packageConfigPath), + ); + return _buildRunner.linkDryRun( + includeParentEnvironment: includeParentEnvironment, + linkModePreference: linkModePreference, + targetOS: targetOS, + workingDirectory: workingDirectory, + packageLayout: packageLayout, + buildDryRunResult: buildDryRunResult, + ); + } + @override Future link({ required bool includeParentEnvironment, @@ -392,7 +277,7 @@ class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunn return cCompilerConfigWindows(); } if (globals.platform.isAndroid) { - throwToolExit('Should use ndkCCompilerConfig for Android.'); + throwToolExit('Should use ndkCCompilerConfigImpl for Android.'); } throwToolExit('Unknown target OS.'); }(); @@ -403,25 +288,26 @@ class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunn }(); } +/// Write [assets] to `native_assets.yaml` in [yamlParentDirectory]. Future writeNativeAssetsYaml( KernelAssets assets, - Uri nativeAssetsYamlUri, + Uri yamlParentDirectory, FileSystem fileSystem, ) async { - globals.logger.printTrace('Writing native assets yaml to $nativeAssetsYamlUri.'); + globals.logger.printTrace('Writing native_assets.yaml.'); final String nativeAssetsDartContents = assets.toNativeAssetsFile(); - final File nativeAssetsFile = fileSystem.file(nativeAssetsYamlUri); - final Directory parentDirectory = nativeAssetsFile.parent; + final Directory parentDirectory = fileSystem.directory(yamlParentDirectory); if (!await parentDirectory.exists()) { await parentDirectory.create(recursive: true); } + final File nativeAssetsFile = parentDirectory.childFile('native_assets.yaml'); await nativeAssetsFile.writeAsString(nativeAssetsDartContents); globals.logger.printTrace('Writing ${nativeAssetsFile.path} done.'); return nativeAssetsFile.uri; } /// Select the native asset build mode for a given Flutter build mode. -BuildModeImpl _nativeAssetsBuildMode(build_info.BuildMode buildMode) { +BuildModeImpl nativeAssetsBuildMode(build_info.BuildMode buildMode) { switch (buildMode) { case build_info.BuildMode.debug: return BuildModeImpl.debug; @@ -438,7 +324,7 @@ BuildModeImpl _nativeAssetsBuildMode(build_info.BuildMode buildMode) { /// /// Native asset builds cannot be run without a package config. If there is /// no package config, leave a logging trace about that. -Future _hasNoPackageConfig(FlutterNativeAssetsBuildRunner buildRunner) async { +Future _hasNoPackageConfig(NativeAssetsBuildRunner buildRunner) async { final bool packageConfigExists = await buildRunner.hasPackageConfig(); if (!packageConfigExists) { globals.logger.printTrace('No package config found. Skipping native assets compilation.'); @@ -446,8 +332,7 @@ Future _hasNoPackageConfig(FlutterNativeAssetsBuildRunner buildRunner) asy return !packageConfigExists; } - -Future _nativeBuildRequired(FlutterNativeAssetsBuildRunner buildRunner) async { +Future nativeBuildRequired(NativeAssetsBuildRunner buildRunner) async { if (await _hasNoPackageConfig(buildRunner)) { return false; } @@ -477,7 +362,7 @@ Future ensureNoNativeAssetsOrOsIsSupported( Uri workingDirectory, String os, FileSystem fileSystem, - FlutterNativeAssetsBuildRunner buildRunner, + NativeAssetsBuildRunner buildRunner, ) async { if (await _hasNoPackageConfig(buildRunner)) { return; @@ -504,11 +389,6 @@ Uri nativeAssetsBuildUri(Uri projectUri, OS os) { return projectUri.resolve('$buildDir/native_assets/$os/'); } -/// Gets the native asset id to dylib mapping to embed in the kernel file. -/// -/// Run hot compiles a kernel file that is pushed to the device after hot -/// restart. We need to embed the native assets mapping in order to access -/// native assets after hot restart. class HotRunnerNativeAssetsBuilderImpl implements HotRunnerNativeAssetsBuilder { const HotRunnerNativeAssetsBuilderImpl(); @@ -521,44 +401,351 @@ class HotRunnerNativeAssetsBuilderImpl implements HotRunnerNativeAssetsBuilder { required PackageConfig packageConfig, required Logger logger, }) async { - final FlutterNativeAssetsBuildRunner buildRunner = - FlutterNativeAssetsBuildRunnerImpl( + final NativeAssetsBuildRunner buildRunner = NativeAssetsBuildRunnerImpl( projectUri, packageConfigPath, packageConfig, fileSystem, globals.logger, ); - - // If `flutter run -d all` is used then we may have multiple OSes. - final List targetPlatforms = flutterDevices - .map((FlutterDevice d) => d.targetPlatform) - .nonNulls - .toList(); - - return runFlutterSpecificDartDryRunOnPlatforms( + return dryRunNativeAssets( projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, - targetPlatforms: targetPlatforms, + flutterDevices: flutterDevices, ); } } +/// Gets the native asset id to dylib mapping to embed in the kernel file. +/// +/// Run hot compiles a kernel file that is pushed to the device after hot +/// restart. We need to embed the native assets mapping in order to access +/// native assets after hot restart. +Future dryRunNativeAssets({ + required Uri projectUri, + required FileSystem fileSystem, + required NativeAssetsBuildRunner buildRunner, + required List flutterDevices, +}) async { + if (flutterDevices.length != 1) { + return dryRunNativeAssetsMultipleOSes( + projectUri: projectUri, + fileSystem: fileSystem, + targetPlatforms: flutterDevices.map((FlutterDevice d) => d.targetPlatform).nonNulls, + buildRunner: buildRunner, + ); + } + final FlutterDevice flutterDevice = flutterDevices.single; + final build_info.TargetPlatform targetPlatform = flutterDevice.targetPlatform!; + + final Uri? nativeAssetsYaml; + switch (targetPlatform) { + case build_info.TargetPlatform.darwin: + nativeAssetsYaml = await dryRunNativeAssetsMacOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + case build_info.TargetPlatform.ios: + nativeAssetsYaml = await dryRunNativeAssetsIOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + case build_info.TargetPlatform.tester: + if (const LocalPlatform().isMacOS) { + nativeAssetsYaml = await dryRunNativeAssetsMacOS( + projectUri: projectUri, + flutterTester: true, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + } else if (const LocalPlatform().isLinux) { + nativeAssetsYaml = await dryRunNativeAssetsLinux( + projectUri: projectUri, + flutterTester: true, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + } else if (const LocalPlatform().isWindows) { + nativeAssetsYaml = await dryRunNativeAssetsWindows( + projectUri: projectUri, + flutterTester: true, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + } else { + await nativeBuildRequired(buildRunner); + nativeAssetsYaml = null; + } + case build_info.TargetPlatform.linux_arm64: + case build_info.TargetPlatform.linux_x64: + nativeAssetsYaml = await dryRunNativeAssetsLinux( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + case build_info.TargetPlatform.windows_arm64: + case build_info.TargetPlatform.windows_x64: + nativeAssetsYaml = await dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + case build_info.TargetPlatform.android_arm: + case build_info.TargetPlatform.android_arm64: + case build_info.TargetPlatform.android_x64: + case build_info.TargetPlatform.android_x86: + case build_info.TargetPlatform.android: + nativeAssetsYaml = await dryRunNativeAssetsAndroid( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + case build_info.TargetPlatform.fuchsia_arm64: + case build_info.TargetPlatform.fuchsia_x64: + case build_info.TargetPlatform.web_javascript: + await ensureNoNativeAssetsOrOsIsSupported( + projectUri, + targetPlatform.toString(), + fileSystem, + buildRunner, + ); + nativeAssetsYaml = null; + } + return nativeAssetsYaml; +} + +/// Dry run the native builds for multiple OSes. +/// +/// Needed for `flutter run -d all`. +Future dryRunNativeAssetsMultipleOSes({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + required FileSystem fileSystem, + required Iterable targetPlatforms, +}) async { + if (await nativeBuildRequired(buildRunner)) { + return null; + } + + final Uri buildUri = buildUriMultiple(projectUri); + final Iterable nativeAssetPaths = [ + if (targetPlatforms.contains(build_info.TargetPlatform.darwin) || + (targetPlatforms.contains(build_info.TargetPlatform.tester) && + OSImpl.current == OSImpl.macOS)) + ...await dryRunNativeAssetsMacOSInternal( + fileSystem, + projectUri, + false, + buildRunner, + ), + if (targetPlatforms.contains(build_info.TargetPlatform.linux_arm64) || + targetPlatforms.contains(build_info.TargetPlatform.linux_x64) || + (targetPlatforms.contains(build_info.TargetPlatform.tester) && + OSImpl.current == OSImpl.linux)) + ...await dryRunNativeAssetsLinuxInternal( + fileSystem, + projectUri, + false, + buildRunner, + ), + if (targetPlatforms.contains(build_info.TargetPlatform.windows_arm64) || + targetPlatforms.contains(build_info.TargetPlatform.windows_x64) || + (targetPlatforms.contains(build_info.TargetPlatform.tester) && + OSImpl.current == OSImpl.windows)) + ...await dryRunNativeAssetsWindowsInternal( + fileSystem, + projectUri, + false, + buildRunner, + ), + if (targetPlatforms.contains(build_info.TargetPlatform.ios)) + ...await dryRunNativeAssetsIOSInternal( + fileSystem, + projectUri, + buildRunner, + ), + if (targetPlatforms.contains(build_info.TargetPlatform.android) || + targetPlatforms.contains(build_info.TargetPlatform.android_arm) || + targetPlatforms.contains(build_info.TargetPlatform.android_arm64) || + targetPlatforms.contains(build_info.TargetPlatform.android_x64) || + targetPlatforms.contains(build_info.TargetPlatform.android_x86)) + ...await dryRunNativeAssetsAndroidInternal( + fileSystem, + projectUri, + buildRunner, + ), + ]; + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(nativeAssetPaths), + buildUri, + fileSystem, + ); + return nativeAssetsUri; +} /// With `flutter run -d all` we need a place to store the native assets /// mapping for multiple OSes combined. -Uri _buildUriMultiple(Uri projectUri) { +Uri buildUriMultiple(Uri projectUri) { final String buildDir = build_info.getBuildDirectory(); return projectUri.resolve('$buildDir/native_assets/multiple/'); } -Map _assetTargetLocationsWindowsLinux( - List assets, +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file. +Future dryRunNativeAssetsSingleArchitecture({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, + required OSImpl os, +}) async { + if (!await nativeBuildRequired(buildRunner)) { + return null; + } + + final Uri buildUri = nativeAssetsBuildUri(projectUri, os); + final Iterable nativeAssetPaths = await dryRunNativeAssetsSingleArchitectureInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + os, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(nativeAssetPaths.toList()), + buildUri, + fileSystem, + ); + return nativeAssetsUri; +} + +Future> dryRunNativeAssetsSingleArchitectureInternal( + FileSystem fileSystem, + Uri projectUri, + bool flutterTester, + NativeAssetsBuildRunner buildRunner, + OSImpl targetOS, +) async { + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + + globals.logger.printTrace('Dry running native assets for $targetOS.'); + + final BuildDryRunResult buildDryRunResult = await buildRunner.buildDryRun( + linkModePreference: LinkModePreferenceImpl.dynamic, + targetOS: targetOS, + workingDirectory: projectUri, + includeParentEnvironment: true, + ); + ensureNativeAssetsBuildDryRunSucceed(buildDryRunResult); + // No link hooks in JIT mode. + final List nativeAssets = buildDryRunResult.assets; + globals.logger.printTrace('Dry running native assets for $targetOS done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; + final Map assetTargetLocations = + _assetTargetLocationsSingleArchitecture( + nativeAssets, + absolutePath, + ); + return assetTargetLocations.values; +} + +/// Builds native assets. +/// +/// If [targetPlatform] is omitted, the current target architecture is used. +/// +/// If [flutterTester] is true, absolute paths are emitted in the native +/// assets mapping. This can be used for JIT mode without sandbox on the host. +/// This is used in `flutter test` and `flutter run -d flutter-tester`. +Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsSingleArchitecture({ + required NativeAssetsBuildRunner buildRunner, + build_info.TargetPlatform? targetPlatform, + required Uri projectUri, + required build_info.BuildMode buildMode, + bool flutterTester = false, + Uri? yamlParentDirectory, + required FileSystem fileSystem, +}) async { + final Target target = targetPlatform != null ? _getNativeTarget(targetPlatform) : Target.current; + final OSImpl targetOS = target.os; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + final Directory buildDir = fileSystem.directory(buildUri); + if (!await buildDir.exists()) { + // CMake requires the folder to exist to do copying. + await buildDir.create(recursive: true); + } + if (!await nativeBuildRequired(buildRunner)) { + final Uri nativeAssetsYaml = await writeNativeAssetsYaml( + KernelAssets(), + yamlParentDirectory ?? buildUri, + fileSystem, + ); + return (nativeAssetsYaml, []); + } + + final BuildModeImpl buildModeCli = nativeAssetsBuildMode(buildMode); + final bool linkingEnabled = buildModeCli == BuildModeImpl.release; + + globals.logger.printTrace('Building native assets for $target $buildModeCli.'); + final BuildResult buildResult = await buildRunner.build( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + linkingEnabled: linkingEnabled, + ); + ensureNativeAssetsBuildSucceed(buildResult); + late final LinkResult linkResult; + if (linkingEnabled) { + linkResult = await buildRunner.link( + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + buildResult: buildResult, + ); + ensureNativeAssetsLinkSucceed(linkResult); + } + final List nativeAssets = [ + ...buildResult.assets, + if (linkingEnabled) ...linkResult.assets, + ]; + final Set dependencies = { + ...buildResult.dependencies, + if (linkingEnabled) ...linkResult.dependencies, + }; + globals.logger.printTrace('Building native assets for $target done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; + final Map assetTargetLocations = + _assetTargetLocationsSingleArchitecture(nativeAssets, absolutePath); + await _copyNativeAssetsSingleArchitecture( + buildUri, + assetTargetLocations, + buildMode, + fileSystem, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + KernelAssets(assetTargetLocations.values.toList()), + yamlParentDirectory ?? buildUri, + fileSystem, + ); + return (nativeAssetsUri, dependencies.toList()); +} + +Map _assetTargetLocationsSingleArchitecture( + List nativeAssets, Uri? absolutePath, ) { - return { - for (final NativeCodeAssetImpl asset in assets) + return { + for (final AssetImpl asset in nativeAssets) asset: _targetLocationSingleArchitecture( asset, absolutePath, @@ -567,17 +754,22 @@ Map _assetTargetLocationsWindowsLinux( } KernelAsset _targetLocationSingleArchitecture( - NativeCodeAssetImpl asset, Uri? absolutePath) { - final LinkMode linkMode = asset.linkMode; + AssetImpl asset, Uri? absolutePath) { + if (asset is! NativeCodeAssetImpl) { + throw Exception( + 'Unsupported asset type ${asset.runtimeType}', + ); + } + final LinkModeImpl linkMode = asset.linkMode; final KernelAssetPath kernelAssetPath; switch (linkMode) { - case DynamicLoadingSystem _: + case DynamicLoadingSystemImpl _: kernelAssetPath = KernelAssetSystemPath(linkMode.uri); - case LookupInExecutable _: + case LookupInExecutableImpl _: kernelAssetPath = KernelAssetInExecutable(); - case LookupInProcess _: + case LookupInProcessImpl _: kernelAssetPath = KernelAssetInProcess(); - case DynamicLoadingBundled _: + case DynamicLoadingBundledImpl _: final String fileName = asset.file!.pathSegments.last; Uri uri; if (absolutePath != null) { @@ -602,240 +794,10 @@ KernelAsset _targetLocationSingleArchitecture( ); } -Map _assetTargetLocationsForOS(OS targetOS, - List codeAssets, bool flutterTester, Uri buildUri) { - switch (targetOS) { - case OS.windows: - case OS.linux: - final Uri? absolutePath = flutterTester ? buildUri : null; - return _assetTargetLocationsWindowsLinux(codeAssets, absolutePath); - case OS.macOS: - final Uri? absolutePath = flutterTester ? buildUri : null; - return assetTargetLocationsMacOS(codeAssets, absolutePath); - case OS.iOS: - return assetTargetLocationsIOS(codeAssets); - case OS.android: - return assetTargetLocationsAndroid(codeAssets); - default: - throw UnimplementedError('This should be unreachable.'); - } -} - -Future _copyNativeCodeAssetsForOS( - OS targetOS, - Uri buildUri, - build_info.BuildMode buildMode, - FileSystem fileSystem, - Map assetTargetLocations, - String? codesignIdentity, - bool flutterTester) async { - final List codeAssets = - assetTargetLocations.keys.toList(); - switch (targetOS) { - case OS.windows: - case OS.linux: - assert(codesignIdentity == null); - await _copyNativeCodeAssetsToBundleOnWindowsLinux( - buildUri, - assetTargetLocations, - buildMode, - fileSystem, - ); - case OS.macOS: - if (flutterTester) { - await copyNativeCodeAssetsMacOSFlutterTester( - buildUri, - fatAssetTargetLocationsMacOS(codeAssets, buildUri), - codesignIdentity, - buildMode, - fileSystem, - ); - } else { - await copyNativeCodeAssetsMacOS( - buildUri, - fatAssetTargetLocationsMacOS(codeAssets, null), - codesignIdentity, - buildMode, - fileSystem, - ); - } - case OS.iOS: - await copyNativeCodeAssetsIOS( - buildUri, - fatAssetTargetLocationsIOS(codeAssets), - codesignIdentity, - buildMode, - fileSystem, - ); - case OS.android: - assert(codesignIdentity == null); - await copyNativeCodeAssetsAndroid( - buildUri, assetTargetLocations, fileSystem); - default: - throw StateError('This should be unreachable.'); - } -} - -/// Invokes the build of all transitive Dart packages. +/// Extract the [Target] from a [TargetPlatform]. /// -/// This will invoke `hook/build.dart` and `hook/link.dart` (if applicable) for -/// all transitive dart packages that define such hooks. -Future _runDartBuild({ - required Map environmentDefines, - required FlutterNativeAssetsBuildRunner buildRunner, - required List targets, - required Uri projectUri, - required BuildModeImpl buildMode, - required FileSystem fileSystem, - required OS targetOS, -}) async { - final bool linkingEnabled = buildMode == BuildMode.release; - final String targetString = targets.length == 1 - ? targets.single.toString() - : targets.toList().toString(); - globals.logger - .printTrace('Building native assets for $targetString $buildMode.'); - final List assets = []; - final Set dependencies = {}; - final build_info.EnvironmentType? environmentType; - if (targetOS == OS.iOS) { - final String? sdkRoot = environmentDefines[build_info.kSdkRoot]; - if (sdkRoot == null) { - throw MissingDefineException(build_info.kSdkRoot, 'native_assets'); - } - environmentType = xcode.environmentTypeFromSdkroot(sdkRoot, fileSystem); - } else { - environmentType = null; - } - - final CCompilerConfigImpl cCompilerConfig = targetOS == OS.android - ? await buildRunner.ndkCCompilerConfigImpl - : await buildRunner.cCompilerConfig; - - final String? codesignIdentity = environmentDefines[build_info.kCodesignIdentity]; - assert(codesignIdentity == null || targetOS == OS.iOS || targetOS == OS.macOS); - - final int? androidNdkApi = targetOS == OS.android ? targetAndroidNdkApi(environmentDefines) : null; - final int? iOSVersion = targetOS == OS.iOS ? targetIOSVersion : null; - final int? macOSVersion = targetOS == OS.macOS ? targetMacOSVersion : null; - final IOSSdkImpl? iOSSdkImpl = targetOS == OS.iOS ? getIOSSdk(environmentType!) : null; - - for (final Target target in targets) { - final BuildResult buildResult = await buildRunner.build( - linkModePreference: LinkModePreferenceImpl.dynamic, - target: target, - buildMode: buildMode, - workingDirectory: projectUri, - includeParentEnvironment: true, - linkingEnabled: linkingEnabled, - cCompilerConfig: cCompilerConfig, - targetAndroidNdkApi: androidNdkApi, - targetIOSVersion: iOSVersion, - targetMacOSVersion: macOSVersion, - targetIOSSdkImpl: iOSSdkImpl, - ); - if (!buildResult.success) { - _throwNativeAssetsBuildFailed(); - } - assets.addAll(buildResult.assets); - dependencies.addAll(buildResult.dependencies); - if (linkingEnabled) { - final LinkResult linkResult = await buildRunner.link( - linkModePreference: LinkModePreferenceImpl.dynamic, - target: target, - buildMode: buildMode, - workingDirectory: projectUri, - includeParentEnvironment: true, - buildResult: buildResult, - cCompilerConfig: cCompilerConfig, - targetAndroidNdkApi: androidNdkApi, - targetIOSVersion: iOSVersion, - targetMacOSVersion: macOSVersion, - targetIOSSdkImpl: iOSSdkImpl, - ); - if (!linkResult.success) { - _throwNativeAssetsLinkFailed(); - } - assets.addAll(linkResult.assets); - dependencies.addAll(linkResult.dependencies); - } - } - - final List codeAssets = - assets.whereType().toList(); - globals.logger - .printTrace('Building native assets for $targetString $buildMode done.'); - return DartBuildResult(codeAssets: codeAssets, dependencies: dependencies.toList()); -} - -Future _runDartDryRunBuild({ - required FlutterNativeAssetsBuildRunner buildRunner, - required Uri projectUri, - required FileSystem fileSystem, - required OSImpl targetOS, -}) async { - globals.logger.printTrace('Dry running native assets for $targetOS.'); - final List assets = []; - final Set dependencies = {}; - - final BuildDryRunResult buildResult = await buildRunner.buildDryRun( - linkModePreference: LinkModePreferenceImpl.dynamic, - targetOS: targetOS, - workingDirectory: projectUri, - includeParentEnvironment: true, - ); - if (!buildResult.success) { - _throwNativeAssetsBuildDryRunFailed(); - } - assets.addAll(buildResult.assets); - - final List codeAssets = - assets.whereType().toList(); - globals.logger.printTrace('Dry running native assets for $targetOS done.'); - return DartBuildResult(codeAssets: codeAssets, dependencies: dependencies.toList()); -} - -List _targetsForOS(build_info.TargetPlatform targetPlatform, - OS targetOS, Map environmentDefines) { - switch (targetOS) { - case OS.linux: - return [_getNativeTarget(targetPlatform)]; - case OS.windows: - return [_getNativeTarget(targetPlatform)]; - case OS.macOS: - final List darwinArchs = - _emptyToNull(environmentDefines[build_info.kDarwinArchs]) - ?.split(' ') - .map(build_info.getDarwinArchForName) - .toList() ?? - [ - build_info.DarwinArch.x86_64, - build_info.DarwinArch.arm64 - ]; - return darwinArchs.map(getNativeMacOSTarget).toList(); - case OS.android: - final String? androidArchsEnvironment = - environmentDefines[build_info.kAndroidArchs]; - final List androidArchs = _androidArchs( - targetPlatform, - androidArchsEnvironment, - ); - return androidArchs.map(getNativeAndroidTarget).toList(); - case OS.iOS: - final List iosArchs = - _emptyToNull(environmentDefines[build_info.kIosArchs]) - ?.split(' ') - .map(build_info.getIOSArchForName) - .toList() ?? - [build_info.DarwinArch.arm64]; - return iosArchs.map(getNativeIOSTarget).toList(); - default: - // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 - // Write the file we claim to have in the [outputs]. - return []; - } -} - +/// Does not cover MacOS, iOS, and Android as these pass the architecture +/// in other enums. Target _getNativeTarget(build_info.TargetPlatform targetPlatform) { switch (targetPlatform) { case build_info.TargetPlatform.linux_x64: @@ -861,124 +823,57 @@ Target _getNativeTarget(build_info.TargetPlatform targetPlatform) { } } -Future _copyNativeCodeAssetsToBundleOnWindowsLinux( +Future _copyNativeAssetsSingleArchitecture( Uri buildUri, - Map assetTargetLocations, + Map assetTargetLocations, build_info.BuildMode buildMode, FileSystem fileSystem, ) async { - globals.logger.printTrace('copyNativeCodeAssetsToBundleOnWindowsLinux()'); if (assetTargetLocations.isNotEmpty) { globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.'); final Directory buildDir = fileSystem.directory(buildUri.toFilePath()); if (!buildDir.existsSync()) { buildDir.createSync(recursive: true); } - for (final MapEntry assetMapping in assetTargetLocations.entries) { + for (final MapEntry assetMapping in assetTargetLocations.entries) { final Uri source = assetMapping.key.file!; final Uri target = (assetMapping.value.path as KernelAssetAbsolutePath).uri; final Uri targetUri = buildUri.resolveUri(target); final String targetFullPath = targetUri.toFilePath(); await fileSystem.file(source).copy(targetFullPath); - globals.logger.printTrace('copyNativeCodeAssetsToBundleOnWindowsLinux(): copied $source to $targetFullPath'); } globals.logger.printTrace('Copying native assets done.'); } } -Never _throwNativeAssetsBuildDryRunFailed() { - throwToolExit( - 'Building (dry run) native assets failed. See the logs for more details.', - ); -} - -Never _throwNativeAssetsBuildFailed() { - throwToolExit( - 'Building native assets failed. See the logs for more details.', - ); -} - -Never _throwNativeAssetsLinkFailed() { - throwToolExit( - 'Linking native assets failed. See the logs for more details.', - ); -} - - -OSImpl _getNativeOSFromTargetPlatfrorm(build_info.TargetPlatform platform) { - switch (platform) { - case build_info.TargetPlatform.ios: - return OSImpl.iOS; - case build_info.TargetPlatform.darwin: - return OSImpl.macOS; - case build_info.TargetPlatform.linux_x64: - case build_info.TargetPlatform.linux_arm64: - return OSImpl.linux; - case build_info.TargetPlatform.windows_x64: - case build_info.TargetPlatform.windows_arm64: - return OSImpl.windows; - case build_info.TargetPlatform.fuchsia_arm64: - case build_info.TargetPlatform.fuchsia_x64: - return OSImpl.fuchsia; - case build_info.TargetPlatform.android: - case build_info.TargetPlatform.android_arm: - case build_info.TargetPlatform.android_arm64: - case build_info.TargetPlatform.android_x64: - case build_info.TargetPlatform.android_x86: - return OSImpl.android; - case build_info.TargetPlatform.tester: - if (const LocalPlatform().isMacOS) { - return OSImpl.macOS; - } else if (const LocalPlatform().isLinux) { - return OSImpl.linux; - } else if (const LocalPlatform().isWindows) { - return OSImpl.windows; - } else { - throw StateError('Unknown operating system'); - } - case build_info.TargetPlatform.web_javascript: - throw StateError('No dart builds for web yet.'); +void ensureNativeAssetsBuildDryRunSucceed(BuildDryRunResult result) { + if (!result.success) { + throwToolExit( + 'Building (dry run) native assets failed. See the logs for more details.', + ); } } -List _androidArchs( - build_info.TargetPlatform targetPlatform, - String? androidArchsEnvironment, -) { - switch (targetPlatform) { - case build_info.TargetPlatform.android_arm: - return [build_info.AndroidArch.armeabi_v7a]; - case build_info.TargetPlatform.android_arm64: - return [build_info.AndroidArch.arm64_v8a]; - case build_info.TargetPlatform.android_x64: - return [build_info.AndroidArch.x86_64]; - case build_info.TargetPlatform.android_x86: - return [build_info.AndroidArch.x86]; - case build_info.TargetPlatform.android: - if (androidArchsEnvironment == null) { - throw MissingDefineException(build_info.kAndroidArchs, 'native_assets'); - } - return androidArchsEnvironment - .split(' ') - .map(build_info.getAndroidArchForName) - .toList(); - case build_info.TargetPlatform.darwin: - case build_info.TargetPlatform.fuchsia_arm64: - case build_info.TargetPlatform.fuchsia_x64: - case build_info.TargetPlatform.ios: - case build_info.TargetPlatform.linux_arm64: - case build_info.TargetPlatform.linux_x64: - case build_info.TargetPlatform.tester: - case build_info.TargetPlatform.web_javascript: - case build_info.TargetPlatform.windows_x64: - case build_info.TargetPlatform.windows_arm64: - throwToolExit('Unsupported Android target platform: $targetPlatform.'); +void ensureNativeAssetsBuildSucceed(BuildResult result) { + if (!result.success) { + throwToolExit( + 'Building native assets failed. See the logs for more details.', + ); } } -String? _emptyToNull(String? input) { - if (input == null || input.isEmpty) { - return null; +void ensureNativeAssetsLinkDryRunSucceed(LinkDryRunResult result) { + if (!result.success) { + throwToolExit( + 'Linking (dry run) native assets failed. See the logs for more details.', + ); + } +} + +void ensureNativeAssetsLinkSucceed(LinkResult result) { + if (!result.success) { + throwToolExit( + 'Linking native assets failed. See the logs for more details.', + ); } - return input; } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index 423c297670..d62ca7c76c 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -6,12 +6,16 @@ import 'package:native_assets_cli/native_assets_cli.dart'; +import '../../../base/os.dart'; import '../../../base/platform.dart'; import '../../../build_info.dart'; import '../../../globals.dart' as globals; import '../../../native_assets.dart'; import '../../../project.dart'; +import '../linux/native_assets.dart'; +import '../macos/native_assets.dart'; import '../native_assets.dart'; +import '../windows/native_assets.dart'; class TestCompilerNativeAssetsBuilderImpl implements TestCompilerNativeAssetsBuilder { @@ -27,36 +31,57 @@ class TestCompilerNativeAssetsBuilderImpl } Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { + Uri? nativeAssetsYaml; if (!buildInfo.buildNativeAssets) { - return null; - } - final Uri projectUri = FlutterProject.current().directory.uri; - final FlutterNativeAssetsBuildRunner buildRunner = FlutterNativeAssetsBuildRunnerImpl( - projectUri, - buildInfo.packageConfigPath, - buildInfo.packageConfig, - globals.fs, - globals.logger, - ); - - if (!globals.platform.isMacOS && - !globals.platform.isLinux && - !globals.platform.isWindows) { - await ensureNoNativeAssetsOrOsIsSupported( + nativeAssetsYaml = null; + } else { + final Uri projectUri = FlutterProject.current().directory.uri; + final NativeAssetsBuildRunner buildRunner = NativeAssetsBuildRunnerImpl( projectUri, - const LocalPlatform().operatingSystem, + buildInfo.packageConfigPath, + buildInfo.packageConfig, globals.fs, - buildRunner, + globals.logger, ); - return null; + if (globals.platform.isMacOS) { + (nativeAssetsYaml, _) = await buildNativeAssetsMacOS( + buildMode: buildInfo.mode, + projectUri: projectUri, + flutterTester: true, + fileSystem: globals.fs, + buildRunner: buildRunner, + ); + } else if (globals.platform.isLinux) { + (nativeAssetsYaml, _) = await buildNativeAssetsLinux( + buildMode: buildInfo.mode, + projectUri: projectUri, + flutterTester: true, + fileSystem: globals.fs, + buildRunner: buildRunner, + ); + } else if (globals.platform.isWindows) { + final TargetPlatform targetPlatform; + if (globals.os.hostPlatform == HostPlatform.windows_x64) { + targetPlatform = TargetPlatform.windows_x64; + } else { + targetPlatform = TargetPlatform.windows_arm64; + } + (nativeAssetsYaml, _) = await buildNativeAssetsWindows( + buildMode: buildInfo.mode, + targetPlatform: targetPlatform, + projectUri: projectUri, + flutterTester: true, + fileSystem: globals.fs, + buildRunner: buildRunner, + ); + } else { + await ensureNoNativeAssetsOrOsIsSupported( + projectUri, + const LocalPlatform().operatingSystem, + globals.fs, + buildRunner, + ); + } } - final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: buildInfo.mode.cliName, - }, - buildRunner: buildRunner, - targetPlatform: TargetPlatform.tester, - projectUri: projectUri, - fileSystem: globals.fs); return nativeAssetsYaml; } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/windows/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/windows/native_assets.dart index f61b1bc76d..11703c4f28 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/windows/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/windows/native_assets.dart @@ -2,10 +2,70 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:native_assets_builder/native_assets_builder.dart' + hide NativeAssetsBuildRunner; import 'package:native_assets_cli/native_assets_cli_internal.dart'; +import '../../../base/file_system.dart'; +import '../../../build_info.dart'; import '../../../globals.dart' as globals; import '../../../windows/visual_studio.dart'; +import '../native_assets.dart'; + +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file. +Future dryRunNativeAssetsWindows({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, +}) { + return dryRunNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + projectUri: projectUri, + flutterTester: flutterTester, + fileSystem: fileSystem, + os: OSImpl.windows, + ); +} + +Future> dryRunNativeAssetsWindowsInternal( + FileSystem fileSystem, + Uri projectUri, + bool flutterTester, + NativeAssetsBuildRunner buildRunner, +) { + return dryRunNativeAssetsSingleArchitectureInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + OSImpl.windows, + ); +} + +Future<(Uri? nativeAssetsYaml, List dependencies)> + buildNativeAssetsWindows({ + required NativeAssetsBuildRunner buildRunner, + TargetPlatform? targetPlatform, + required Uri projectUri, + required BuildMode buildMode, + bool flutterTester = false, + Uri? yamlParentDirectory, + required FileSystem fileSystem, +}) { + return buildNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + targetPlatform: targetPlatform, + projectUri: projectUri, + buildMode: buildMode, + flutterTester: flutterTester, + yamlParentDirectory: yamlParentDirectory, + fileSystem: fileSystem, + ); +} Future cCompilerConfigWindows() async { final VisualStudio visualStudio = VisualStudio( diff --git a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart index 587a2a47a1..3d0a03251d 100644 --- a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart @@ -5,7 +5,6 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/android/gradle_utils.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -15,7 +14,7 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; +import 'package:flutter_tools/src/isolated/native_assets/android/native_assets.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:package_config/package_config_types.dart'; @@ -49,6 +48,173 @@ void main() { projectUri = environment.projectDir.uri; }); + testUsingContext('dry run with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + expect( + await dryRunNativeAssetsAndroid( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ), + null, + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('build with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await buildNativeAssetsAndroid( + androidArchs: [AndroidArch.arm64_v8a], + targetAndroidNdkApi: 21, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsAndroid( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('dry run with assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.macOS, + architecture: ArchitectureImpl.arm64, + file: Uri.file('libbar.so'), + ), + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.macOS, + architecture: ArchitectureImpl.x64, + file: Uri.file('libbar.so'), + ), + ], + ), + ); + final Uri? nativeAssetsYaml = await dryRunNativeAssetsAndroid( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for android.', + 'Dry running native assets for android done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/android/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + contains('package:bar/bar.dart'), + ); + expect(buildRunner.buildDryRunInvocations, 1); + expect(buildRunner.linkDryRunInvocations, 0); + }); + + testUsingContext('build with assets but not enabled', () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsAndroid( + androidArchs: [AndroidArch.arm64_v8a], + targetAndroidNdkApi: 21, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('build no assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + await buildNativeAssetsAndroid( + androidArchs: [AndroidArch.arm64_v8a], + targetAndroidNdkApi: 21, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ); + expect( + environment.buildDir.childFile('native_assets.yaml'), + exists, + ); + }); + for (final BuildMode buildMode in [ BuildMode.debug, BuildMode.release, @@ -61,17 +227,17 @@ void main() { }, () async { final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.parent.create(); await packageConfig.create(); final File dylibAfterCompiling = fileSystem.file('libbar.so'); // The mock doesn't create the file, so create it here. await dylibAfterCompiling.create(); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = + FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', projectUri), ], - buildResult: FakeFlutterNativeAssetsBuilderResult( + buildResult: FakeNativeAssetsBuilderResult( assets: [ NativeCodeAssetImpl( id: 'package:bar/bar.dart', @@ -83,25 +249,22 @@ void main() { ], ), ); - await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: buildMode.cliName, - kMinSdkVersion: minSdkVersion, - }, - targetPlatform: TargetPlatform.android_arm64, + await buildNativeAssetsAndroid( + androidArchs: [AndroidArch.arm64_v8a], + targetAndroidNdkApi: 21, projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, + buildMode: buildMode, fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, buildRunner: buildRunner, ); expect( (globals.logger as BufferLogger).traceText, stringContainsInOrder([ - 'Building native assets for android_arm64 $buildMode.', - 'Building native assets for android_arm64 $buildMode done.', + 'Building native assets for [android_arm64] $buildMode.', + 'Building native assets for [android_arm64] done.', ]), ); - expect( environment.buildDir.childFile('native_assets.yaml'), exists, @@ -124,16 +287,12 @@ void main() { }, () async { final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.create(recursive: true); - await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - kMinSdkVersion: minSdkVersion, - }, - targetPlatform: TargetPlatform.android_x64, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, + await buildNativeAssetsAndroid( + androidArchs: [AndroidArch.x86_64], + targetAndroidNdkApi: 21, projectUri: projectUri, + buildMode: BuildMode.debug, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(), ); @@ -149,19 +308,16 @@ void main() { }, () async { final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.parent.create(); await packageConfig.create(); expect( - () => runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - kMinSdkVersion: minSdkVersion, - }, - targetPlatform: TargetPlatform.android_arm64, + () => buildNativeAssetsAndroid( + androidArchs: [AndroidArch.arm64_v8a], + targetAndroidNdkApi: 21, projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, + buildMode: BuildMode.debug, fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, buildRunner: _BuildRunnerWithoutNdk( packagesWithNativeAssetsResult: [ Package('bar', projectUri), @@ -174,9 +330,73 @@ void main() { ); }); + testUsingContext('Native assets dry run error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsAndroid( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building (dry run) native assets failed. See the logs for more details.', + ), + ); + }); + + testUsingContext('Native assets build error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + for (final String hook in ['Building', 'Linking']) { + expect( + () => buildNativeAssetsAndroid( + androidArchs: [AndroidArch.arm64_v8a], + targetAndroidNdkApi: 21, + projectUri: projectUri, + buildMode: BuildMode.release, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: FakeNativeAssetsBuilderResult( + success: hook != 'Building', + ), + linkResult: FakeNativeAssetsBuilderResult( + success: hook != 'Linking', + ), + ), + ), + throwsToolExit( + message: + '$hook native assets failed. See the logs for more details.', + ), + ); + } + }); } -class _BuildRunnerWithoutNdk extends FakeFlutterNativeAssetsBuildRunner { +class _BuildRunnerWithoutNdk extends FakeNativeAssetsBuildRunner { _BuildRunnerWithoutNdk({ super.packagesWithNativeAssetsResult = const [], }); diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index fc68734822..c8210fe7a9 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -76,29 +76,21 @@ void main() { iosEnvironment.defines.remove(kIosArchs); - final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(); + final NativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner(); await NativeAssets(buildRunner: buildRunner).build(iosEnvironment); final File nativeAssetsYaml = iosEnvironment.buildDir.childFile('native_assets.yaml'); - final File depsFile = iosEnvironment.buildDir.childFile('native_assets.d'); expect(depsFile, exists); expect(nativeAssetsYaml, exists); }); - testUsingContext('NativeAssets throws error if missing sdk root', overrides: { - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), - }, () async { + testUsingContext('NativeAssets throws error if missing sdk root', () async { await createPackageConfig(iosEnvironment); - final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('foo', iosEnvironment.projectDir.uri), - ]); - iosEnvironment.defines.remove(kSdkRoot); - expect(NativeAssets(buildRunner: buildRunner).build(iosEnvironment), throwsA(isA())); + expect(const NativeAssets().build(iosEnvironment), throwsA(isA())); }); // The NativeAssets Target should _always_ be creating a yaml an d file. @@ -117,7 +109,7 @@ void main() { () async { await createPackageConfig(iosEnvironment); - final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(); + final NativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner(); await NativeAssets(buildRunner: buildRunner).build(iosEnvironment); expect(iosEnvironment.buildDir.childFile('native_assets.d'), exists); @@ -190,9 +182,9 @@ void main() { () async { await createPackageConfig(iosEnvironment); - final FlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final NativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [Package('foo', iosEnvironment.buildDir.uri)], - buildResult: FakeFlutterNativeAssetsBuilderResult( + buildResult: FakeNativeAssetsBuilderResult( assets: [ native_assets_cli.NativeCodeAssetImpl( id: 'package:foo/foo.dart', @@ -251,11 +243,11 @@ void main() { await createPackageConfig(androidEnvironment); await fileSystem.file('libfoo.so').create(); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('foo', androidEnvironment.buildDir.uri) ], - buildResult: FakeFlutterNativeAssetsBuilderResult( + buildResult: FakeNativeAssetsBuilderResult( assets: [ if (hasAssets) native_assets_cli.NativeCodeAssetImpl( diff --git a/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart b/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart index 339f6a7faa..20bf8d1e1c 100644 --- a/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart +++ b/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart @@ -4,7 +4,6 @@ import 'package:file/file.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/run_hot.dart'; @@ -15,15 +14,15 @@ import 'package:package_config/package_config_types.dart'; /// Mocks all logic instead of using `package:native_assets_builder`, which /// relies on doing process calls to `pub` and the local file system. -class FakeFlutterNativeAssetsBuildRunner implements FlutterNativeAssetsBuildRunner { - FakeFlutterNativeAssetsBuildRunner({ +class FakeNativeAssetsBuildRunner implements NativeAssetsBuildRunner { + FakeNativeAssetsBuildRunner({ this.hasPackageConfigResult = true, this.packagesWithNativeAssetsResult = const [], this.onBuild, - this.buildDryRunResult = const FakeFlutterNativeAssetsBuilderResult(), - this.buildResult = const FakeFlutterNativeAssetsBuilderResult(), - this.linkResult = const FakeFlutterNativeAssetsBuilderResult(), - this.linkDryRunResult = const FakeFlutterNativeAssetsBuilderResult(), + this.buildDryRunResult = const FakeNativeAssetsBuilderResult(), + this.buildResult = const FakeNativeAssetsBuilderResult(), + this.linkResult = const FakeNativeAssetsBuilderResult(), + this.linkDryRunResult = const FakeNativeAssetsBuilderResult(), CCompilerConfigImpl? cCompilerConfigResult, CCompilerConfigImpl? ndkCCompilerConfigImplResult, }) : cCompilerConfigResult = cCompilerConfigResult ?? CCompilerConfigImpl(), @@ -97,6 +96,18 @@ class FakeFlutterNativeAssetsBuildRunner implements FlutterNativeAssetsBuildRunn return buildDryRunResult; } + @override + Future linkDryRun({ + required bool includeParentEnvironment, + required LinkModePreferenceImpl linkModePreference, + required OSImpl targetOS, + required Uri workingDirectory, + required native_assets_builder.BuildDryRunResult buildDryRunResult, + }) async { + linkDryRunInvocations++; + return linkDryRunResult; + } + @override Future hasPackageConfig() async { hasPackageConfigInvocations++; @@ -118,13 +129,13 @@ class FakeFlutterNativeAssetsBuildRunner implements FlutterNativeAssetsBuildRunn cCompilerConfigResult; } -final class FakeFlutterNativeAssetsBuilderResult +final class FakeNativeAssetsBuilderResult implements native_assets_builder.BuildResult, native_assets_builder.BuildDryRunResult, native_assets_builder.LinkResult, native_assets_builder.LinkDryRunResult { - const FakeFlutterNativeAssetsBuilderResult({ + const FakeNativeAssetsBuilderResult({ this.assets = const [], this.assetsForLinking = const >{}, this.dependencies = const [], @@ -147,7 +158,7 @@ final class FakeFlutterNativeAssetsBuilderResult class FakeHotRunnerNativeAssetsBuilder implements HotRunnerNativeAssetsBuilder { FakeHotRunnerNativeAssetsBuilder(this.buildRunner); - final FlutterNativeAssetsBuildRunner buildRunner; + final NativeAssetsBuildRunner buildRunner; @override Future dryRun({ @@ -158,15 +169,11 @@ class FakeHotRunnerNativeAssetsBuilder implements HotRunnerNativeAssetsBuilder { required PackageConfig packageConfig, required Logger logger, }) { - final List targetPlatforms = flutterDevices - .map((FlutterDevice d) => d.targetPlatform) - .nonNulls - .toList(); - return runFlutterSpecificDartDryRunOnPlatforms( + return dryRunNativeAssets( projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, - targetPlatforms: targetPlatforms, + flutterDevices: flutterDevices, ); } } diff --git a/packages/flutter_tools/test/general.shard/isolated/hot_test.dart b/packages/flutter_tools/test/general.shard/isolated/hot_test.dart index 64aa9fbf78..ff10468eec 100644 --- a/packages/flutter_tools/test/general.shard/isolated/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/hot_test.dart @@ -58,11 +58,11 @@ void main() { (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', fileSystem.currentDirectory.uri), ], - buildDryRunResult: FakeFlutterNativeAssetsBuilderResult( + buildDryRunResult: FakeNativeAssetsBuilderResult( assets: [ NativeCodeAssetImpl( id: 'package:bar/bar.dart', @@ -127,11 +127,11 @@ void main() { (fakeFlutterDevice.devFS! as FakeDevFs).baseUri = Uri.parse('file:///base_uri'); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', fileSystem.currentDirectory.uri), ], - buildDryRunResult: FakeFlutterNativeAssetsBuilderResult( + buildDryRunResult: FakeNativeAssetsBuilderResult( assets: [ NativeCodeAssetImpl( id: 'package:bar/bar.dart', diff --git a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart index da875d8d91..21bd6e6944 100644 --- a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart @@ -13,8 +13,9 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; -import 'package:native_assets_cli/native_assets_cli_internal.dart' hide Target; +import 'package:flutter_tools/src/isolated/native_assets/ios/native_assets.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart' + hide Target; import 'package:native_assets_cli/native_assets_cli_internal.dart' as native_assets_cli; import 'package:package_config/package_config_types.dart'; @@ -49,6 +50,174 @@ void main() { projectUri = environment.projectDir.uri; }); + testUsingContext('dry run with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + expect( + await dryRunNativeAssetsIOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ), + null, + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('build with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await buildNativeAssetsIOS( + darwinArchs: [DarwinArch.arm64], + environmentType: EnvironmentType.simulator, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsIOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('dry run with assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.macOS, + architecture: ArchitectureImpl.arm64, + file: Uri.file('libbar.dylib'), + ), + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.macOS, + architecture: ArchitectureImpl.x64, + file: Uri.file('libbar.dylib'), + ), + ], + ), + ); + final Uri? nativeAssetsYaml = await dryRunNativeAssetsIOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for ios.', + 'Dry running native assets for ios done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/ios/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + contains('package:bar/bar.dart'), + ); + expect(buildRunner.buildDryRunInvocations, 1); + expect(buildRunner.linkDryRunInvocations, 0); + }); + + testUsingContext('build with assets but not enabled', () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsIOS( + darwinArchs: [DarwinArch.arm64], + environmentType: EnvironmentType.simulator, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('build no assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + await buildNativeAssetsIOS( + darwinArchs: [DarwinArch.arm64], + environmentType: EnvironmentType.simulator, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ); + expect( + environment.buildDir.childFile('native_assets.yaml'), + exists, + ); + }); + + for (final BuildMode buildMode in [ BuildMode.debug, BuildMode.release, @@ -161,15 +330,15 @@ void main() { } final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.parent.create(); await packageConfig.create(); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = + FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', projectUri), ], onBuild: (native_assets_cli.Target target) => - FakeFlutterNativeAssetsBuilderResult( + FakeNativeAssetsBuilderResult( assets: [ NativeCodeAssetImpl( id: 'package:bar/bar.dart', @@ -186,25 +355,22 @@ void main() { file: Uri.file('${target.architecture}/libbuz.dylib'), ), ], - ), + ), ); - await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: buildMode.cliName, - kSdkRoot: '.../iPhone Simulator', - kIosArchs: 'arm64 x86_64', - }, - targetPlatform: TargetPlatform.ios, + await buildNativeAssetsIOS( + darwinArchs: [DarwinArch.arm64, DarwinArch.x86_64], + environmentType: EnvironmentType.simulator, projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, + buildMode: buildMode, fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, buildRunner: buildRunner, ); expect( (globals.logger as BufferLogger).traceText, stringContainsInOrder([ 'Building native assets for [ios_arm64, ios_x64] $buildMode.', - 'Building native assets for [ios_arm64, ios_x64] $buildMode done.', + 'Building native assets for [ios_arm64, ios_x64] done.', ]), ); expect( @@ -219,4 +385,69 @@ void main() { ); }); } + + testUsingContext('Native assets dry run error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsIOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building (dry run) native assets failed. See the logs for more details.', + ), + ); + }); + + testUsingContext('Native assets build error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + for (final String hook in ['Building', 'Linking']) { + expect( + () => buildNativeAssetsIOS( + darwinArchs: [DarwinArch.arm64], + environmentType: EnvironmentType.simulator, + projectUri: projectUri, + buildMode: BuildMode.release, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: FakeNativeAssetsBuilderResult( + success: hook != 'Building', + ), + linkResult: FakeNativeAssetsBuilderResult( + success: hook != 'Linking', + ), + ), + ), + throwsToolExit( + message: + '$hook native assets failed. See the logs for more details.', + ), + ); + } + }); } diff --git a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart index 978e733c47..793df6f7ec 100644 --- a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart @@ -4,6 +4,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -14,6 +15,7 @@ import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/dart/package_map.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/isolated/native_assets/linux/native_assets.dart'; import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart' hide Target; @@ -49,21 +51,51 @@ void main() { projectUri = environment.projectDir.uri; }); + testUsingContext('dry run with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + expect( + await dryRunNativeAssetsLinux( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ), + null, + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('build with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await buildNativeAssetsLinux( + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + testUsingContext('does not throw if clang not present but no native assets present', overrides: { FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), ProcessManager: () => FakeProcessManager.empty(), }, () async { final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.create(recursive: true); - - await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - }, - targetPlatform: TargetPlatform.linux_x64, + await buildNativeAssetsLinux( projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, + buildMode: BuildMode.debug, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutClang(), ); @@ -73,6 +105,300 @@ void main() { ); }); + testUsingContext('dry run for multiple OSes with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await dryRunNativeAssetsMultipleOSes( + projectUri: projectUri, + fileSystem: fileSystem, + targetPlatforms: [ + TargetPlatform.darwin, + TargetPlatform.ios, + ], + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsLinux( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('dry run with assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.linux, + architecture: ArchitectureImpl.x64, + file: Uri.file('libbar.so'), + ), + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.linux, + architecture: ArchitectureImpl.arm64, + file: Uri.file('libbar.so'), + ), + ], + ), + ); + final Uri? nativeAssetsYaml = await dryRunNativeAssetsLinux( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for linux.', + 'Dry running native assets for linux done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/linux/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + contains('package:bar/bar.dart'), + ); + expect(buildRunner.buildDryRunInvocations, 1); + expect(buildRunner.linkDryRunInvocations, 0); + }); + + testUsingContext('build with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsLinux( + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('build no assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsLinux( + targetPlatform: TargetPlatform.linux_x64, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/linux/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + isNot(contains('package:bar/bar.dart')), + ); + expect( + environment.projectDir + .childDirectory('build') + .childDirectory('native_assets') + .childDirectory('linux'), + exists, + ); + }); + + for (final bool flutterTester in [false, true]) { + String testName = ''; + if (flutterTester) { + testName += ' flutter tester'; + } + for (final BuildMode buildMode in [ + BuildMode.debug, + BuildMode.release, + ]) { + testUsingContext('build with assets $buildMode$testName', + overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir + .childDirectory('.dart_tool') + .childFile('package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final File dylibAfterCompiling = fileSystem.file('libbar.so'); + // The mock doesn't create the file, so create it here. + await dylibAfterCompiling.create(); + final FakeNativeAssetsBuildRunner buildRunner = + FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: FakeNativeAssetsBuilderResult( + assets: [ + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.linux, + architecture: ArchitectureImpl.x64, + file: dylibAfterCompiling.uri, + ), + ], + ), + ); + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsLinux( + targetPlatform: TargetPlatform.linux_x64, + projectUri: projectUri, + buildMode: buildMode, + fileSystem: fileSystem, + flutterTester: flutterTester, + buildRunner: buildRunner, + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Building native assets for linux_x64 $buildMode.', + 'Building native assets for linux_x64 done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/linux/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + stringContainsInOrder([ + 'package:bar/bar.dart', + if (flutterTester) + // Tests run on host system, so the have the full path on the system. + '- ${projectUri.resolve('build/native_assets/linux/libbar.so').toFilePath()}' + else + // Apps are a bundle with the dylibs on their dlopen path. + '- libbar.so', + ]), + ); + expect(buildRunner.buildInvocations, 1); + expect( + buildRunner.linkInvocations, + buildMode == BuildMode.release ? 1 : 0, + ); + }); + } + } + + testUsingContext('Native assets dry run error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsLinux( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building (dry run) native assets failed. See the logs for more details.', + ), + ); + }); + + testUsingContext('Native assets build error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsLinux( + targetPlatform: TargetPlatform.linux_x64, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building native assets failed. See the logs for more details.', + ), + ); + }); + // This logic is mocked in the other tests to avoid having test order // randomization causing issues with what processes are invoked. // Exercise the parsing of the process output in this separate test. @@ -110,14 +436,14 @@ void main() { packageConfigFile, logger: environment.logger, ); - final FlutterNativeAssetsBuildRunner runner = - FlutterNativeAssetsBuildRunnerImpl(projectUri, packageConfigFile.path, packageConfig, fileSystem, logger); + final NativeAssetsBuildRunner runner = + NativeAssetsBuildRunnerImpl(projectUri, packageConfigFile.path, packageConfig, fileSystem, logger); final CCompilerConfigImpl result = await runner.cCompilerConfig; expect(result.compiler, Uri.file('/some/path/to/clang')); }); } -class _BuildRunnerWithoutClang extends FakeFlutterNativeAssetsBuildRunner { +class _BuildRunnerWithoutClang extends FakeNativeAssetsBuildRunner { @override Future get cCompilerConfig async => throwToolExit('Failed to find clang++ on the PATH.'); diff --git a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart index 5a2ae7fad0..3d23fb8b66 100644 --- a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart @@ -13,8 +13,10 @@ import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/dart/package_map.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/isolated/native_assets/macos/native_assets.dart'; import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; -import 'package:native_assets_cli/native_assets_cli_internal.dart' hide Target; +import 'package:native_assets_cli/native_assets_cli_internal.dart' + hide Target; import 'package:native_assets_cli/native_assets_cli_internal.dart' as native_assets_cli; import 'package:package_config/package_config_types.dart'; @@ -49,9 +51,207 @@ void main() { projectUri = environment.projectDir.uri; }); - for (final bool flutterTester in [false, true]) { - final bool isArm64 = native_assets_cli.ArchitectureImpl.current == ArchitectureImpl.arm64; + testUsingContext('dry run with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + expect( + await dryRunNativeAssetsMacOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ), + null, + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + testUsingContext('build with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await buildNativeAssetsMacOS( + darwinArchs: [DarwinArch.arm64], + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run for multiple OSes with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await dryRunNativeAssetsMultipleOSes( + projectUri: projectUri, + fileSystem: fileSystem, + targetPlatforms: [ + TargetPlatform.darwin, + TargetPlatform.ios, + ], + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsMacOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('dry run with assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.macOS, + architecture: ArchitectureImpl.arm64, + file: Uri.file('libbar.dylib'), + ), + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.macOS, + architecture: ArchitectureImpl.x64, + file: Uri.file('libbar.dylib'), + ), + ], + ), + ); + final Uri? nativeAssetsYaml = await dryRunNativeAssetsMacOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for macos.', + 'Dry running native assets for macos done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/macos/native_assets.yaml'), + ); + final String nativeAssetsYamlContents = + await fileSystem.file(nativeAssetsYaml).readAsString(); + expect( + nativeAssetsYamlContents, + contains('package:bar/bar.dart'), + ); + expect(buildRunner.buildDryRunInvocations, 1); + expect(buildRunner.linkDryRunInvocations, 0); + // Check that the framework uri is identical for both archs. + final String pathSeparator = const LocalPlatform().pathSeparator; + expect( + nativeAssetsYamlContents, + stringContainsInOrder( + [ + 'bar.framework${pathSeparator}bar', + 'bar.framework${pathSeparator}bar', + ], + ), + ); + }); + + testUsingContext('build with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsMacOS( + darwinArchs: [DarwinArch.arm64], + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('build no assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsMacOS( + darwinArchs: [DarwinArch.arm64], + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/macos/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + isNot(contains('package:bar/bar.dart')), + ); + }); + + for (final bool flutterTester in [false, true]) { String testName = ''; if (flutterTester) { testName += ' flutter tester'; @@ -76,7 +276,7 @@ void main() { } for (final BuildMode buildMode in [ BuildMode.debug, - if (!flutterTester) BuildMode.release, + BuildMode.release, ]) { testUsingContext('build with assets $buildMode$testName', overrides: { FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), @@ -89,7 +289,8 @@ void main() { '-create', '-output', dylibPathBar, - '${isArm64 ? 'arm64' : 'x64'}/libbar.dylib', + 'arm64/libbar.dylib', + 'x64/libbar.dylib', ], ), FakeCommand( @@ -111,7 +312,8 @@ void main() { '-create', '-output', dylibPathBuz, - '${isArm64 ? 'arm64' : 'x64'}/libbuz.dylib', + 'arm64/libbuz.dylib', + 'x64/libbuz.dylib', ], ), FakeCommand( @@ -121,7 +323,9 @@ void main() { dylibPathBuz, ], stdout: [ - '$dylibPathBuz (architecture ${isArm64 ? 'arm64' : 'x86_64'}):', + '$dylibPathBuz (architecture x86_64):', + '@rpath/libbuz.dylib', + '$dylibPathBuz (architecture arm64):', '@rpath/libbuz.dylib', ].join('\n'), ), @@ -145,7 +349,8 @@ void main() { '--force', '--sign', '-', - if (buildMode == BuildMode.debug) '--timestamp=none', + if (buildMode == BuildMode.debug) + '--timestamp=none', signPathBar, ], ), @@ -156,7 +361,7 @@ void main() { dylibPathBuz, '-change', '@rpath/libbar.dylib', - dylibPathBar, + dylibPathBar, '-change', '@rpath/libbuz.dylib', signPathBuz, @@ -169,7 +374,8 @@ void main() { '--force', '--sign', '-', - if (buildMode == BuildMode.debug) '--timestamp=none', + if (buildMode == BuildMode.debug) + '--timestamp=none', signPathBuz, ], ), @@ -255,27 +461,15 @@ void main() { if (const LocalPlatform().isWindows) { return; // Backslashes in commands, but we will never run these commands on Windows. } - if (flutterTester && !const LocalPlatform().isMacOS) { - // The [runFlutterSpecificDartBuild] will - when given - // `TargetPlatform.tester` - enable `flutter test` mode. That means if - // this test is run on linux, it's going to do a linux build. - // Though this test is mac-specific, so we skip that. - // - // Running the test in `!flutterTester` mode still works on linux as - // we explicitly tell it to do a mac build (instead of letting it - // choose the local build). - return; - } final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.parent.create(); await packageConfig.create(); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', projectUri), ], onBuild: (native_assets_cli.Target target) => - FakeFlutterNativeAssetsBuilderResult( + FakeNativeAssetsBuilderResult( assets: [ NativeCodeAssetImpl( id: 'package:bar/bar.dart', @@ -292,34 +486,26 @@ void main() { file: Uri.file('${target.architecture}/libbuz.dylib'), ), ], - ), + ), ); - final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: buildMode.cliName, - kDarwinArchs: 'arm64 x86_64', - }, - targetPlatform: flutterTester ? TargetPlatform.tester : TargetPlatform.darwin, + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsMacOS( + darwinArchs: [DarwinArch.arm64, DarwinArch.x86_64], projectUri: projectUri, - nativeAssetsYamlUri: flutterTester ? null : nonFlutterTesterAssetUri, + buildMode: buildMode, fileSystem: fileSystem, + flutterTester: flutterTester, buildRunner: buildRunner, ); - final String expectedArchsBeingBuilt = flutterTester - ? (isArm64 ? 'macos_arm64' : 'macos_x64') - : '[macos_arm64, macos_x64]'; expect( (globals.logger as BufferLogger).traceText, stringContainsInOrder([ - 'Building native assets for $expectedArchsBeingBuilt $buildMode.', - 'Building native assets for $expectedArchsBeingBuilt $buildMode done.', + 'Building native assets for [macos_arm64, macos_x64] $buildMode.', + 'Building native assets for [macos_arm64, macos_x64] done.', ]), ); expect( nativeAssetsYaml, - flutterTester - ? projectUri.resolve('build/native_assets/macos/native_assets.yaml') - : nonFlutterTesterAssetUri + projectUri.resolve('build/native_assets/macos/native_assets.yaml'), ); expect( await fileSystem.file(nativeAssetsYaml).readAsString(), @@ -327,10 +513,10 @@ void main() { 'package:bar/bar.dart', if (flutterTester) // Tests run on host system, so the have the full path on the system. - projectUri.resolve('build/native_assets/macos/libbar.dylib').toFilePath() + '- ${projectUri.resolve('build/native_assets/macos/libbar.dylib').toFilePath()}' else // Apps are a bundle with the dylibs on their dlopen path. - 'bar.framework/bar', + '- bar.framework/bar', ]), ); expect( @@ -339,14 +525,14 @@ void main() { 'package:buz/buz.dart', if (flutterTester) // Tests run on host system, so the have the full path on the system. - projectUri.resolve('build/native_assets/macos/libbuz.dylib').toFilePath() + '- ${projectUri.resolve('build/native_assets/macos/libbuz.dylib').toFilePath()}' else // Apps are a bundle with the dylibs on their dlopen path. - 'buz.framework/buz', + '- buz.framework/buz', ]), ); // Multi arch. - expect(buildRunner.buildInvocations, flutterTester ? 1 : 2); + expect(buildRunner.buildInvocations, 2); expect( buildRunner.linkInvocations, buildMode == BuildMode.release ? 2 : 0, @@ -355,24 +541,82 @@ void main() { } } + testUsingContext('Native assets dry run error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsMacOS( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building (dry run) native assets failed. See the logs for more details.', + ), + ); + }); + + testUsingContext('Native assets build error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsMacOS( + darwinArchs: [DarwinArch.arm64], + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building native assets failed. See the logs for more details.', + ), + ); + }); + // This logic is mocked in the other tests to avoid having test order // randomization causing issues with what processes are invoked. // Exercise the parsing of the process output in this separate test. - testUsingContext('NativeAssetsBuildRunnerImpl.cCompilerConfig', - overrides: { - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), - ProcessManager: () => FakeProcessManager.list( - [ - const FakeCommand( - command: ['xcrun', 'clang', '--version'], - stdout: ''' + testUsingContext('NativeAssetsBuildRunnerImpl.cCompilerConfig', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.list( + [ + const FakeCommand( + command: ['xcrun', 'clang', '--version'], + stdout: ''' Apple clang version 14.0.0 (clang-1400.0.29.202) Target: arm64-apple-darwin22.6.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin''', - ) - ], - ), + ) + ], + ), }, () async { if (!const LocalPlatform().isMacOS) { return; @@ -388,7 +632,7 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault packageConfigFile, logger: environment.logger, ); - final FlutterNativeAssetsBuildRunner runner = FlutterNativeAssetsBuildRunnerImpl( + final NativeAssetsBuildRunner runner = NativeAssetsBuildRunnerImpl( projectUri, packageConfigFile.path, packageConfig, diff --git a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart deleted file mode 100644 index 5710e1da10..0000000000 --- a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/build_info.dart'; -import 'package:flutter_tools/src/build_system/build_system.dart'; -import 'package:flutter_tools/src/features.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; -import 'package:native_assets_cli/native_assets_cli_internal.dart'; -import 'package:package_config/package_config_types.dart'; - -import '../../src/common.dart'; -import '../../src/context.dart'; -import '../../src/fakes.dart'; -import 'fake_native_assets_build_runner.dart'; - -void main() { - late FakeProcessManager processManager; - late Environment environment; - late Artifacts artifacts; - late FileSystem fileSystem; - late BufferLogger logger; - late Uri projectUri; - - setUp(() { - processManager = FakeProcessManager.empty(); - logger = BufferLogger.test(); - artifacts = Artifacts.test(); - fileSystem = MemoryFileSystem.test(); - environment = Environment.test( - fileSystem.currentDirectory, - inputs: {}, - artifacts: artifacts, - processManager: processManager, - fileSystem: fileSystem, - logger: logger, - ); - environment.buildDir.createSync(recursive: true); - projectUri = environment.projectDir.uri; - }); - - testUsingContext('dry run with no package config', overrides: { - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - expect( - await runFlutterSpecificDartDryRunOnPlatforms( - projectUri: projectUri, - fileSystem: fileSystem, - targetPlatforms: [TargetPlatform.windows_x64], - buildRunner: FakeFlutterNativeAssetsBuildRunner( - hasPackageConfigResult: false, - ), - ), - null, - ); - expect( - (globals.logger as BufferLogger).traceText, - contains('No package config found. Skipping native assets compilation.'), - ); - }); - - testUsingContext('build with no package config', overrides: { - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; - await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - }, - targetPlatform: TargetPlatform.windows_x64, - projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, - fileSystem: fileSystem, - buildRunner: FakeFlutterNativeAssetsBuildRunner( - hasPackageConfigResult: false, - ), - ); - expect( - (globals.logger as BufferLogger).traceText, - contains('No package config found. Skipping native assets compilation.'), - ); - }); - - testUsingContext('dry run for multiple OSes with no package config', overrides: { - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - await runFlutterSpecificDartDryRunOnPlatforms( - projectUri: projectUri, - fileSystem: fileSystem, - targetPlatforms: [ - TargetPlatform.windows_x64, - TargetPlatform.darwin, - TargetPlatform.ios, - ], - buildRunner: FakeFlutterNativeAssetsBuildRunner( - hasPackageConfigResult: false, - ), - ); - expect( - (globals.logger as BufferLogger).traceText, - contains('No package config found. Skipping native assets compilation.'), - ); - }); - - testUsingContext('dry run with assets but not enabled', overrides: { - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - await packageConfig.parent.create(); - await packageConfig.create(); - expect( - () => runFlutterSpecificDartDryRunOnPlatforms( - projectUri: projectUri, - fileSystem: fileSystem, - targetPlatforms: [TargetPlatform.windows_x64], - buildRunner: FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('bar', projectUri), - ], - ), - ), - throwsToolExit( - message: 'Package(s) bar require the native assets feature to be enabled. ' - 'Enable using `flutter config --enable-native-assets`.', - ), - ); - }); - - testUsingContext('dry run with assets', overrides: { - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - await packageConfig.parent.create(); - await packageConfig.create(); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('bar', projectUri), - ], - buildDryRunResult: FakeFlutterNativeAssetsBuilderResult( - assets: [ - NativeCodeAssetImpl( - id: 'package:bar/bar.dart', - linkMode: DynamicLoadingBundledImpl(), - os: OSImpl.windows, - architecture: ArchitectureImpl.x64, - file: Uri.file('bar.dll'), - ), - ], - ), - ); - final Uri? nativeAssetsYaml = await runFlutterSpecificDartDryRunOnPlatforms( - projectUri: projectUri, - fileSystem: fileSystem, - targetPlatforms: [TargetPlatform.windows_x64], - buildRunner: buildRunner, - ); - expect( - (globals.logger as BufferLogger).traceText, - stringContainsInOrder([ - 'Dry running native assets for windows.', - 'Dry running native assets for windows done.', - ]), - ); - expect( - nativeAssetsYaml, - projectUri.resolve('build/native_assets/windows/native_assets.yaml'), - ); - expect( - await fileSystem.file(nativeAssetsYaml).readAsString(), - contains('package:bar/bar.dart'), - ); - expect(buildRunner.buildDryRunInvocations, 1); - }); - - testUsingContext('build with assets but not enabled', overrides: { - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; - await packageConfig.parent.create(); - await packageConfig.create(); - expect( - () => runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - }, - targetPlatform: TargetPlatform.windows_x64, - projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, - fileSystem: fileSystem, - buildRunner: FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('bar', projectUri), - ], - ), - ), - throwsToolExit( - message: 'Package(s) bar require the native assets feature to be enabled. ' - 'Enable using `flutter config --enable-native-assets`.', - ), - ); - }); - - testUsingContext('build no assets', overrides: { - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; - await packageConfig.parent.create(); - await packageConfig.create(); - final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - }, - targetPlatform: TargetPlatform.windows_x64, - projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, - fileSystem: fileSystem, - buildRunner: FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('bar', projectUri), - ], - ), - ); - expect(nativeAssetsYaml, nonFlutterTesterAssetUri); - expect( - await fileSystem.file(nativeAssetsYaml).readAsString(), - isNot(contains('package:bar/bar.dart')), - ); - expect( - environment.projectDir.childDirectory('build').childDirectory('native_assets').childDirectory('windows'), - exists, - ); - }); - - testUsingContext('Native assets dry run error', overrides: { - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final File packageConfig = - environment.projectDir.childFile('.dart_tool/package_config.json'); - await packageConfig.parent.create(); - await packageConfig.create(); - expect( - () => runFlutterSpecificDartDryRunOnPlatforms( - projectUri: projectUri, - fileSystem: fileSystem, - targetPlatforms: [TargetPlatform.windows_x64], - buildRunner: FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('bar', projectUri), - ], - buildDryRunResult: const FakeFlutterNativeAssetsBuilderResult( - success: false, - ), - ), - ), - throwsToolExit( - message: - 'Building (dry run) native assets failed. See the logs for more details.', - ), - ); - }); - - testUsingContext('Native assets build error', overrides: { - FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), - ProcessManager: () => FakeProcessManager.empty(), - }, () async { - final File packageConfig = - environment.projectDir.childFile('.dart_tool/package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; - await packageConfig.parent.create(); - await packageConfig.create(); - expect( - () => runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: BuildMode.debug.cliName, - }, - targetPlatform: TargetPlatform.linux_x64, - projectUri: projectUri, - nativeAssetsYamlUri: nonFlutterTesterAssetUri, - fileSystem: fileSystem, - buildRunner: FakeFlutterNativeAssetsBuildRunner( - packagesWithNativeAssetsResult: [ - Package('bar', projectUri), - ], - buildResult: const FakeFlutterNativeAssetsBuilderResult( - success: false, - ), - ), - ), - throwsToolExit( - message: - 'Building native assets failed. See the logs for more details.', - ), - ); - }); - -} diff --git a/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart index abf01d853a..362af17509 100644 --- a/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart @@ -56,7 +56,7 @@ void main() { globals.fs .file(globals.fs.path.join('lib', 'main.dart')) .createSync(recursive: true); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner(); + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner(); final HotRunner residentRunner = HotRunner( [ flutterDevice, diff --git a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart index 2eb7bf9b76..45c35d8a14 100644 --- a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart @@ -4,6 +4,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -14,8 +15,9 @@ import 'package:flutter_tools/src/dart/package_map.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; -import 'package:native_assets_cli/native_assets_cli_internal.dart'; -import 'package:native_assets_cli/native_assets_cli_internal.dart' as native_assets_cli; +import 'package:flutter_tools/src/isolated/native_assets/windows/native_assets.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart' + hide Target; import 'package:package_config/package_config_types.dart'; import '../../../src/common.dart'; @@ -48,6 +50,187 @@ void main() { projectUri = environment.projectDir.uri; }); + testUsingContext('dry run with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + expect( + await dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ), + null, + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('build with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await buildNativeAssetsWindows( + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run for multiple OSes with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await dryRunNativeAssetsMultipleOSes( + projectUri: projectUri, + fileSystem: fileSystem, + targetPlatforms: [ + TargetPlatform.windows_x64, + ], + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('dry run with assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + NativeCodeAssetImpl( + id: 'package:bar/bar.dart', + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.windows, + architecture: ArchitectureImpl.x64, + file: Uri.file('bar.dll'), + ), + ], + ), + ); + final Uri? nativeAssetsYaml = await dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for windows.', + 'Dry running native assets for windows done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/windows/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + contains('package:bar/bar.dart'), + ); + expect(buildRunner.buildDryRunInvocations, 1); + expect(buildRunner.linkDryRunInvocations, 0); + }); + + testUsingContext('build with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsWindows( + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('build no assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsWindows( + targetPlatform: TargetPlatform.windows_x64, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/windows/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + isNot(contains('package:bar/bar.dart')), + ); + expect( + environment.projectDir.childDirectory('build').childDirectory('native_assets').childDirectory('windows'), + exists, + ); + }); + for (final bool flutterTester in [false, true]) { String testName = ''; if (flutterTester) { @@ -55,33 +238,23 @@ void main() { } for (final BuildMode buildMode in [ BuildMode.debug, - if (!flutterTester) BuildMode.release, + BuildMode.release, ]) { - if (flutterTester && !const LocalPlatform().isWindows) { - // When calling [runFlutterSpecificDartBuild] with the flutter tester - // target platform, it will perform a build for the local machine. That - // means e.g. running this test on MacOS will cause it to run a MacOS - // build - which in return requires a special [ProcessManager] that can - // simulate output of `otool` invocations. - continue; - } - testUsingContext('build with assets $buildMode$testName', overrides: { FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), ProcessManager: () => FakeProcessManager.empty(), }, () async { final File packageConfig = environment.projectDir.childDirectory('.dart_tool').childFile('package_config.json'); - final Uri nonFlutterTesterAssetUri = environment.buildDir.childFile('native_assets.yaml').uri; await packageConfig.parent.create(); await packageConfig.create(); final File dylibAfterCompiling = fileSystem.file('bar.dll'); // The mock doesn't create the file, so create it here. await dylibAfterCompiling.create(); - final FakeFlutterNativeAssetsBuildRunner buildRunner = FakeFlutterNativeAssetsBuildRunner( + final FakeNativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner( packagesWithNativeAssetsResult: [ Package('bar', projectUri), ], - buildResult: FakeFlutterNativeAssetsBuilderResult( + buildResult: FakeNativeAssetsBuilderResult( assets: [ NativeCodeAssetImpl( id: 'package:bar/bar.dart', @@ -93,35 +266,24 @@ void main() { ], ), ); - final (_, Uri nativeAssetsYaml) = await runFlutterSpecificDartBuild( - environmentDefines: { - kBuildMode: buildMode.cliName, - }, - targetPlatform: flutterTester - ? TargetPlatform.tester - : TargetPlatform.windows_x64, + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsWindows( + targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, - nativeAssetsYamlUri: flutterTester ? null : nonFlutterTesterAssetUri, + buildMode: buildMode, fileSystem: fileSystem, + flutterTester: flutterTester, buildRunner: buildRunner, ); - final String expectedOS = flutterTester - ? native_assets_cli.Target.current.toString() - : 'windows_x64'; expect( (globals.logger as BufferLogger).traceText, stringContainsInOrder([ - 'Building native assets for $expectedOS $buildMode.', - 'Building native assets for $expectedOS $buildMode done.', + 'Building native assets for windows_x64 $buildMode.', + 'Building native assets for windows_x64 done.', ]), ); - final String expectedDirectory = flutterTester - ? native_assets_cli.OSImpl.current.toString() - : 'windows'; - expect(nativeAssetsYaml, - flutterTester - ? projectUri.resolve('build/native_assets/$expectedDirectory/native_assets.yaml') - : nonFlutterTesterAssetUri + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/windows/native_assets.yaml'), ); expect( await fileSystem.file(nativeAssetsYaml).readAsString(), @@ -129,21 +291,80 @@ void main() { 'package:bar/bar.dart', if (flutterTester) // Tests run on host system, so the have the full path on the system. - projectUri.resolve('build/native_assets/$expectedDirectory/bar.dll').toFilePath() + '- ${projectUri.resolve('build/native_assets/windows/bar.dll').toFilePath()}' else // Apps are a bundle with the dylibs on their dlopen path. - 'bar.dll', + '- bar.dll', ]), ); expect(buildRunner.buildInvocations, 1); expect( buildRunner.linkInvocations, - buildMode == BuildMode.release ? 1 : 0, + buildMode == BuildMode.release ? 1 :0, ); }); } } + testUsingContext('Native assets dry run error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildDryRunResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building (dry run) native assets failed. See the logs for more details.', + ), + ); + }); + + testUsingContext('Native assets build error', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = + environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsWindows( + targetPlatform: TargetPlatform.windows_x64, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + yamlParentDirectory: environment.buildDir.uri, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: const FakeNativeAssetsBuilderResult( + success: false, + ), + ), + ), + throwsToolExit( + message: + 'Building native assets failed. See the logs for more details.', + ), + ); + }); + // This logic is mocked in the other tests to avoid having test order // randomization causing issues with what processes are invoked. // Exercise the parsing of the process output in this separate test. @@ -232,14 +453,17 @@ void main() { fileSystem.directory(r'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64'); await msvcBinDir.create(recursive: true); - final File packageConfigFile = fileSystem.directory(projectUri).childDirectory('.dart_tool').childFile('package_config.json'); + final File packageConfigFile = fileSystem + .directory(projectUri) + .childDirectory('.dart_tool') + .childFile('package_config.json'); await packageConfigFile.parent.create(); await packageConfigFile.create(); final PackageConfig packageConfig = await loadPackageConfigWithLogging( packageConfigFile, logger: environment.logger, ); - final FlutterNativeAssetsBuildRunner runner = FlutterNativeAssetsBuildRunnerImpl( + final NativeAssetsBuildRunner runner = NativeAssetsBuildRunnerImpl( projectUri, packageConfigFile.path, packageConfig,