Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 94 additions & 1 deletion build_tool/lib/src/artifacts_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ class ArtifactProvider {
final CargokitUserOptions userOptions;

Future<Map<Target, List<Artifact>>> getArtifacts(List<Target> targets) async {
final result = await _getPrecompiledArtifacts(targets);
final result = userOptions.useLocalPrecompiledBinaries == false
? await _getLocalPrecompiledArtifacts(targets)
: await _getPrecompiledArtifacts(targets);

final pendingTargets = List.of(targets);
pendingTargets.removeWhere((element) => result.containsKey(element));
Expand Down Expand Up @@ -178,6 +180,82 @@ class ArtifactProvider {
}
}

Future<Map<Target, List<Artifact>>> _getLocalPrecompiledArtifacts(
List<Target> targets,
) async {
if (userOptions.usePrecompiledBinaries == false) {
_log.info('Precompiled binaries are disabled');
return {};
}
if (environment.crateOptions.precompiledBinaries == null) {
_log.fine('Precompiled binaries not enabled for this crate');
return {};
}

final start = Stopwatch()..start();
final crateHash = CrateHash.compute(
environment.manifestDir,
tempStorage: environment.targetTempDir,
);
_log.fine(
'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms');

final downloadedArtifactsDir = path.join(
environment.targetTempDir,
'precompiled',
crateHash,
);
Directory(downloadedArtifactsDir).createSync(recursive: true);

final res = <Target, List<Artifact>>{};

for (final target in targets) {
final requiredArtifacts = getArtifactNames(
target: target,
libraryName: environment.crateInfo.packageName,
remote: true,
);
final artifactsForTarget = <Artifact>[];

for (final artifact in requiredArtifacts) {
final fileName =
'$target/$artifact'; // PrecompileBinaries.fileName(target, artifact);
final downloadedPath = path.join(downloadedArtifactsDir, fileName);

if (!File(downloadedPath).existsSync()) {
String filePath = "${Directory.current.path}/directory.txt";
File file = File(filePath);

if (file.existsSync()) {
String firstLine = file.readAsLinesSync().first;

await _tryLocalDownloadArtifacts(
fileName: fileName,
finalPath: downloadedPath,
sdkDirectory: firstLine,
);
}
}
if (File(downloadedPath).existsSync()) {
artifactsForTarget.add(Artifact(
path: downloadedPath,
finalFileName: artifact,
));
} else {
break;
}
}

// Only provide complete set of artifacts.
if (artifactsForTarget.length == requiredArtifacts.length) {
_log.fine('Found precompiled artifacts for $target');
res[target] = artifactsForTarget;
}
}

return res;
}

Future<void> _tryDownloadArtifacts({
required String crateHash,
required String fileName,
Expand Down Expand Up @@ -213,6 +291,21 @@ class ArtifactProvider {
_log.shout('Signature verification failed! Ignoring binary.');
}
}

Future<void> _tryLocalDownloadArtifacts({
required String fileName,
required String finalPath,
required String sdkDirectory,
}) async {
final sdkPath = '$sdkDirectory/binary/$fileName';
final binaryFile = File(sdkPath);
if (!binaryFile.existsSync()) {
throw Exception('Missing artifact: ${binaryFile.path}');
}
File destinationFile = File(finalPath);
destinationFile.parent.createSync(recursive: true);
binaryFile.copySync(finalPath);
}
}

enum AritifactType {
Expand Down
90 changes: 90 additions & 0 deletions build_tool/lib/src/build_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'build_pod.dart';
import 'logging.dart';
import 'options.dart';
import 'precompile_binaries.dart';
import 'precompile_local_binaries.dart';
import 'target.dart';
import 'util.dart';
import 'verify_binaries.dart';
Expand Down Expand Up @@ -94,6 +95,94 @@ class GenKeyCommand extends Command {
}
}

class PrecompileLocalBinariesCommand extends Command {
PrecompileLocalBinariesCommand() {
argParser
..addOption(
'manifest-dir',
mandatory: true,
help: 'Directory containing Cargo.toml',
)
..addMultiOption(
'target',
help: 'Rust target triple of artifact to build.\n'
'Can be specified multiple times or omitted in which case\n'
'all targets for current platform will be built.',
)
..addOption(
'android-sdk-location',
help: 'Location of Android SDK (if available)',
)
..addOption(
'android-ndk-version',
help: 'Android NDK version (if available)',
)
..addOption(
'android-min-sdk-version',
help: 'Android minimum rquired version (if available)',
)
..addOption(
'temp-dir',
help: 'Directory to store temporary build artifacts',
)
..addFlag(
"verbose",
abbr: "v",
defaultsTo: false,
help: "Enable verbose logging",
);
}

@override
final name = 'precompile-local-binaries';

@override
final description = 'Prebuild and create local binaries\n';

@override
Future<void> run() async {
final verbose = argResults!['verbose'] as bool;
if (verbose) {
enableVerboseLogging();
}

final manifestDir = argResults!['manifest-dir'] as String;
if (!Directory(manifestDir).existsSync()) {
throw ArgumentError('Manifest directory does not exist: $manifestDir');
}
String? androidMinSdkVersionString =
argResults!['android-min-sdk-version'] as String?;
int? androidMinSdkVersion;
if (androidMinSdkVersionString != null) {
androidMinSdkVersion = int.tryParse(androidMinSdkVersionString);
if (androidMinSdkVersion == null) {
throw ArgumentError(
'Invalid android-min-sdk-version: $androidMinSdkVersionString',
);
}
}
final targetStrigns = argResults!['target'] as List<String>;
final targets = targetStrigns.map((target) {
final res = Target.forRustTriple(target);
if (res == null) {
throw ArgumentError('Invalid target: $target');
}
return res;
}).toList(growable: false);

final precompileBinaries = PrecompileLocalBinaries(
manifestDir: manifestDir,
targets: targets,
androidSdkLocation: argResults!['android-sdk-location'] as String?,
androidNdkVersion: argResults!['android-ndk-version'] as String?,
androidMinSdkVersion: androidMinSdkVersion,
tempDir: argResults!['temp-dir'] as String?,
);

await precompileBinaries.run();
}
}

class PrecompileBinariesCommand extends Command {
PrecompileBinariesCommand() {
argParser
Expand Down Expand Up @@ -243,6 +332,7 @@ Future<void> runMain(List<String> args) async {
..addCommand(BuildCMakeCommand())
..addCommand(GenKeyCommand())
..addCommand(PrecompileBinariesCommand())
..addCommand(PrecompileLocalBinariesCommand())
..addCommand(VerifyBinariesCommand());

await runner.run(args);
Expand Down
18 changes: 16 additions & 2 deletions build_tool/lib/src/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,21 @@ class CargokitUserOptions {
CargokitUserOptions({
required this.usePrecompiledBinaries,
required this.verboseLogging,
required this.useLocalPrecompiledBinaries,
});

CargokitUserOptions._()
: usePrecompiledBinaries = defaultUsePrecompiledBinaries(),
verboseLogging = false;
verboseLogging = false,
useLocalPrecompiledBinaries = false;

static CargokitUserOptions parse(YamlNode node) {
if (node is! YamlMap) {
throw SourceSpanException('Cargokit options must be a map', node.span);
}
bool usePrecompiledBinaries = defaultUsePrecompiledBinaries();
bool verboseLogging = false;
bool useLocalPrecompiledBinaries = false;

for (final entry in node.nodes.entries) {
if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) {
Expand All @@ -267,15 +270,25 @@ class CargokitUserOptions {
throw SourceSpanException(
'Invalid value for "verbose_logging". Must be a boolean.',
entry.value.span);
} else if (entry.key
case YamlScalar(value: 'use_local_precompiled_binaries')) {
if (entry.value case YamlScalar(value: bool value)) {
useLocalPrecompiledBinaries = value;
continue;
}
throw SourceSpanException(
'Invalid value for "use_local_precompiled_binaries". Must be a boolean.',
entry.value.span);
} else {
throw SourceSpanException(
'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".',
'Unknown cargokit option type. Must be "use_precompiled_binaries" , "use_local_precompiled_binaries" or "verbose_logging".',
entry.key.span);
}
}
return CargokitUserOptions(
usePrecompiledBinaries: usePrecompiledBinaries,
verboseLogging: verboseLogging,
useLocalPrecompiledBinaries: useLocalPrecompiledBinaries,
);
}

Expand Down Expand Up @@ -303,4 +316,5 @@ class CargokitUserOptions {

final bool usePrecompiledBinaries;
final bool verboseLogging;
final bool useLocalPrecompiledBinaries;
}
107 changes: 107 additions & 0 deletions build_tool/lib/src/precompile_local_binaries.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;

import 'artifacts_provider.dart';
import 'builder.dart';
import 'cargo.dart';
import 'options.dart';
import 'rustup.dart';
import 'target.dart';

final _log = Logger('precompile_local_binaries');

class PrecompileLocalBinaries {
PrecompileLocalBinaries({
required this.manifestDir,
required this.targets,
this.androidSdkLocation,
this.androidNdkVersion,
this.androidMinSdkVersion,
this.tempDir,
});

final String manifestDir;
final List<Target> targets;
final String? androidSdkLocation;
final String? androidNdkVersion;
final int? androidMinSdkVersion;
final String? tempDir;

static String fileName(
Target target,
String name,
) {
return '${target.rust}_$name';
}

static String signatureFileName(
Target target,
String name,
) {
return '${target.rust}_$name.sig';
}

Future<void> run() async {
final crateInfo = CrateInfo.load(manifestDir);

final targets = List.of(this.targets);
if (targets.isEmpty) {
targets.addAll([
...Target.buildableTargets(),
if (androidSdkLocation != null) ...Target.androidTargets(),
]);
}

_log.info('Precompiling binaries for $targets');

final tempDir = this.tempDir != null
? Directory(this.tempDir!)
: Directory.systemTemp.createTempSync('precompiled_');

tempDir.createSync(recursive: true);

final crateOptions = CargokitCrateOptions.load(manifestDir: manifestDir);

final buildEnvironment = BuildEnvironment(
configuration: BuildConfiguration.release,
crateOptions: crateOptions,
targetTempDir: tempDir.path,
manifestDir: manifestDir,
crateInfo: crateInfo,
isAndroid: androidSdkLocation != null,
androidSdkPath: androidSdkLocation,
androidNdkVersion: androidNdkVersion,
androidMinSdkVersion: androidMinSdkVersion,
);

final rustup = Rustup();

for (final target in targets) {
final artifactNames = getArtifactNames(
target: target, libraryName: crateInfo.packageName, remote: true);

_log.info('Building for $target');

final builder =
RustBuilder(target: target, environment: buildEnvironment);
builder.prepare(rustup);
final res = await builder.build();

for (final name in artifactNames) {
final file = File(path.join(res, name));
if (!file.existsSync()) {
throw Exception('Missing artifact: ${file.path}');
}

String destinationPath = "../binary/$target/$name";
File destinationFile = File(destinationPath);
destinationFile.parent.createSync(recursive: true);
file.copySync(destinationPath);
}
}

_log.info('Cleaning up');
tempDir.deleteSync(recursive: true);
}
}
Loading