alef 0.25.37

Opinionated polyglot binding generator for Rust libraries
Documentation
#!/usr/bin/env dart
// Generated by alef. Do not edit by hand.
// Download platform-specific native library for {{ crate_name }} from GitHub releases.

import 'dart:ffi' show Abi;
import 'dart:io';
import 'dart:typed_data';
import 'package:http/http.dart' as http;

const String _moduleVersion = '{{ version }}';
const String _repoUrl = '{{ repo_url }}';
const String _assetPrefix = '{{ crate_name }}';
const String _libStem = '{{ lib_stem }}';

void main() async {
  try {
    await _run();
  } catch (e) {
    stderr.writeln('Error: $e');
    exit(1);
  }
}

Future<void> _run() async {
  final (cacheDir, libPath) = _determinePaths();

  // Check if library already exists
  if (File(libPath).existsSync()) {
    return;
  }

  // Download the FFI library archive
  await _downloadAndExtractLibrary(cacheDir);
}

(String, String) _determinePaths() {
  final osName = _osName();
  final arch = _arch();

  // Use the package root (where pubspec.yaml lives) as base
  final packageRoot = _packageRoot();
  final libDir = '$packageRoot${Platform.pathSeparator}lib${Platform.pathSeparator}src${Platform.pathSeparator}native${Platform.pathSeparator}$osName-$arch';
  final libPath = '$libDir${Platform.pathSeparator}${_libFilename()}';

  return (libDir, libPath);
}

String _osName() {
  if (Platform.isMacOS) return 'macos';
  if (Platform.isWindows) return 'windows';
  if (Platform.isLinux) return 'linux';
  throw UnsupportedError('Unsupported OS: ${Platform.operatingSystem}');
}

String _arch() {
  // `Abi.current()` reports the exact runtime (os, arch) tuple, so derive arch
  // from it directly. Prior code grep'd `Platform.resolvedExecutable` for
  // 'arm64'/'aarch64'/'x86_64', which broke whenever the dart binary path
  // didn't embed the arch (e.g., `/opt/homebrew/Cellar/dart/.../bin/dart` on
  // Apple Silicon contains neither 'arm64' nor 'aarch64').
  final abi = Abi.current();
  switch (abi) {
    case Abi.macosArm64:
    case Abi.linuxArm64:
    case Abi.windowsArm64:
    case Abi.androidArm64:
    case Abi.iosArm64:
      return 'arm64';
    case Abi.macosX64:
    case Abi.linuxX64:
    case Abi.windowsX64:
    case Abi.androidX64:
    case Abi.iosX64:
      return 'x64';
    default:
      throw UnsupportedError('Unsupported architecture: $abi');
  }
}

String _libFilename() {
  if (Platform.isMacOS) return 'lib${_libStem}_dart.dylib';
  if (Platform.isWindows) return '${_libStem}_dart.dll';
  return 'lib${_libStem}_dart.so';
}

String _packageRoot() {
  // Use the directory containing this script (bin/) as reference
  // to find the package root (parent of bin/).
  final scriptPath = Platform.script.toFilePath();
  final binDir = File(scriptPath).parent;
  return binDir.parent.path;
}

Future<void> _downloadAndExtractLibrary(String cacheDir) async {
  final osName = _osName();
  var arch = _arch();

  // Map Dart arch names to alef platform names used in release asset filenames.
  // Go uses x86_64/aarch64; macOS arm64 may differ.
  if (arch == 'arm64' && !Platform.isMacOS) {
    arch = 'aarch64';
  } else if (arch == 'x64') {
    arch = 'x86_64';
  }

  // Clean version for asset name
  final version = _moduleVersion.startsWith('v')
      ? _moduleVersion.substring(1)
      : _moduleVersion;
  final assetName = '$_assetPrefix-dart-v$version-$osName-$arch.tar.gz';
  final downloadUrl = '$_repoUrl/releases/download/v$version/$assetName';

  // Create cache directory
  await Directory(cacheDir).create(recursive: true);

  // Download tarball
  final response = await http.get(Uri.parse(downloadUrl));
  if (response.statusCode != 200) {
    throw Exception('HTTP ${response.statusCode}: Failed to download $downloadUrl');
  }

  // Extract tarball to cache directory
  await _extractTarGz(response.bodyBytes, cacheDir);
}

Future<void> _extractTarGz(Uint8List bytes, String dstDir) async {
  // Simple tar.gz extraction via a native tar command (Unix/macOS) or 7-Zip (Windows).
  // A pure-Dart implementation would require a tar+gzip library; invoking the OS
  // tool is simpler and more reliable.

  final tempFile = File('$dstDir/.download.tar.gz');
  try {
    await tempFile.writeAsBytes(bytes);

    if (Platform.isWindows) {
      // Windows: use 7z if available (usually bundled), else tar.exe (Win10+)
      await Process.run('tar', ['-xzf', tempFile.path, '-C', dstDir]);
    } else {
      // Unix/macOS: tar command is ubiquitous
      await Process.run('tar', ['-xzf', tempFile.path, '-C', dstDir]);
    }
  } finally {
    if (tempFile.existsSync()) {
      await tempFile.delete();
    }
  }
}