/// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'dart:isolate'; import 'dart:math' as math; import 'package:collection/collection.dart'; import 'package:path/path.dart' as path; import 'package:version/version.dart'; import 'target.dart'; import 'util.dart'; class AndroidEnvironment { AndroidEnvironment({ required this.sdkPath, required this.ndkVersion, required this.minSdkVersion, required this.targetTempDir, required this.target, }); static void clangLinkerWrapper(List args) { final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; if (clang == null) { throw Exception( "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); } final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; if (target == null) { throw Exception( "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); } runCommand(clang, [ target, ...args, ]); } /// Full path to Android SDK. final String sdkPath; /// Full version of Android NDK. final String ndkVersion; /// Minimum supported SDK version. final int minSdkVersion; /// Target directory for build artifacts. final String targetTempDir; /// Target being built. final Target target; bool ndkIsInstalled() { final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); return ndkPackageXml.existsSync(); } void installNdk({ required String javaHome, }) { final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; final sdkManager = path.join( sdkPath, 'cmdline-tools', 'latest', 'bin', 'sdkmanager$sdkManagerExtension', ); log.info('Installing NDK $ndkVersion'); runCommand(sdkManager, [ '--install', 'ndk;$ndkVersion', ], environment: { 'JAVA_HOME': javaHome, }); } Future> buildEnvironment() async { final hostArch = Platform.isMacOS ? "darwin-x86_64" : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); final toolchainPath = path.join( ndkPath, 'toolchains', 'llvm', 'prebuilt', hostArch, 'bin', ); final minSdkVersion = math.max(target.androidMinSdkVersion!, this.minSdkVersion); final exe = Platform.isWindows ? '.exe' : ''; final arKey = 'AR_${target.rust}'; final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] .map((e) => path.join(toolchainPath, e)) .firstWhereOrNull((element) => File(element).existsSync()); if (arValue == null) { throw Exception('Failed to find ar for $target in $toolchainPath'); } final targetArg = '--target=${target.rust}$minSdkVersion'; final ccKey = 'CC_${target.rust}'; final ccValue = path.join(toolchainPath, 'clang$exe'); final cfFlagsKey = 'CFLAGS_${target.rust}'; final cFlagsValue = targetArg; final cxxKey = 'CXX_${target.rust}'; final cxxValue = path.join(toolchainPath, 'clang++$exe'); final cxxfFlagsKey = 'CXXFLAGS_${target.rust}'; final cxxFlagsValue = targetArg; final linkerKey = 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); final ranlibKey = 'RANLIB_${target.rust}'; final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); final ndkVersionParsed = Version.parse(ndkVersion); final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); final runRustTool = Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; final packagePath = (await Isolate.resolvePackageUri( Uri.parse('package:build_tool/buildtool.dart')))! .toFilePath(); final selfPath = path.canonicalize(path.join( packagePath, '..', '..', '..', runRustTool, )); // Make sure that run_build_tool is working properly even initially launched directly // through dart run. final toolTempDir = Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; return { arKey: arValue, ccKey: ccValue, cfFlagsKey: cFlagsValue, cxxKey: cxxValue, cxxfFlagsKey: cxxFlagsValue, ranlibKey: ranlibValue, rustFlagsKey: rustFlagsValue, linkerKey: selfPath, // Recognized by main() so we know when we're acting as a wrapper '_CARGOKIT_NDK_LINK_TARGET': targetArg, '_CARGOKIT_NDK_LINK_CLANG': ccValue, 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, }; } // Workaround for libgcc missing in NDK23, inspired by cargo-ndk String _libGccWorkaround(String buildDir, Version ndkVersion) { final workaroundDir = path.join( buildDir, 'cargokit', 'libgcc_workaround', '${ndkVersion.major}', ); Directory(workaroundDir).createSync(recursive: true); if (ndkVersion.major >= 23) { File(path.join(workaroundDir, 'libgcc.a')) .writeAsStringSync('INPUT(-lunwind)'); } else { // Other way around, untested, forward libgcc.a from libunwind once Rust // gets updated for NDK23+. File(path.join(workaroundDir, 'libunwind.a')) .writeAsStringSync('INPUT(-lgcc)'); } var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; if (rustFlags.isNotEmpty) { rustFlags = '$rustFlags\x1f'; } rustFlags = '$rustFlags-L\x1f$workaroundDir'; return rustFlags; } }