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,