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