diff --git a/build_tool/lib/src/artifacts_provider.dart b/build_tool/lib/src/artifacts_provider.dart index ef655a9..e362bb7 100644 --- a/build_tool/lib/src/artifacts_provider.dart +++ b/build_tool/lib/src/artifacts_provider.dart @@ -51,7 +51,9 @@ class ArtifactProvider { final CargokitUserOptions userOptions; Future>> getArtifacts(List 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)); @@ -178,6 +180,82 @@ class ArtifactProvider { } } + Future>> _getLocalPrecompiledArtifacts( + List 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 = >{}; + + for (final target in targets) { + final requiredArtifacts = getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + remote: true, + ); + final artifactsForTarget = []; + + 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 _tryDownloadArtifacts({ required String crateHash, required String fileName, @@ -213,6 +291,21 @@ class ArtifactProvider { _log.shout('Signature verification failed! Ignoring binary.'); } } + + Future _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 { diff --git a/build_tool/lib/src/build_tool.dart b/build_tool/lib/src/build_tool.dart index 1d9462a..ee03a15 100644 --- a/build_tool/lib/src/build_tool.dart +++ b/build_tool/lib/src/build_tool.dart @@ -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'; @@ -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 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; + 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 @@ -243,6 +332,7 @@ Future runMain(List args) async { ..addCommand(BuildCMakeCommand()) ..addCommand(GenKeyCommand()) ..addCommand(PrecompileBinariesCommand()) + ..addCommand(PrecompileLocalBinariesCommand()) ..addCommand(VerifyBinariesCommand()); await runner.run(args); diff --git a/build_tool/lib/src/options.dart b/build_tool/lib/src/options.dart index 7937dca..b1d3ea1 100644 --- a/build_tool/lib/src/options.dart +++ b/build_tool/lib/src/options.dart @@ -237,11 +237,13 @@ 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) { @@ -249,6 +251,7 @@ class CargokitUserOptions { } 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')) { @@ -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, ); } @@ -303,4 +316,5 @@ class CargokitUserOptions { final bool usePrecompiledBinaries; final bool verboseLogging; + final bool useLocalPrecompiledBinaries; } diff --git a/build_tool/lib/src/precompile_local_binaries.dart b/build_tool/lib/src/precompile_local_binaries.dart new file mode 100644 index 0000000..77d025c --- /dev/null +++ b/build_tool/lib/src/precompile_local_binaries.dart @@ -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 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 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); + } +} diff --git a/run_build_tool.sh b/run_build_tool.sh index 24b0ed8..7367bcb 100755 --- a/run_build_tool.sh +++ b/run_build_tool.sh @@ -11,6 +11,9 @@ cd "$CARGOKIT_TOOL_TEMP_DIR" # Write a very simple bin package in temp folder that depends on build_tool package # from Cargokit. This is done to ensure that we don't pollute Cargokit folder # with .dart_tool contents. +cat << EOF > "directory.txt" +$BASEDIR +EOF BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool"