android_environment.dart
1 /// This is copied from Cargokit (which is the official way to use it currently) 2 /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 4 import 'dart:io'; 5 import 'dart:isolate'; 6 import 'dart:math' as math; 7 8 import 'package:collection/collection.dart'; 9 import 'package:path/path.dart' as path; 10 import 'package:version/version.dart'; 11 12 import 'target.dart'; 13 import 'util.dart'; 14 15 class AndroidEnvironment { 16 AndroidEnvironment({ 17 required this.sdkPath, 18 required this.ndkVersion, 19 required this.minSdkVersion, 20 required this.targetTempDir, 21 required this.target, 22 }); 23 24 static void clangLinkerWrapper(List<String> args) { 25 final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; 26 if (clang == null) { 27 throw Exception( 28 "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); 29 } 30 final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; 31 if (target == null) { 32 throw Exception( 33 "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); 34 } 35 36 runCommand(clang, [ 37 target, 38 ...args, 39 ]); 40 } 41 42 /// Full path to Android SDK. 43 final String sdkPath; 44 45 /// Full version of Android NDK. 46 final String ndkVersion; 47 48 /// Minimum supported SDK version. 49 final int minSdkVersion; 50 51 /// Target directory for build artifacts. 52 final String targetTempDir; 53 54 /// Target being built. 55 final Target target; 56 57 bool ndkIsInstalled() { 58 final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); 59 final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); 60 return ndkPackageXml.existsSync(); 61 } 62 63 void installNdk({ 64 required String javaHome, 65 }) { 66 final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; 67 final sdkManager = path.join( 68 sdkPath, 69 'cmdline-tools', 70 'latest', 71 'bin', 72 'sdkmanager$sdkManagerExtension', 73 ); 74 75 log.info('Installing NDK $ndkVersion'); 76 runCommand(sdkManager, [ 77 '--install', 78 'ndk;$ndkVersion', 79 ], environment: { 80 'JAVA_HOME': javaHome, 81 }); 82 } 83 84 Future<Map<String, String>> buildEnvironment() async { 85 final hostArch = Platform.isMacOS 86 ? "darwin-x86_64" 87 : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); 88 89 final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); 90 final toolchainPath = path.join( 91 ndkPath, 92 'toolchains', 93 'llvm', 94 'prebuilt', 95 hostArch, 96 'bin', 97 ); 98 99 final minSdkVersion = 100 math.max(target.androidMinSdkVersion!, this.minSdkVersion); 101 102 final exe = Platform.isWindows ? '.exe' : ''; 103 104 final arKey = 'AR_${target.rust}'; 105 final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] 106 .map((e) => path.join(toolchainPath, e)) 107 .firstWhereOrNull((element) => File(element).existsSync()); 108 if (arValue == null) { 109 throw Exception('Failed to find ar for $target in $toolchainPath'); 110 } 111 112 final targetArg = '--target=${target.rust}$minSdkVersion'; 113 114 final ccKey = 'CC_${target.rust}'; 115 final ccValue = path.join(toolchainPath, 'clang$exe'); 116 final cfFlagsKey = 'CFLAGS_${target.rust}'; 117 final cFlagsValue = targetArg; 118 119 final cxxKey = 'CXX_${target.rust}'; 120 final cxxValue = path.join(toolchainPath, 'clang++$exe'); 121 final cxxFlagsKey = 'CXXFLAGS_${target.rust}'; 122 final cxxFlagsValue = targetArg; 123 124 final linkerKey = 125 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); 126 127 final ranlibKey = 'RANLIB_${target.rust}'; 128 final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); 129 130 final ndkVersionParsed = Version.parse(ndkVersion); 131 final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; 132 final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); 133 134 final runRustTool = 135 Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; 136 137 final packagePath = (await Isolate.resolvePackageUri( 138 Uri.parse('package:build_tool/buildtool.dart')))! 139 .toFilePath(); 140 final selfPath = path.canonicalize(path.join( 141 packagePath, 142 '..', 143 '..', 144 '..', 145 runRustTool, 146 )); 147 148 // Make sure that run_build_tool is working properly even initially launched directly 149 // through dart run. 150 final toolTempDir = 151 Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; 152 153 return { 154 arKey: arValue, 155 ccKey: ccValue, 156 cfFlagsKey: cFlagsValue, 157 cxxKey: cxxValue, 158 cxxFlagsKey: cxxFlagsValue, 159 ranlibKey: ranlibValue, 160 rustFlagsKey: rustFlagsValue, 161 linkerKey: selfPath, 162 // Recognized by main() so we know when we're acting as a wrapper 163 '_CARGOKIT_NDK_LINK_TARGET': targetArg, 164 '_CARGOKIT_NDK_LINK_CLANG': ccValue, 165 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, 166 }; 167 } 168 169 // Workaround for libgcc missing in NDK23, inspired by cargo-ndk 170 String _libGccWorkaround(String buildDir, Version ndkVersion) { 171 final workaroundDir = path.join( 172 buildDir, 173 'cargokit', 174 'libgcc_workaround', 175 '${ndkVersion.major}', 176 ); 177 Directory(workaroundDir).createSync(recursive: true); 178 if (ndkVersion.major >= 23) { 179 File(path.join(workaroundDir, 'libgcc.a')) 180 .writeAsStringSync('INPUT(-lunwind)'); 181 } else { 182 // Other way around, untested, forward libgcc.a from libunwind once Rust 183 // gets updated for NDK23+. 184 File(path.join(workaroundDir, 'libunwind.a')) 185 .writeAsStringSync('INPUT(-lgcc)'); 186 } 187 188 var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; 189 if (rustFlags.isNotEmpty) { 190 rustFlags = '$rustFlags\x1f'; 191 } 192 rustFlags = '$rustFlags-L\x1f$workaroundDir'; 193 return rustFlags; 194 } 195 }