// 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:native_assets_builder/native_assets_builder.dart' show BuildResult; import 'package:native_assets_cli/native_assets_cli.dart' hide BuildMode; import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli; 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, }) async { if (await hasNoPackageConfig(buildRunner) || await isDisabledAndNoNativeAssets(buildRunner)) { return null; } final Uri buildUri_ = nativeAssetsBuildUri(projectUri, OS.linux); final Iterable nativeAssetPaths = await dryRunNativeAssetsLinuxInternal( fileSystem, projectUri, flutterTester, buildRunner, ); final Uri nativeAssetsUri = await writeNativeAssetsYaml( nativeAssetPaths, buildUri_, fileSystem, ); return nativeAssetsUri; } Future> dryRunNativeAssetsLinuxInternal( FileSystem fileSystem, Uri projectUri, bool flutterTester, NativeAssetsBuildRunner buildRunner, ) async { const OS targetOs = OS.linux; final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs); globals.logger.printTrace('Dry running native assets for $targetOs.'); final List nativeAssets = (await buildRunner.dryRun( linkModePreference: LinkModePreference.dynamic, targetOs: targetOs, workingDirectory: projectUri, includeParentEnvironment: true, )) .assets; ensureNoLinkModeStatic(nativeAssets); globals.logger.printTrace('Dry running native assets for $targetOs done.'); final Uri? absolutePath = flutterTester ? buildUri_ : null; final Map assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath); final Iterable nativeAssetPaths = assetTargetLocations.values; return nativeAssetPaths; } /// 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)> buildNativeAssetsLinux({ required NativeAssetsBuildRunner buildRunner, TargetPlatform? targetPlatform, required Uri projectUri, required BuildMode buildMode, bool flutterTester = false, Uri? yamlParentDirectory, required FileSystem fileSystem, }) async { const OS targetOs = OS.linux; 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 hasNoPackageConfig(buildRunner) || await isDisabledAndNoNativeAssets(buildRunner)) { final Uri nativeAssetsYaml = await writeNativeAssetsYaml([], yamlParentDirectory ?? buildUri_, fileSystem); return (nativeAssetsYaml, []); } final Target target = targetPlatform != null ? _getNativeTarget(targetPlatform) : Target.current; final native_assets_cli.BuildMode buildModeCli = nativeAssetsBuildMode(buildMode); globals.logger.printTrace('Building native assets for $target $buildModeCli.'); final BuildResult result = await buildRunner.build( linkModePreference: LinkModePreference.dynamic, target: target, buildMode: buildModeCli, workingDirectory: projectUri, includeParentEnvironment: true, cCompilerConfig: await buildRunner.cCompilerConfig, ); final List nativeAssets = result.assets; final Set dependencies = result.dependencies.toSet(); ensureNoLinkModeStatic(nativeAssets); globals.logger.printTrace('Building native assets for $target done.'); final Uri? absolutePath = flutterTester ? buildUri_ : null; final Map assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath); await _copyNativeAssetsLinux( buildUri_, assetTargetLocations, buildMode, fileSystem, ); final Uri nativeAssetsUri = await writeNativeAssetsYaml( assetTargetLocations.values, yamlParentDirectory ?? buildUri_, fileSystem, ); return (nativeAssetsUri, dependencies.toList()); } Map _assetTargetLocations( List nativeAssets, Uri? absolutePath, ) => { for (final Asset asset in nativeAssets) asset: _targetLocationLinux(asset, absolutePath), }; Asset _targetLocationLinux(Asset asset, Uri? absolutePath) { final AssetPath path = asset.path; switch (path) { case AssetSystemPath _: case AssetInExecutable _: case AssetInProcess _: return asset; case AssetAbsolutePath _: final String fileName = path.uri.pathSegments.last; Uri uri; if (absolutePath != null) { // Flutter tester needs full host paths. uri = absolutePath.resolve(fileName); } else { // Flutter Desktop needs "absolute" paths inside the app. // "relative" in the context of native assets would be relative to the // kernel or aot snapshot. uri = Uri(path: fileName); } return asset.copyWith(path: AssetAbsolutePath(uri)); } throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset'); } /// Extract the [Target] from a [TargetPlatform]. Target _getNativeTarget(TargetPlatform targetPlatform) { switch (targetPlatform) { case TargetPlatform.linux_x64: return Target.linuxX64; case TargetPlatform.linux_arm64: return Target.linuxArm64; case TargetPlatform.android: case TargetPlatform.ios: case TargetPlatform.darwin: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia_arm64: case TargetPlatform.fuchsia_x64: case TargetPlatform.tester: case TargetPlatform.web_javascript: case TargetPlatform.android_arm: case TargetPlatform.android_arm64: case TargetPlatform.android_x64: case TargetPlatform.android_x86: throw Exception('Unknown targetPlatform: $targetPlatform.'); } } Future _copyNativeAssetsLinux( Uri buildUri, Map assetTargetLocations, BuildMode buildMode, FileSystem fileSystem, ) async { 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) { final Uri source = (assetMapping.key.path as AssetAbsolutePath).uri; final Uri target = (assetMapping.value.path as AssetAbsolutePath).uri; final Uri targetUri = buildUri.resolveUri(target); final String targetFullPath = targetUri.toFilePath(); await fileSystem.file(source).copy(targetFullPath); } globals.logger.printTrace('Copying native assets done.'); } } /// Flutter expects `clang++` to be on the path on Linux hosts. /// /// Search for the accompanying `clang`, `ar`, and `ld`. Future cCompilerConfigLinux() async { const String kClangPlusPlusBinary = 'clang++'; const String kClangBinary = 'clang'; const String kArBinary = 'llvm-ar'; const String kLdBinary = 'ld.lld'; final ProcessResult whichResult = await globals.processManager.run(['which', kClangPlusPlusBinary]); if (whichResult.exitCode != 0) { throwToolExit('Failed to find $kClangPlusPlusBinary on PATH.'); } File clangPpFile = globals.fs.file((whichResult.stdout as String).trim()); clangPpFile = globals.fs.file(await clangPpFile.resolveSymbolicLinks()); final Directory clangDir = clangPpFile.parent; final Map binaryPaths = {}; for (final String binary in [kClangBinary, kArBinary, kLdBinary]) { final File binaryFile = clangDir.childFile(binary); if (!await binaryFile.exists()) { throwToolExit("Failed to find $binary relative to $clangPpFile: $binaryFile doesn't exist."); } binaryPaths[binary] = binaryFile.uri; } return CCompilerConfig( ar: binaryPaths[kArBinary], cc: binaryPaths[kClangBinary], ld: binaryPaths[kLdBinary], ); }