/ app / rust_builder / cargokit / build_tool / lib / src / android_environment.dart
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  }