/ app / rust_builder / cargokit / build_tool / lib / src / builder.dart
builder.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 'package:collection/collection.dart';
  5  import 'package:logging/logging.dart';
  6  import 'package:path/path.dart' as path;
  7  
  8  import 'android_environment.dart';
  9  import 'cargo.dart';
 10  import 'environment.dart';
 11  import 'options.dart';
 12  import 'rustup.dart';
 13  import 'target.dart';
 14  import 'util.dart';
 15  
 16  final _log = Logger('builder');
 17  
 18  enum BuildConfiguration {
 19    debug,
 20    release,
 21    profile,
 22  }
 23  
 24  extension on BuildConfiguration {
 25    bool get isDebug => this == BuildConfiguration.debug;
 26    String get rustName => switch (this) {
 27          BuildConfiguration.debug => 'debug',
 28          BuildConfiguration.release => 'release',
 29          BuildConfiguration.profile => 'release',
 30        };
 31  }
 32  
 33  class BuildException implements Exception {
 34    final String message;
 35  
 36    BuildException(this.message);
 37  
 38    @override
 39    String toString() {
 40      return 'BuildException: $message';
 41    }
 42  }
 43  
 44  class BuildEnvironment {
 45    final BuildConfiguration configuration;
 46    final CargokitCrateOptions crateOptions;
 47    final String targetTempDir;
 48    final String manifestDir;
 49    final CrateInfo crateInfo;
 50  
 51    final bool isAndroid;
 52    final String? androidSdkPath;
 53    final String? androidNdkVersion;
 54    final int? androidMinSdkVersion;
 55    final String? javaHome;
 56  
 57    BuildEnvironment({
 58      required this.configuration,
 59      required this.crateOptions,
 60      required this.targetTempDir,
 61      required this.manifestDir,
 62      required this.crateInfo,
 63      required this.isAndroid,
 64      this.androidSdkPath,
 65      this.androidNdkVersion,
 66      this.androidMinSdkVersion,
 67      this.javaHome,
 68    });
 69  
 70    static BuildConfiguration parseBuildConfiguration(String value) {
 71      // XCode configuration adds the flavor to configuration name.
 72      final firstSegment = value.split('-').first;
 73      final buildConfiguration = BuildConfiguration.values.firstWhereOrNull(
 74        (e) => e.name == firstSegment,
 75      );
 76      if (buildConfiguration == null) {
 77        _log.warning('Unknown build configuraiton $value, will assume release');
 78        return BuildConfiguration.release;
 79      }
 80      return buildConfiguration;
 81    }
 82  
 83    static BuildEnvironment fromEnvironment({
 84      required bool isAndroid,
 85    }) {
 86      final buildConfiguration =
 87          parseBuildConfiguration(Environment.configuration);
 88      final manifestDir = Environment.manifestDir;
 89      final crateOptions = CargokitCrateOptions.load(
 90        manifestDir: manifestDir,
 91      );
 92      final crateInfo = CrateInfo.load(manifestDir);
 93      return BuildEnvironment(
 94        configuration: buildConfiguration,
 95        crateOptions: crateOptions,
 96        targetTempDir: Environment.targetTempDir,
 97        manifestDir: manifestDir,
 98        crateInfo: crateInfo,
 99        isAndroid: isAndroid,
100        androidSdkPath: isAndroid ? Environment.sdkPath : null,
101        androidNdkVersion: isAndroid ? Environment.ndkVersion : null,
102        androidMinSdkVersion:
103            isAndroid ? int.parse(Environment.minSdkVersion) : null,
104        javaHome: isAndroid ? Environment.javaHome : null,
105      );
106    }
107  }
108  
109  class RustBuilder {
110    final Target target;
111    final BuildEnvironment environment;
112  
113    RustBuilder({
114      required this.target,
115      required this.environment,
116    });
117  
118    void prepare(
119      Rustup rustup,
120    ) {
121      final toolchain = _toolchain;
122      if (rustup.installedTargets(toolchain) == null) {
123        rustup.installToolchain(toolchain);
124      }
125      if (toolchain == 'nightly') {
126        rustup.installRustSrcForNightly();
127      }
128      if (!rustup.installedTargets(toolchain)!.contains(target.rust)) {
129        rustup.installTarget(target.rust, toolchain: toolchain);
130      }
131    }
132  
133    CargoBuildOptions? get _buildOptions =>
134        environment.crateOptions.cargo[environment.configuration];
135  
136    String get _toolchain => _buildOptions?.toolchain.name ?? 'stable';
137  
138    /// Returns the path of directory containing build artifacts.
139    Future<String> build() async {
140      final extraArgs = _buildOptions?.flags ?? [];
141      final manifestPath = path.join(environment.manifestDir, 'Cargo.toml');
142      runCommand(
143        'rustup',
144        [
145          'run',
146          _toolchain,
147          'cargo',
148          'build',
149          ...extraArgs,
150          '--manifest-path',
151          manifestPath,
152          '-p',
153          environment.crateInfo.packageName,
154          if (!environment.configuration.isDebug) '--release',
155          '--target',
156          target.rust,
157          '--target-dir',
158          environment.targetTempDir,
159        ],
160        environment: await _buildEnvironment(),
161      );
162      return path.join(
163        environment.targetTempDir,
164        target.rust,
165        environment.configuration.rustName,
166      );
167    }
168  
169    Future<Map<String, String>> _buildEnvironment() async {
170      if (target.android == null) {
171        return {};
172      } else {
173        final sdkPath = environment.androidSdkPath;
174        final ndkVersion = environment.androidNdkVersion;
175        final minSdkVersion = environment.androidMinSdkVersion;
176        if (sdkPath == null) {
177          throw BuildException('androidSdkPath is not set');
178        }
179        if (ndkVersion == null) {
180          throw BuildException('androidNdkVersion is not set');
181        }
182        if (minSdkVersion == null) {
183          throw BuildException('androidMinSdkVersion is not set');
184        }
185        final env = AndroidEnvironment(
186          sdkPath: sdkPath,
187          ndkVersion: ndkVersion,
188          minSdkVersion: minSdkVersion,
189          targetTempDir: environment.targetTempDir,
190          target: target,
191        );
192        if (!env.ndkIsInstalled() && environment.javaHome != null) {
193          env.installNdk(javaHome: environment.javaHome!);
194        }
195        return env.buildEnvironment();
196      }
197    }
198  }