From 7c4c6109f8d8a3c32f607c9b2bc42aa70c76c539 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 19 Jun 2026 09:50:36 -0500 Subject: [PATCH 1/4] fix(android): apply 16kb alignment config in native asset builds --- .github/workflows/ci.yml | 53 +++++++ hook/build.dart | 11 +- pubspec.yaml | 1 + scripts/check_android_elf_alignment.dart | 173 ++++++++++++++++++++++ scripts/check_android_hook_alignment.dart | 158 ++++++++++++++++++++ 5 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 scripts/check_android_elf_alignment.dart create mode 100644 scripts/check_android_hook_alignment.dart diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d134a4..0ade4b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,59 @@ jobs: working-directory: bdk_demo run: flutter build apk --debug --target-platform android-arm64 + android-alignment: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: "3.10.0" + + - name: Pub get + run: dart pub get + + - name: Install Android NDK + run: sdkmanager "ndk;27.1.12297006" + + - name: Build Android native library with NDK 27 + run: | + rustup toolchain install 1.85.1 --profile minimal --target aarch64-linux-android + NDK="$ANDROID_HOME/ndk/27.1.12297006" + TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64" + export ANDROID_NDK_HOME="$NDK" + export ANDROID_NDK_ROOT="$NDK" + export AR_aarch64_linux_android="$TOOLCHAIN/bin/llvm-ar" + export CC_aarch64_linux_android="$TOOLCHAIN/bin/aarch64-linux-android35-clang" + export CXX_aarch64_linux_android="$TOOLCHAIN/bin/aarch64-linux-android35-clang++" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/aarch64-linux-android35-clang" + export BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android="--sysroot=$TOOLCHAIN/sysroot -I$TOOLCHAIN/sysroot/usr/include/aarch64-linux-android" + rustup run 1.85.1 cargo build \ + --release \ + --manifest-path native/Cargo.toml \ + --package bdk_dart_ffi \ + --target aarch64-linux-android \ + --target-dir /tmp/bdk-dart-android-target \ + --config native/.cargo/config.toml + + - name: Verify NDK 27 native library alignment + run: dart scripts/check_android_elf_alignment.dart /tmp/bdk-dart-android-target/aarch64-linux-android/release/libbdk_dart_ffi.so + + - name: Verify NDK 27 hook alignment + run: | + export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.1.12297006" + export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" + dart scripts/check_android_hook_alignment.dart + ios-smoke: runs-on: macos-latest steps: diff --git a/hook/build.dart b/hook/build.dart index 7e7790e..1d97b29 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,10 +1,17 @@ import 'package:hooks/hooks.dart'; import 'package:native_toolchain_rust/native_toolchain_rust.dart'; -void main(List args) async { +Future main(List args) async { await build(args, (input, output) async { - await const RustBuilder( + final cargoConfigPath = input.packageRoot + .resolve('native/.cargo/config.toml') + .toFilePath(); + + // Native Assets invokes Cargo from the package root, so pass the crate-local + // config explicitly instead of relying on Cargo's working-directory lookup. + await RustBuilder( assetName: 'uniffi:bdk_dart_ffi', + extraCargoBuildArgs: ['--config', cargoConfigPath], ).run(input: input, output: output); }); } diff --git a/pubspec.yaml b/pubspec.yaml index c84e174..ac598f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: hooks: ^1.0.0 native_toolchain_rust: ^1.0.0 dev_dependencies: + code_assets: ^1.0.0 test: ^1.26.2 hooks: diff --git a/scripts/check_android_elf_alignment.dart b/scripts/check_android_elf_alignment.dart new file mode 100644 index 0000000..d0ef62d --- /dev/null +++ b/scripts/check_android_elf_alignment.dart @@ -0,0 +1,173 @@ +import 'dart:io'; +import 'dart:typed_data'; + +const _libraryName = 'libbdk_dart_ffi.so'; +const _minimumLoadAlignment = 0x4000; +const _ptLoad = 1; + +Future main(List args) async { + if (args.isEmpty) { + _fail( + 'Usage: dart scripts/check_android_elf_alignment.dart [...]', + ); + } + + for (final path in args) { + final input = File(path); + if (!input.existsSync()) { + _fail('File not found: ${input.path}'); + } + + if (_isElf(input)) { + _checkLibrary(input, input.path); + } else { + await _checkApk(input); + } + } +} + +Future _checkApk(File apk) async { + final tempDir = await Directory.systemTemp.createTemp( + 'bdk_dart_elf_alignment_', + ); + + try { + await _extractLibraries(apk, tempDir); + final libraries = + tempDir + .listSync(recursive: true) + .whereType() + .where((file) => file.uri.pathSegments.last == _libraryName) + .toList() + ..sort((a, b) => a.path.compareTo(b.path)); + + if (libraries.isEmpty) { + _fail('No $_libraryName entries found in ${apk.path}'); + } + + for (final library in libraries) { + _checkLibrary(library, _relativePath(library, tempDir)); + } + } finally { + await tempDir.delete(recursive: true); + } +} + +void _checkLibrary(File library, String label) { + final loadAlignments = _readLoadAlignments(library); + if (loadAlignments.isEmpty) { + _fail('No PT_LOAD segments found in $label'); + } + + final invalidAlignments = loadAlignments.where( + (alignment) => + alignment < _minimumLoadAlignment || + alignment % _minimumLoadAlignment != 0, + ); + + if (invalidAlignments.isNotEmpty) { + _fail( + '$label has invalid PT_LOAD alignment(s): ' + '${invalidAlignments.map(_hex).join(', ')}', + ); + } + + final minimumAlignment = loadAlignments.reduce((a, b) => a < b ? a : b); + stdout.writeln('OK $label minLOADalign=${_hex(minimumAlignment)}'); +} + +Future _extractLibraries(File apk, Directory destination) async { + final result = await Process.run('unzip', [ + '-q', + apk.path, + 'lib/*/$_libraryName', + '-d', + destination.path, + ]); + + if (result.exitCode != 0) { + _fail( + 'Failed to extract $_libraryName from ${apk.path}.\n' + '${result.stderr}', + ); + } +} + +bool _isElf(File file) { + final reader = file.openSync(); + try { + final magic = reader.readSync(4); + return magic.length == 4 && + magic[0] == 0x7f && + magic[1] == 0x45 && + magic[2] == 0x4c && + magic[3] == 0x46; + } finally { + reader.closeSync(); + } +} + +List _readLoadAlignments(File elfFile) { + final bytes = elfFile.readAsBytesSync(); + if (bytes.length < 64 || !_isElf(elfFile)) { + _fail('${elfFile.path} is not an ELF file'); + } + + final elfClass = bytes[4]; + final endian = switch (bytes[5]) { + 1 => Endian.little, + 2 => Endian.big, + _ => throw FormatException('Unsupported ELF endianness in ${elfFile.path}'), + }; + final data = ByteData.sublistView(bytes); + + int uint16(int offset) => data.getUint16(offset, endian); + int uint32(int offset) => data.getUint32(offset, endian); + int uint64(int offset) => data.getUint64(offset, endian); + + final ( + programHeaderOffset, + programHeaderEntrySize, + programHeaderCount, + ) = switch (elfClass) { + 1 => (uint32(28), uint16(42), uint16(44)), + 2 => (uint64(32), uint16(54), uint16(56)), + _ => throw FormatException('Unsupported ELF class in ${elfFile.path}'), + }; + + final alignments = []; + for (var index = 0; index < programHeaderCount; index++) { + final headerOffset = programHeaderOffset + index * programHeaderEntrySize; + if (headerOffset + programHeaderEntrySize > bytes.length) { + _fail('${elfFile.path} has a truncated ELF program header table'); + } + + final type = uint32(headerOffset); + if (type != _ptLoad) { + continue; + } + + final alignment = switch (elfClass) { + 1 => uint32(headerOffset + 28), + 2 => uint64(headerOffset + 48), + _ => throw StateError('unreachable'), + }; + alignments.add(alignment); + } + + return alignments; +} + +String _relativePath(File file, Directory root) { + final prefix = '${root.path}${Platform.pathSeparator}'; + return file.path.startsWith(prefix) + ? file.path.substring(prefix.length) + : file.path; +} + +String _hex(int value) => '0x${value.toRadixString(16)}'; + +Never _fail(String message) { + stderr.writeln(message); + exit(1); +} diff --git a/scripts/check_android_hook_alignment.dart b/scripts/check_android_hook_alignment.dart new file mode 100644 index 0000000..fd96e9a --- /dev/null +++ b/scripts/check_android_hook_alignment.dart @@ -0,0 +1,158 @@ +import 'dart:io'; + +import 'package:code_assets/code_assets.dart'; + +import '../hook/build.dart' as build_hook; +import 'check_android_elf_alignment.dart' as elf_alignment; + +const _androidNdkVersion = '27.1.12297006'; +const _androidApi = 35; +const _androidArm64Clang = 'aarch64-linux-android35-clang'; +const _assetId = 'package:bdk_dart/uniffi:bdk_dart_ffi'; + +Future main(List args) async { + final ndk = _findNdk(args); + final toolchain = _findLlvmToolchain(ndk); + final clang = _tool(toolchain, _androidArm64Clang); + + await testCodeBuildHook( + mainMethod: build_hook.main, + targetOS: OS.android, + targetArchitecture: Architecture.arm64, + targetAndroidNdkApi: _androidApi, + cCompiler: CCompilerConfig( + archiver: _tool(toolchain, 'llvm-ar').uri, + compiler: clang.uri, + linker: clang.uri, + ), + check: (_, output) async { + final assets = output.assets.code + .where((asset) => asset.id == _assetId) + .toList(); + if (assets.length != 1) { + _fail( + 'Expected exactly one $_assetId asset, found ${assets.length}: ' + '${output.assets.code.map((asset) => asset.id).join(', ')}', + ); + } + + final file = assets.single.file; + if (file == null) { + _fail('$_assetId did not include a file path'); + } + + final library = File.fromUri(file); + if (!library.existsSync()) { + _fail('Hook output file does not exist: ${library.path}'); + } + + stdout.writeln('Hook emitted ${library.path}'); + await elf_alignment.main([library.path]); + }, + ); +} + +Directory _findNdk(List args) { + final explicitNdkPath = _parseNdkPath(args); + final androidHome = Platform.environment['ANDROID_HOME']; + final androidSdkRoot = Platform.environment['ANDROID_SDK_ROOT']; + final candidates = [ + explicitNdkPath, + Platform.environment['ANDROID_NDK_HOME'], + Platform.environment['ANDROID_NDK_ROOT'], + if (androidHome != null) + _join(_join(androidHome, 'ndk'), _androidNdkVersion), + if (androidSdkRoot != null) + _join(_join(androidSdkRoot, 'ndk'), _androidNdkVersion), + ]; + + for (final path in candidates.whereType()) { + final ndk = Directory(path); + if (ndk.existsSync()) { + return ndk; + } + } + + _fail( + 'Android NDK $_androidNdkVersion not found. Set ANDROID_NDK_HOME, ' + 'ANDROID_NDK_ROOT, ANDROID_HOME, ANDROID_SDK_ROOT, or pass --ndk .', + ); +} + +String? _parseNdkPath(List args) { + String? ndkPath; + for (var index = 0; index < args.length; index++) { + final arg = args[index]; + if (arg == '--ndk') { + if (index + 1 >= args.length) { + _usage(); + } + ndkPath = args[++index]; + } else if (arg.startsWith('--ndk=')) { + ndkPath = arg.substring('--ndk='.length); + } else { + _usage(); + } + } + return ndkPath; +} + +Directory _findLlvmToolchain(Directory ndk) { + final prebuilt = Directory( + _join(_join(_join(ndk.path, 'toolchains'), 'llvm'), 'prebuilt'), + ); + if (!prebuilt.existsSync()) { + _fail('NDK LLVM prebuilt directory not found: ${prebuilt.path}'); + } + + final candidates = + prebuilt + .listSync() + .whereType() + .where( + (directory) => + _tool(directory, 'llvm-ar').existsSync() && + _tool(directory, _androidArm64Clang).existsSync(), + ) + .toList() + ..sort((a, b) => a.path.compareTo(b.path)); + if (candidates.isEmpty) { + _fail( + 'No NDK LLVM toolchain under ${prebuilt.path} contains llvm-ar and ' + '$_androidArm64Clang', + ); + } + + return candidates.first; +} + +File _tool(Directory toolchain, String name) { + final bin = Directory(_join(toolchain.path, 'bin')); + final tool = File(_join(bin.path, name)); + if (tool.existsSync() || !Platform.isWindows) { + return tool; + } + + final cmdTool = File(_join(bin.path, '$name.cmd')); + if (cmdTool.existsSync()) { + return cmdTool; + } + + return File(_join(bin.path, '$name.exe')); +} + +String _join(String parent, String child) { + final separator = Platform.pathSeparator; + return parent.endsWith(separator) + ? '$parent$child' + : '$parent$separator$child'; +} + +Never _usage() { + _fail('Usage: dart scripts/check_android_hook_alignment.dart [--ndk ]'); +} + +Never _fail(String message) { + stderr.writeln(message); + exit(1); +} From 6c06f6f39bfba66ea94977de83d0fe3e5e275c50 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 22 Jun 2026 11:13:14 -0500 Subject: [PATCH 2/4] feat: support aab native library alignment checks --- scripts/check_android_elf_alignment.dart | 99 +++++++++++++++++------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/scripts/check_android_elf_alignment.dart b/scripts/check_android_elf_alignment.dart index d0ef62d..e86993c 100644 --- a/scripts/check_android_elf_alignment.dart +++ b/scripts/check_android_elf_alignment.dart @@ -8,7 +8,8 @@ const _ptLoad = 1; Future main(List args) async { if (args.isEmpty) { _fail( - 'Usage: dart scripts/check_android_elf_alignment.dart [...]', + 'Usage: dart scripts/check_android_elf_alignment.dart ' + ' [...]', ); } @@ -21,32 +22,30 @@ Future main(List args) async { if (_isElf(input)) { _checkLibrary(input, input.path); } else { - await _checkApk(input); + await _checkArchive(input); } } } -Future _checkApk(File apk) async { +Future _checkArchive(File archive) async { final tempDir = await Directory.systemTemp.createTemp( 'bdk_dart_elf_alignment_', ); try { - await _extractLibraries(apk, tempDir); - final libraries = - tempDir - .listSync(recursive: true) - .whereType() - .where((file) => file.uri.pathSegments.last == _libraryName) - .toList() - ..sort((a, b) => a.path.compareTo(b.path)); - - if (libraries.isEmpty) { - _fail('No $_libraryName entries found in ${apk.path}'); + final entries = await _findNativeLibraryEntries(archive); + if (entries.isEmpty) { + _fail('No $_libraryName entries found in ${archive.path}'); } - for (final library in libraries) { - _checkLibrary(library, _relativePath(library, tempDir)); + await _extractLibraries(archive, tempDir, entries); + + for (final entry in entries) { + final library = _extractedEntry(tempDir, entry); + if (!library.existsSync()) { + _fail('Extracted library not found: $entry'); + } + _checkLibrary(library, entry); } } finally { await tempDir.delete(recursive: true); @@ -76,23 +75,69 @@ void _checkLibrary(File library, String label) { stdout.writeln('OK $label minLOADalign=${_hex(minimumAlignment)}'); } -Future _extractLibraries(File apk, Directory destination) async { +Future> _findNativeLibraryEntries(File archive) async { + final result = await Process.run('unzip', ['-Z', '-1', archive.path]); + if (result.exitCode != 0) { + _fail( + 'Failed to list $_libraryName entries in ${archive.path}.\n' + '${result.stderr}', + ); + } + + final entries = + (result.stdout as String) + .split('\n') + .where(_isTargetLibraryEntry) + .toList() + ..sort(); + return entries; +} + +bool _isTargetLibraryEntry(String entry) { + final segments = entry.split('/'); + if (segments.any((segment) => segment.isEmpty || segment == '.')) { + return false; + } + if (segments.any((segment) => segment == '..')) { + return false; + } + if (segments.last != _libraryName) { + return false; + } + + final isApkLibrary = segments.length == 3 && segments[0] == 'lib'; + final isAabLibrary = segments.length == 4 && segments[1] == 'lib'; + return isApkLibrary || isAabLibrary; +} + +Future _extractLibraries( + File archive, + Directory destination, + List entries, +) async { final result = await Process.run('unzip', [ '-q', - apk.path, - 'lib/*/$_libraryName', + archive.path, + ...entries, '-d', destination.path, ]); if (result.exitCode != 0) { _fail( - 'Failed to extract $_libraryName from ${apk.path}.\n' + 'Failed to extract $_libraryName from ${archive.path}.\n' '${result.stderr}', ); } } +File _extractedEntry(Directory root, String entry) { + final path = entry + .split('/') + .fold(root.path, (parent, child) => _join(parent, child)); + return File(path); +} + bool _isElf(File file) { final reader = file.openSync(); try { @@ -158,15 +203,15 @@ List _readLoadAlignments(File elfFile) { return alignments; } -String _relativePath(File file, Directory root) { - final prefix = '${root.path}${Platform.pathSeparator}'; - return file.path.startsWith(prefix) - ? file.path.substring(prefix.length) - : file.path; -} - String _hex(int value) => '0x${value.toRadixString(16)}'; +String _join(String parent, String child) { + final separator = Platform.pathSeparator; + return parent.endsWith(separator) + ? '$parent$child' + : '$parent$separator$child'; +} + Never _fail(String message) { stderr.writeln(message); exit(1); From 8c2154fd529353ac138d374eb2c07013ce19e9c6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 22 Jun 2026 11:19:28 -0500 Subject: [PATCH 3/4] fix: verify android alignment for x86_64 --- .github/workflows/ci.yml | 44 +++++++++++++++---- scripts/check_android_hook_alignment.dart | 53 +++++++++++++++++------ 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ade4b4..bd3ce2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,16 @@ jobs: run: flutter build apk --debug --target-platform android-arm64 android-alignment: + name: android-alignment (${{ matrix.rust_target }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - rust_target: aarch64-linux-android + dart_arch: arm64 + - rust_target: x86_64-linux-android + dart_arch: x64 steps: - name: Checkout @@ -151,32 +160,49 @@ jobs: - name: Build Android native library with NDK 27 run: | - rustup toolchain install 1.85.1 --profile minimal --target aarch64-linux-android + rustup toolchain install 1.85.1 --profile minimal --target "${{ matrix.rust_target }}" NDK="$ANDROID_HOME/ndk/27.1.12297006" TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/linux-x86_64" export ANDROID_NDK_HOME="$NDK" export ANDROID_NDK_ROOT="$NDK" - export AR_aarch64_linux_android="$TOOLCHAIN/bin/llvm-ar" - export CC_aarch64_linux_android="$TOOLCHAIN/bin/aarch64-linux-android35-clang" - export CXX_aarch64_linux_android="$TOOLCHAIN/bin/aarch64-linux-android35-clang++" - export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/aarch64-linux-android35-clang" - export BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android="--sysroot=$TOOLCHAIN/sysroot -I$TOOLCHAIN/sysroot/usr/include/aarch64-linux-android" + + case "${{ matrix.rust_target }}" in + aarch64-linux-android) + export AR_aarch64_linux_android="$TOOLCHAIN/bin/llvm-ar" + export CC_aarch64_linux_android="$TOOLCHAIN/bin/aarch64-linux-android35-clang" + export CXX_aarch64_linux_android="$TOOLCHAIN/bin/aarch64-linux-android35-clang++" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/aarch64-linux-android35-clang" + export BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android="--sysroot=$TOOLCHAIN/sysroot -I$TOOLCHAIN/sysroot/usr/include/aarch64-linux-android" + ;; + x86_64-linux-android) + export AR_x86_64_linux_android="$TOOLCHAIN/bin/llvm-ar" + export CC_x86_64_linux_android="$TOOLCHAIN/bin/x86_64-linux-android35-clang" + export CXX_x86_64_linux_android="$TOOLCHAIN/bin/x86_64-linux-android35-clang++" + export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/x86_64-linux-android35-clang" + export BINDGEN_EXTRA_CLANG_ARGS_x86_64_linux_android="--sysroot=$TOOLCHAIN/sysroot -I$TOOLCHAIN/sysroot/usr/include/x86_64-linux-android" + ;; + *) + echo "Unsupported Android target: ${{ matrix.rust_target }}" + exit 1 + ;; + esac + rustup run 1.85.1 cargo build \ --release \ --manifest-path native/Cargo.toml \ --package bdk_dart_ffi \ - --target aarch64-linux-android \ + --target "${{ matrix.rust_target }}" \ --target-dir /tmp/bdk-dart-android-target \ --config native/.cargo/config.toml - name: Verify NDK 27 native library alignment - run: dart scripts/check_android_elf_alignment.dart /tmp/bdk-dart-android-target/aarch64-linux-android/release/libbdk_dart_ffi.so + run: dart scripts/check_android_elf_alignment.dart /tmp/bdk-dart-android-target/${{ matrix.rust_target }}/release/libbdk_dart_ffi.so - name: Verify NDK 27 hook alignment run: | export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.1.12297006" export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" - dart scripts/check_android_hook_alignment.dart + dart scripts/check_android_hook_alignment.dart --arch "${{ matrix.dart_arch }}" ios-smoke: runs-on: macos-latest diff --git a/scripts/check_android_hook_alignment.dart b/scripts/check_android_hook_alignment.dart index fd96e9a..8b9a99d 100644 --- a/scripts/check_android_hook_alignment.dart +++ b/scripts/check_android_hook_alignment.dart @@ -7,18 +7,29 @@ import 'check_android_elf_alignment.dart' as elf_alignment; const _androidNdkVersion = '27.1.12297006'; const _androidApi = 35; -const _androidArm64Clang = 'aarch64-linux-android35-clang'; const _assetId = 'package:bdk_dart/uniffi:bdk_dart_ffi'; +const _defaultArchitectureName = 'arm64'; +const _targetArchitectures = { + 'arm64': Architecture.arm64, + 'x64': Architecture.x64, +}; +const _androidClangs = { + 'arm64': 'aarch64-linux-android35-clang', + 'x64': 'x86_64-linux-android35-clang', +}; Future main(List args) async { - final ndk = _findNdk(args); - final toolchain = _findLlvmToolchain(ndk); - final clang = _tool(toolchain, _androidArm64Clang); + final options = _parseOptions(args); + final targetArchitecture = _targetArchitectures[options.architectureName]!; + final androidClang = _androidClangs[options.architectureName]!; + final ndk = _findNdk(options.ndkPath); + final toolchain = _findLlvmToolchain(ndk, androidClang); + final clang = _tool(toolchain, androidClang); await testCodeBuildHook( mainMethod: build_hook.main, targetOS: OS.android, - targetArchitecture: Architecture.arm64, + targetArchitecture: targetArchitecture, targetAndroidNdkApi: _androidApi, cCompiler: CCompilerConfig( archiver: _tool(toolchain, 'llvm-ar').uri, @@ -52,8 +63,7 @@ Future main(List args) async { ); } -Directory _findNdk(List args) { - final explicitNdkPath = _parseNdkPath(args); +Directory _findNdk(String? explicitNdkPath) { final androidHome = Platform.environment['ANDROID_HOME']; final androidSdkRoot = Platform.environment['ANDROID_SDK_ROOT']; final candidates = [ @@ -79,8 +89,10 @@ Directory _findNdk(List args) { ); } -String? _parseNdkPath(List args) { +({String architectureName, String? ndkPath}) _parseOptions(List args) { + var architectureName = _defaultArchitectureName; String? ndkPath; + for (var index = 0; index < args.length; index++) { final arg = args[index]; if (arg == '--ndk') { @@ -90,14 +102,26 @@ String? _parseNdkPath(List args) { ndkPath = args[++index]; } else if (arg.startsWith('--ndk=')) { ndkPath = arg.substring('--ndk='.length); + } else if (arg == '--arch') { + if (index + 1 >= args.length) { + _usage(); + } + architectureName = args[++index]; + } else if (arg.startsWith('--arch=')) { + architectureName = arg.substring('--arch='.length); } else { _usage(); } } - return ndkPath; + + if (!_targetArchitectures.containsKey(architectureName)) { + _usage(); + } + + return (architectureName: architectureName, ndkPath: ndkPath); } -Directory _findLlvmToolchain(Directory ndk) { +Directory _findLlvmToolchain(Directory ndk, String androidClang) { final prebuilt = Directory( _join(_join(_join(ndk.path, 'toolchains'), 'llvm'), 'prebuilt'), ); @@ -112,14 +136,14 @@ Directory _findLlvmToolchain(Directory ndk) { .where( (directory) => _tool(directory, 'llvm-ar').existsSync() && - _tool(directory, _androidArm64Clang).existsSync(), + _tool(directory, androidClang).existsSync(), ) .toList() ..sort((a, b) => a.path.compareTo(b.path)); if (candidates.isEmpty) { _fail( 'No NDK LLVM toolchain under ${prebuilt.path} contains llvm-ar and ' - '$_androidArm64Clang', + '$androidClang', ); } @@ -149,7 +173,10 @@ String _join(String parent, String child) { } Never _usage() { - _fail('Usage: dart scripts/check_android_hook_alignment.dart [--ndk ]'); + _fail( + 'Usage: dart scripts/check_android_hook_alignment.dart ' + '[--arch ] [--ndk ]', + ); } Never _fail(String message) { From 7d8522968c34d3824619960394cd2c9a3ca6ddfd Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 22 Jun 2026 11:27:31 -0500 Subject: [PATCH 4/4] fix: verify apk elf alignment in android smoke --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd3ce2f..4fd1ff5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,9 @@ jobs: working-directory: bdk_demo run: flutter build apk --debug --target-platform android-arm64 + - name: Verify APK ELF alignment + run: dart scripts/check_android_elf_alignment.dart bdk_demo/build/app/outputs/flutter-apk/app-debug.apk + android-alignment: name: android-alignment (${{ matrix.rust_target }}) runs-on: ubuntu-latest