From ebb63fde19b046da7300f55849942865f0cc548e Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 25 Jan 2026 16:39:38 +0100 Subject: [PATCH 1/6] cbuilder: add macos->linux cross compilation tests Add tests for cross compiling from macos to linux (arm, arm64, x86, x64). The macos->linux tests will explicitly specify apple clang as the c compiler toolchain. The default logic will only attempt apple clang for macos targets, so it won't find a compatible toolchain otherwise. Furthermore a linux target triple is specified in the CBuilder flags. (for example `--target=arm-linux-gnueabihf`) --- .../cbuilder_cross_macos_host_test.dart | 79 ++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart index 0029b642b4..2086835fa7 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart @@ -11,6 +11,8 @@ import 'dart:io'; import 'package:code_assets/code_assets.dart'; import 'package:hooks/hooks.dart'; import 'package:native_toolchain_c/native_toolchain_c.dart'; +import 'package:native_toolchain_c/src/native_toolchain/apple_clang.dart'; +import 'package:native_toolchain_c/src/tool/tool_resolver.dart'; import 'package:native_toolchain_c/src/utils/run_process.dart'; import 'package:test/test.dart'; @@ -22,12 +24,30 @@ void main() { return; } - const targets = [Architecture.arm64, Architecture.x64]; + const targets = [ + (OS.macOS, Architecture.arm64), + (OS.macOS, Architecture.x64), + (OS.linux, Architecture.arm), + (OS.linux, Architecture.arm64), + (OS.linux, Architecture.ia32), + (OS.linux, Architecture.x64), + + // Not supported by Apple Clang right now. + // (OS.linux, Architecture.riscv32), + // (OS.linux, Architecture.riscv64), + ]; // Dont include 'mach-o' or 'Mach-O', different spelling is used. const objdumpFileFormat = { - Architecture.arm64: 'arm64', - Architecture.x64: '64-bit x86-64', + (OS.macOS, Architecture.arm64): 'arm64', + (OS.macOS, Architecture.x64): '64-bit x86-64', + (OS.linux, Architecture.arm): 'elf32-littlearm', + (OS.linux, Architecture.arm64): 'elf64-littleaarch64', + (OS.linux, Architecture.ia32): 'elf32-i386', + (OS.linux, Architecture.x64): 'elf64-x86-64', + + (OS.linux, Architecture.riscv32): 'elf32-riscv32', + (OS.linux, Architecture.riscv64): 'elf64-riscv64', }; const optimizationLevels = OptimizationLevel.values; @@ -35,14 +55,17 @@ void main() { for (final language in [Language.c, Language.objectiveC]) { for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) { - for (final target in targets) { + for (final (os, arch) in targets) { + // Don't build Objective-C targets for linux. + if (language == Language.objectiveC && os == OS.linux) continue; + // Cycle through all optimization levels. final optimizationLevel = optimizationLevels[selectOptimizationLevel]; selectOptimizationLevel = (selectOptimizationLevel + 1) % optimizationLevels.length; test( - 'CBuilder $linkMode $language library $target $optimizationLevel', + 'CBuilder $linkMode $language library $os $arch $optimizationLevel', () async { final tempUri = await tempDirForTest(); final tempUri2 = await tempDirForTest(); @@ -57,6 +80,15 @@ void main() { }; const name = 'add'; + // When cross-compiling from MacOS, explicitly specify apple clang. + // + // The default tool-finding does not support macos cross compiling right now. + var chosenCCompiler = cCompiler; + if (os == OS.linux) { + // still respect the CI-provided compiler + chosenCCompiler ??= await resolveAppleToolchain(); + } + final buildInputBuilder = BuildInputBuilder() ..setupShared( packageName: name, @@ -67,12 +99,12 @@ void main() { ..config.setupBuild(linkingEnabled: false) ..addExtension( CodeAssetExtension( - targetOS: OS.macOS, - targetArchitecture: target, + targetOS: os, + targetArchitecture: arch, linkModePreference: linkMode == DynamicLoadingBundled() ? LinkModePreference.dynamic : LinkModePreference.static, - cCompiler: cCompiler, + cCompiler: chosenCCompiler, macOS: MacOSCodeConfig(targetVersion: defaultMacOSVersion), ), ); @@ -86,6 +118,20 @@ void main() { language: language, optimizationLevel: optimizationLevel, buildMode: BuildMode.release, + flags: [ + if (os == OS.linux) + switch (arch) { + Architecture.arm => '--target=arm-linux-gnueabihf', + Architecture.arm64 => '--target=aarch64-linux-gnu', + Architecture.ia32 => '--target=i686-linux-gnu', + Architecture.x64 => '--target=x86_64-linux-gnu', + Architecture.riscv32 => '--target=riscv32-linux-gnu', + Architecture.riscv64 => '--target=riscv64-linux-gnu', + _ => throw UnsupportedError( + 'Unexpected linux architecture: $arch', + ), + }, + ], ); await cbuilder.run( input: buildInput, @@ -105,7 +151,7 @@ void main() { final machine = result.stdout .split('\n') .firstWhere((e) => e.contains('file format')); - expect(machine, contains(objdumpFileFormat[target])); + expect(machine, contains(objdumpFileFormat[(os, arch)])); }, ); } @@ -193,3 +239,18 @@ Future buildLib( ); return libUri; } + +Future resolveAppleToolchain() async { + // (still respect the CI provided compiler) + final context = ToolResolvingContext(logger: logger); + + final resolvedClang = await appleClang.defaultResolver!.resolve(context); + final resolvedAr = await appleAr.defaultResolver!.resolve(context); + final resolvedLd = await appleLd.defaultResolver!.resolve(context); + + return CCompilerConfig( + compiler: resolvedClang.first.uri, + archiver: resolvedAr.first.uri, + linker: resolvedLd.first.uri, + ); +} From 2042b18290d3f6355c54af2e4a631fe504ad4c68 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 25 Jan 2026 17:34:23 +0100 Subject: [PATCH 2/6] cbuilder: fix linking ld.lld (installed via homebrew install lld) must be used for linking, as the apple ld can't link for linux. -nostartfiles and -nostdlib must be specified as we don't have a sysroot / c-runtime for linux. The resulting binary isn't really useful, but at least proves that compilation works. --- .github/workflows/native.yaml | 4 ++++ .../test/cbuilder/cbuilder_cross_macos_host_test.dart | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index d28c3ff7b6..f51c9c97dc 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -69,6 +69,10 @@ jobs: run: sudo apt-get update && sudo apt-get install clang-15 gcc-i686-linux-gnu gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-riscv64-linux-gnu if: ${{ matrix.os == 'ubuntu' }} + - name: Install native toolchains + run: brew install lld + if: ${{ matrix.os == 'macos' }} + - run: dart pub get - name: Run pub get, analysis, formatting, generators, tests, and examples. diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart index 2086835fa7..18b915dde1 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart @@ -131,6 +131,13 @@ void main() { 'Unexpected linux architecture: $arch', ), }, + // Only homebrew lld can link for linux, and we don't have a sysroot + // so we can't use stdlibs / C-runtime files. + if (os == OS.linux) ...[ + '--ld-path=ld.lld', + '-nostartfiles', + '-nostdlib', + ], ], ); await cbuilder.run( @@ -140,7 +147,7 @@ void main() { ); final libUri = buildInput.outputDirectory.resolve( - OS.macOS.libraryFileName(name, linkMode), + os.libraryFileName(name, linkMode), ); final result = await runProcess( executable: Uri.file('objdump'), From c1ef08042477411aec1e7f42184d709b669a28bc Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 25 Jan 2026 17:35:41 +0100 Subject: [PATCH 3/6] cbuilder: fix unportable linker arg -Wl,...=x is technically a GNU extension and not supported by Apple Clang. The more portable variant is -Wl,...,x. --- pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart index b7f363daca..c3272cbc00 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -339,7 +339,7 @@ class RunCBuilder { // During bundling code assets are all placed in the same directory. // Setting this rpath allows the binary to find other code assets // it is linked against. - '-Wl,-rpath=\$ORIGIN', + '-Wl,-rpath,\$ORIGIN', for (final directory in libraryDirectories) '-L${directory.toFilePath()}', for (final library in libraries) '-l$library', From c17243793dfd9f1186ec1ceca7bababe1375d9f6 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 25 Jan 2026 17:50:35 +0100 Subject: [PATCH 4/6] update changelog --- pkgs/native_toolchain_c/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index d9e919221b..5f3b5fdb62 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -4,6 +4,7 @@ - On iOS and macOS, use the `-encryptable` linker flag. This resolves an [issue](https://github.com/dart-lang/native/issues/2973) with app store rejections. +- Fix unportable link arg when cross-compiling from MacOS. ## 0.17.4 From 075cb158698d2905ff55460f5f9108efd288dad2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 26 Jan 2026 18:34:05 +0100 Subject: [PATCH 5/6] fix long doc comments --- .../test/cbuilder/cbuilder_cross_macos_host_test.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart index 18b915dde1..b4e7d05f6d 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_macos_host_test.dart @@ -82,7 +82,8 @@ void main() { // When cross-compiling from MacOS, explicitly specify apple clang. // - // The default tool-finding does not support macos cross compiling right now. + // The default tool-finding does not support macos cross compiling + // right now. var chosenCCompiler = cCompiler; if (os == OS.linux) { // still respect the CI-provided compiler @@ -131,8 +132,8 @@ void main() { 'Unexpected linux architecture: $arch', ), }, - // Only homebrew lld can link for linux, and we don't have a sysroot - // so we can't use stdlibs / C-runtime files. + // Only homebrew lld can link for linux, and we don't have a + // sysroot so we can't use stdlibs / C-runtime files. if (os == OS.linux) ...[ '--ld-path=ld.lld', '-nostartfiles', From 99e58f21a1e6ff24fbd8f019d31f497448ca3526 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 26 Jan 2026 20:07:23 +0100 Subject: [PATCH 6/6] don't resolve lld symlinks `lld` can only be invoked with `ld.lld` or similar. --- pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart b/pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart index ffd451e9d1..8221b55f5d 100644 --- a/pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart +++ b/pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart @@ -82,7 +82,7 @@ class PathToolResolver extends ToolResolver { if (process.exitCode == 0) { final file = File(LineSplitter.split(process.stdout).first); final uri = File(await file.resolveSymbolicLinks()).uri; - if (uri.pathSegments.last == 'llvm') { + if (uri.pathSegments.last case 'llvm' || 'lld') { // https://github.com/dart-lang/native/issues/136 return file.uri; }