build_tool.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 6 import 'package:args/command_runner.dart'; 7 import 'package:ed25519_edwards/ed25519_edwards.dart'; 8 import 'package:github/github.dart'; 9 import 'package:hex/hex.dart'; 10 import 'package:logging/logging.dart'; 11 12 import 'android_environment.dart'; 13 import 'build_cmake.dart'; 14 import 'build_gradle.dart'; 15 import 'build_pod.dart'; 16 import 'logging.dart'; 17 import 'options.dart'; 18 import 'precompile_binaries.dart'; 19 import 'target.dart'; 20 import 'util.dart'; 21 import 'verify_binaries.dart'; 22 23 final log = Logger('build_tool'); 24 25 abstract class BuildCommand extends Command { 26 Future<void> runBuildCommand(CargokitUserOptions options); 27 28 @override 29 Future<void> run() async { 30 final options = CargokitUserOptions.load(); 31 32 if (options.verboseLogging || 33 Platform.environment['CARGOKIT_VERBOSE'] == '1') { 34 enableVerboseLogging(); 35 } 36 37 await runBuildCommand(options); 38 } 39 } 40 41 class BuildPodCommand extends BuildCommand { 42 @override 43 final name = 'build-pod'; 44 45 @override 46 final description = 'Build cocoa pod library'; 47 48 @override 49 Future<void> runBuildCommand(CargokitUserOptions options) async { 50 final build = BuildPod(userOptions: options); 51 await build.build(); 52 } 53 } 54 55 class BuildGradleCommand extends BuildCommand { 56 @override 57 final name = 'build-gradle'; 58 59 @override 60 final description = 'Build android library'; 61 62 @override 63 Future<void> runBuildCommand(CargokitUserOptions options) async { 64 final build = BuildGradle(userOptions: options); 65 await build.build(); 66 } 67 } 68 69 class BuildCMakeCommand extends BuildCommand { 70 @override 71 final name = 'build-cmake'; 72 73 @override 74 final description = 'Build CMake library'; 75 76 @override 77 Future<void> runBuildCommand(CargokitUserOptions options) async { 78 final build = BuildCMake(userOptions: options); 79 await build.build(); 80 } 81 } 82 83 class GenKeyCommand extends Command { 84 @override 85 final name = 'gen-key'; 86 87 @override 88 final description = 'Generate key pair for signing precompiled binaries'; 89 90 @override 91 void run() { 92 final kp = generateKey(); 93 final private = HEX.encode(kp.privateKey.bytes); 94 final public = HEX.encode(kp.publicKey.bytes); 95 print("Private Key: $private"); 96 print("Public Key: $public"); 97 } 98 } 99 100 class PrecompileBinariesCommand extends Command { 101 PrecompileBinariesCommand() { 102 argParser 103 ..addOption( 104 'repository', 105 mandatory: true, 106 help: 'Github repository slug in format owner/name', 107 ) 108 ..addOption( 109 'manifest-dir', 110 mandatory: true, 111 help: 'Directory containing Cargo.toml', 112 ) 113 ..addMultiOption('target', 114 help: 'Rust target triple of artifact to build.\n' 115 'Can be specified multiple times or omitted in which case\n' 116 'all targets for current platform will be built.') 117 ..addOption( 118 'android-sdk-location', 119 help: 'Location of Android SDK (if available)', 120 ) 121 ..addOption( 122 'android-ndk-version', 123 help: 'Android NDK version (if available)', 124 ) 125 ..addOption( 126 'android-min-sdk-version', 127 help: 'Android minimum rquired version (if available)', 128 ) 129 ..addOption( 130 'temp-dir', 131 help: 'Directory to store temporary build artifacts', 132 ) 133 ..addFlag( 134 "verbose", 135 abbr: "v", 136 defaultsTo: false, 137 help: "Enable verbose logging", 138 ); 139 } 140 141 @override 142 final name = 'precompile-binaries'; 143 144 @override 145 final description = 'Prebuild and upload binaries\n' 146 'Private key must be passed through PRIVATE_KEY environment variable. ' 147 'Use gen_key through generate priave key.\n' 148 'Github token must be passed as GITHUB_TOKEN environment variable.\n'; 149 150 @override 151 Future<void> run() async { 152 final verbose = argResults!['verbose'] as bool; 153 if (verbose) { 154 enableVerboseLogging(); 155 } 156 157 final privateKeyString = Platform.environment['PRIVATE_KEY']; 158 if (privateKeyString == null) { 159 throw ArgumentError('Missing PRIVATE_KEY environment variable'); 160 } 161 final githubToken = Platform.environment['GITHUB_TOKEN']; 162 if (githubToken == null) { 163 throw ArgumentError('Missing GITHUB_TOKEN environment variable'); 164 } 165 final privateKey = HEX.decode(privateKeyString); 166 if (privateKey.length != 64) { 167 throw ArgumentError('Private key must be 64 bytes long'); 168 } 169 final manifestDir = argResults!['manifest-dir'] as String; 170 if (!Directory(manifestDir).existsSync()) { 171 throw ArgumentError('Manifest directory does not exist: $manifestDir'); 172 } 173 String? androidMinSdkVersionString = 174 argResults!['android-min-sdk-version'] as String?; 175 int? androidMinSdkVersion; 176 if (androidMinSdkVersionString != null) { 177 androidMinSdkVersion = int.tryParse(androidMinSdkVersionString); 178 if (androidMinSdkVersion == null) { 179 throw ArgumentError( 180 'Invalid android-min-sdk-version: $androidMinSdkVersionString'); 181 } 182 } 183 final targetStrigns = argResults!['target'] as List<String>; 184 final targets = targetStrigns.map((target) { 185 final res = Target.forRustTriple(target); 186 if (res == null) { 187 throw ArgumentError('Invalid target: $target'); 188 } 189 return res; 190 }).toList(growable: false); 191 final precompileBinaries = PrecompileBinaries( 192 privateKey: PrivateKey(privateKey), 193 githubToken: githubToken, 194 manifestDir: manifestDir, 195 repositorySlug: RepositorySlug.full(argResults!['repository'] as String), 196 targets: targets, 197 androidSdkLocation: argResults!['android-sdk-location'] as String?, 198 androidNdkVersion: argResults!['android-ndk-version'] as String?, 199 androidMinSdkVersion: androidMinSdkVersion, 200 tempDir: argResults!['temp-dir'] as String?, 201 ); 202 203 await precompileBinaries.run(); 204 } 205 } 206 207 class VerifyBinariesCommand extends Command { 208 VerifyBinariesCommand() { 209 argParser.addOption( 210 'manifest-dir', 211 mandatory: true, 212 help: 'Directory containing Cargo.toml', 213 ); 214 } 215 216 @override 217 final name = "verify-binaries"; 218 219 @override 220 final description = 'Verifies published binaries\n' 221 'Checks whether there is a binary published for each targets\n' 222 'and checks the signature.'; 223 224 @override 225 Future<void> run() async { 226 final manifestDir = argResults!['manifest-dir'] as String; 227 final verifyBinaries = VerifyBinaries( 228 manifestDir: manifestDir, 229 ); 230 await verifyBinaries.run(); 231 } 232 } 233 234 Future<void> runMain(List<String> args) async { 235 try { 236 // Init logging before options are loaded 237 initLogging(); 238 239 if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) { 240 return AndroidEnvironment.clangLinkerWrapper(args); 241 } 242 243 final runner = CommandRunner('build_tool', 'Cargokit built_tool') 244 ..addCommand(BuildPodCommand()) 245 ..addCommand(BuildGradleCommand()) 246 ..addCommand(BuildCMakeCommand()) 247 ..addCommand(GenKeyCommand()) 248 ..addCommand(PrecompileBinariesCommand()) 249 ..addCommand(VerifyBinariesCommand()); 250 251 await runner.run(args); 252 } on ArgumentError catch (e) { 253 stderr.writeln(e.toString()); 254 exit(1); 255 } catch (e, s) { 256 log.severe(kDoubleSeparator); 257 log.severe('Cargokit BuildTool failed with error:'); 258 log.severe(kSeparator); 259 log.severe(e); 260 // This tells user to install Rust, there's no need to pollute the log with 261 // stack trace. 262 if (e is! RustupNotFoundException) { 263 log.severe(kSeparator); 264 log.severe(s); 265 log.severe(kSeparator); 266 log.severe('BuildTool arguments: $args'); 267 } 268 log.severe(kDoubleSeparator); 269 exit(1); 270 } 271 }