// Generated by alef. Do not edit by hand.
// Shared native-library resolution for {{ crate_name }}: cache-path computation,
// release-asset download, SHA-256 verification, and tar.gz extraction.
//
// This helper is imported by both `bin/download_libs.dart` (the install-time
// fetcher) and the flutter_rust_bridge loader override in
// `frb_generated.dart` (the first-use resolver), so the cache-path and checksum
// logic lives in exactly one place.
import 'dart:ffi' show Abi;
import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' show sha256;
import 'package:http/http.dart' as http;
/// Package version baked at codegen time. The release tag and the cache
/// subdirectory both derive from this, so a downloaded native can never
/// mismatch the package version: an upgrade changes the cache path and forces
/// a fresh download.
const String nativeModuleVersion = '{{ version }}';
/// GitHub repository base URL (e.g. `https://github.com/owner/repo`).
const String nativeRepoUrl = '{{ repo_url }}';
/// Release-asset name prefix and cache namespace (the package name).
const String nativeAssetPrefix = '{{ crate_name }}';
/// Native library file stem (without the platform prefix/suffix).
const String nativeLibStem = '{{ lib_stem }}';
/// Environment variable that, when set to a directory, short-circuits all
/// resolution and native download (dev/test path, no network). The directory
/// is expected to contain the platform native library directly.
const String nativeLibDirEnv = 'FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR';
/// Version with any leading `v` stripped (asset names embed the bare version).
String nativeCleanVersion() {
return nativeModuleVersion.startsWith('v') ? nativeModuleVersion.substring(1) : nativeModuleVersion;
}
/// Runtime identifier used for the cache subdirectory and the loader search
/// path (`macos-arm64`, `linux-x64`, `windows-arm64`, …), or `null` on an
/// unsupported platform.
String? nativeComputeRid() {
final abi = Abi.current();
final os = Platform.operatingSystem;
if (os == 'linux') {
if (abi == Abi.linuxX64) return 'linux-x64';
if (abi == Abi.linuxArm64) return 'linux-arm64';
} else if (os == 'macos') {
if (abi == Abi.macosX64) return 'macos-x64';
if (abi == Abi.macosArm64) return 'macos-arm64';
} else if (os == 'windows') {
if (abi == Abi.windowsX64) return 'windows-x64';
if (abi == Abi.windowsArm64) return 'windows-arm64';
}
return null;
}
/// Platform-specific native library filename (`lib<stem>_dart.dylib`, etc.).
String nativeLibFilename() {
if (Platform.isMacOS) return 'lib${nativeLibStem}_dart.dylib';
if (Platform.isWindows) return '${nativeLibStem}_dart.dll';
return 'lib${nativeLibStem}_dart.so';
}
/// User-cache base directory, resolved from `XDG_CACHE_HOME`, then the
/// platform default (`~/Library/Caches` on macOS, `%LOCALAPPDATA%` on Windows,
/// `~/.cache` elsewhere). Returns `null` if no base can be determined.
String? nativeCacheBase() {
final env = Platform.environment;
final xdg = env['XDG_CACHE_HOME'];
if (xdg != null && xdg.isNotEmpty) return xdg;
if (Platform.isMacOS) {
final home = env['HOME'];
if (home != null && home.isNotEmpty) {
return '$home${Platform.pathSeparator}Library${Platform.pathSeparator}Caches';
}
} else if (Platform.isWindows) {
final localAppData = env['LOCALAPPDATA'];
if (localAppData != null && localAppData.isNotEmpty) return localAppData;
}
final home = env['HOME'];
if (home != null && home.isNotEmpty) {
return '$home${Platform.pathSeparator}.cache';
}
return null;
}
/// Versioned per-RID cache directory `<cache>/<prefix>/<version>/<rid>/`, or
/// `null` when the cache base or RID cannot be resolved.
String? nativeCacheDir() {
final base = nativeCacheBase();
final rid = nativeComputeRid();
if (base == null || rid == null) return null;
final sep = Platform.pathSeparator;
return '$base$sep$nativeAssetPrefix$sep${nativeCleanVersion()}$sep$rid';
}
/// Absolute path of the cached native library, or `null` if the cache
/// directory cannot be resolved.
String? nativeCachedLibPath() {
final dir = nativeCacheDir();
if (dir == null) return null;
return '$dir${Platform.pathSeparator}${nativeLibFilename()}';
}
/// Release-asset blob name for the current platform (`macos-x86_64`,
/// `linux-aarch64`, …) — the asset filenames use `x86_64`/`aarch64` while the
/// cache RID uses `x64`/`arm64`.
String nativeAssetBlob() {
final os = _osName();
final abi = Abi.current();
final arch = (abi == Abi.macosArm64 ||
abi == Abi.linuxArm64 ||
abi == Abi.windowsArm64)
? 'aarch64'
: 'x86_64';
return '$os-$arch';
}
String _osName() {
if (Platform.isMacOS) return 'macos';
if (Platform.isWindows) return 'windows';
if (Platform.isLinux) return 'linux';
throw UnsupportedError('Unsupported OS: ${Platform.operatingSystem}');
}
/// Base URL (without the trailing extension) for the current platform's
/// release asset: `<repo>/releases/download/v<version>/<prefix>-dart-v<version>-<blob>`.
String nativeAssetUrlBase() {
final version = nativeCleanVersion();
final assetStem = '$nativeAssetPrefix-dart-v$version-${nativeAssetBlob()}';
return '$nativeRepoUrl/releases/download/v$version/$assetStem';
}
/// Download the platform native library into the versioned user cache,
/// verifying its SHA-256 against the published `.sha256` sidecar before
/// extraction. Returns the absolute path of the extracted library.
///
/// Throws a [StateError] on checksum mismatch and an [Exception] on any HTTP
/// or extraction failure.
Future<String> nativeDownloadAndCacheLibrary() async {
final cacheDir = nativeCacheDir();
if (cacheDir == null) {
throw StateError(
'Unable to determine a user-cache directory for the $nativeAssetPrefix native library. '
'Set XDG_CACHE_HOME, or override with $nativeLibDirEnv.',
);
}
final libPath = '$cacheDir${Platform.pathSeparator}${nativeLibFilename()}';
if (File(libPath).existsSync()) {
return libPath;
}
final urlBase = nativeAssetUrlBase();
final tarballUrl = '$urlBase.tar.gz';
final sha256Url = '$tarballUrl.sha256';
// Download the tarball.
final tarballResponse = await http.get(Uri.parse(tarballUrl));
if (tarballResponse.statusCode != 200) {
throw Exception(
'HTTP ${tarballResponse.statusCode}: failed to download $tarballUrl',
);
}
final Uint8List tarballBytes = tarballResponse.bodyBytes;
// Download the `.sha256` sidecar and verify before extracting.
final shaResponse = await http.get(Uri.parse(sha256Url));
if (shaResponse.statusCode != 200) {
throw Exception(
'HTTP ${shaResponse.statusCode}: failed to download checksum $sha256Url',
);
}
final expected = _parseSha256Sidecar(shaResponse.body);
final actual = sha256.convert(tarballBytes).toString();
if (expected != actual) {
throw StateError(
'SHA-256 mismatch for $tarballUrl: expected $expected, got $actual. '
'Refusing to install a native library that does not match its published checksum.',
);
}
await Directory(cacheDir).create(recursive: true);
await _extractTarGz(tarballBytes, cacheDir);
return libPath;
}
/// Parse the hex digest from a `sha256sum`-style sidecar body, which is either
/// a bare digest or `<digest> <filename>`.
String _parseSha256Sidecar(String body) {
final trimmed = body.trim();
final firstToken = trimmed.split(RegExp(r'\s+')).first;
return firstToken.toLowerCase();
}
Future<void> _extractTarGz(Uint8List bytes, String dstDir) async {
// Extract via the OS `tar` (ubiquitous on Unix/macOS; bundled on Windows 10+),
// avoiding a pure-Dart tar+gzip dependency.
final tempFile = File('$dstDir${Platform.pathSeparator}.download.tar.gz');
try {
await Directory(dstDir).create(recursive: true);
await tempFile.writeAsBytes(bytes);
final result = await Process.run('tar', ['-xzf', tempFile.path, '-C', dstDir]);
if (result.exitCode != 0) {
throw Exception('tar extraction failed (exit ${result.exitCode}): ${result.stderr}');
}
} finally {
if (tempFile.existsSync()) {
await tempFile.delete();
}
}
}