diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index d18d9e67d..9c175ad70 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -197,7 +197,7 @@ class CBuilder extends CTool implements Builder { cppLinkStdLib: cppLinkStdLib, optimizationLevel: optimizationLevel, ); - await task.run(); + await task.runCompiler(); if (assetName != null) { for (final route in routing) { diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart index f4217d197..2025c1917 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart @@ -14,7 +14,7 @@ import 'linker_options.dart'; import 'linkmode.dart'; import 'optimization_level.dart'; import 'output_type.dart'; -import 'run_cbuilder.dart'; +import 'run_clinker.dart'; /// Specification for linking an artifact with a C linker. //TODO(mosuem): This is currently only implemented for linux. @@ -43,6 +43,10 @@ class CLinker extends CTool implements Linker { super.optimizationLevel = OptimizationLevel.o3, }) : super(type: OutputType.library); + //TODO(mosuem): Remove this field once all OSs are supported. + @visibleForTesting + static const supportedLinkingOSs = [OS.linux, OS.macOS]; + /// Runs the C Linker with on this C build spec. /// /// Completes with an error if the linking fails. @@ -52,9 +56,10 @@ class CLinker extends CTool implements Linker { required LinkOutputBuilder output, required Logger? logger, }) async { - if (OS.current != OS.linux || input.config.code.targetOS != OS.linux) { + if (!supportedLinkingOSs.contains(OS.current) || + !supportedLinkingOSs.contains(input.config.code.targetOS)) { throw UnsupportedError( - 'Currently, only linux is supported for this ' + 'Currently, only $supportedLinkingOSs are supported for this ' 'feature. See also https://github.com/dart-lang/native/issues/1376', ); } @@ -79,7 +84,7 @@ class CLinker extends CTool implements Linker { for (final directory in this.libraryDirectories) outDir.resolveUri(Uri.file(directory)), ]; - final task = RunCBuilder( + final task = RunCLinker( input: input, codeConfig: input.config.code, linkerOptions: linkerOptions, @@ -101,7 +106,7 @@ class CLinker extends CTool implements Linker { cppLinkStdLib: cppLinkStdLib, optimizationLevel: optimizationLevel, ); - await task.run(); + await task.runLinker(); if (assetName != null) { output.assets.code.add( diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart index cd2e7aed0..5d4529f71 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart @@ -4,6 +4,8 @@ import 'dart:io'; +import 'package:native_assets_cli/code_assets.dart'; + import '../native_toolchain/tool_likeness.dart'; import '../tool/tool.dart'; @@ -15,7 +17,7 @@ import '../tool/tool.dart'; class LinkerOptions { /// The flags to be passed to the linker. As they depend on the linker being /// invoked, the actual usage is via the [postSourcesFlags] method. - final List _linkerFlags; + final List linkerFlags; /// Enable garbage collection of unused input sections. /// @@ -31,16 +33,22 @@ class LinkerOptions { /// /// This is achieved by setting the `whole-archive` flag before passing the /// sources, and the `no-whole-archive` flag after. - final bool _wholeArchiveSandwich; + final bool includeAllSymbols; + + final bool linkStandardLibraries; + + final bool stripDebug; /// Create linking options manually for fine-grained control. LinkerOptions.manual({ List? flags, bool? gcSections, this.linkerScript, - }) : _linkerFlags = flags ?? [], - gcSections = gcSections ?? true, - _wholeArchiveSandwich = false; + this.linkStandardLibraries = true, + this.includeAllSymbols = true, + this.stripDebug = true, + }) : linkerFlags = flags ?? [], + gcSections = gcSections ?? true; /// Create linking options to tree-shake symbols from the input files. /// @@ -48,17 +56,21 @@ class LinkerOptions { LinkerOptions.treeshake({ Iterable? flags, required Iterable? symbols, - }) : _linkerFlags = + this.linkStandardLibraries = true, + this.stripDebug = true, + }) : linkerFlags = [ ...flags ?? [], - '--strip-debug', - if (symbols != null) ...symbols.expand((e) => ['-u', e]), + if (symbols != null) + ...symbols.expand( + (e) => ['-u', (OS.current == OS.macOS ? '_' : '') + e], + ), ].toList(), gcSections = true, - _wholeArchiveSandwich = symbols == null, + includeAllSymbols = symbols == null, linkerScript = _createLinkerScript(symbols); - Iterable _toLinkerSyntax(Tool linker, List flagList) { + Iterable toLinkerSyntax(Tool linker, List flagList) { if (linker.isClangLike) { return flagList.map((e) => '-Wl,$e'); } else if (linker.isLdLike) { @@ -84,40 +96,3 @@ class LinkerOptions { return symbolsFileUri; } } - -extension LinkerOptionsExt on LinkerOptions { - /// The flags for the specified [linker], which are inserted _before_ the - /// sources. - /// - /// This is mainly used for the whole-archive ... no-whole-archive - /// trick, which includes all symbols when linking object files. - /// - /// Throws if the [linker] is not supported. - Iterable preSourcesFlags(Tool linker, Iterable sourceFiles) => - _toLinkerSyntax( - linker, - sourceFiles.any((source) => source.endsWith('.a')) || - _wholeArchiveSandwich - ? ['--whole-archive'] - : [], - ); - - /// The flags for the specified [linker], which are inserted _after_ the - /// sources. - /// - /// This is mainly used for the whole-archive ... no-whole-archive - /// trick, which includes all symbols when linking object files. - /// - /// Throws if the [linker] is not supported. - Iterable postSourcesFlags( - Tool linker, - Iterable sourceFiles, - ) => _toLinkerSyntax(linker, [ - ..._linkerFlags, - if (gcSections) '--gc-sections', - if (linkerScript != null) '--version-script=${linkerScript!.toFilePath()}', - if (sourceFiles.any((source) => source.endsWith('.a')) || - _wholeArchiveSandwich) - '--no-whole-archive', - ]); -} 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 2675856c6..a63492909 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -117,11 +117,12 @@ class RunCBuilder { Uri androidSysroot(ToolInstance compiler) => compiler.uri.resolve('../sysroot/'); - Future run() async { + Future runCompiler() async { + assert(linkerOptions == null); final toolInstance_ = linkerOptions != null ? await linker() : await compiler(); final tool = toolInstance_.tool; - if (tool.isClangLike || tool.isLdLike) { + if (tool.isClangLike) { await runClangLike(tool: toolInstance_); return; } else if (tool == cl) { @@ -141,12 +142,8 @@ class RunCBuilder { archiver_ = await archiver(); } - final IOSSdk? targetIosSdk; - if (codeConfig.targetOS == OS.iOS) { - targetIosSdk = codeConfig.iOS.targetSdk; - } else { - targetIosSdk = null; - } + final targetIosSdk = + codeConfig.targetOS == OS.iOS ? codeConfig.iOS.targetSdk : null; // The Android Gradle plugin does not honor API level 19 and 20 when // invoking clang. Mimic that behavior here. @@ -295,7 +292,6 @@ class RunCBuilder { ], if (optimizationLevel != OptimizationLevel.unspecified) optimizationLevel.clangFlag(), - ...linkerOptions?.preSourcesFlags(toolInstance.tool, sourceFiles) ?? [], // Support Android 15 page size by default, can be overridden by // passing [flags]. if (codeConfig.targetOS == OS.android) '-Wl,-z,max-page-size=16384', @@ -321,17 +317,12 @@ class RunCBuilder { '-o', outFile!.toFilePath(), ], - ...linkerOptions?.postSourcesFlags(toolInstance.tool, sourceFiles) ?? - [], if (executable != null || dynamicLibrary != null) ...[ if (codeConfig.targetOS case OS.android || OS.linux) // 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. - if (linkerOptions != null) - '-rpath=\$ORIGIN' - else - '-Wl,-rpath=\$ORIGIN', + '-Wl,-rpath=\$ORIGIN', for (final directory in libraryDirectories) '-L${directory.toFilePath()}', for (final library in libraries) '-l$library', @@ -419,6 +410,11 @@ class RunCBuilder { Architecture.x64: 'x86_64-apple-darwin', }; + static const appleLdArchs = { + Architecture.arm64: 'arm64', + Architecture.x64: 'x86_64', + }; + static const appleClangIosTargetFlags = { Architecture.arm64: { IOSSdk.iPhoneOS: 'arm64-apple-ios', diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/run_clinker.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/run_clinker.dart new file mode 100644 index 000000000..6eadf619e --- /dev/null +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_clinker.dart @@ -0,0 +1,301 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/code_assets.dart'; + +import '../native_toolchain/tool_likeness.dart'; +import '../native_toolchain/xcode.dart'; +import '../tool/tool_instance.dart'; +import '../utils/run_process.dart'; +import 'compiler_resolver.dart'; +import 'language.dart'; +import 'linker_options.dart'; +import 'optimization_level.dart'; + +class RunCLinker { + /// The options are for linking only, so this will be non-null iff a linker + /// should be run. + final LinkerOptions? linkerOptions; + final HookInput input; + final CodeConfig codeConfig; + final Logger? logger; + final List sources; + final List includes; + final List forcedIncludes; + final List frameworks; + final List libraries; + final List libraryDirectories; + final Uri? executable; + final Uri? dynamicLibrary; + final Uri? staticLibrary; + final Uri outDir; + + /// The install name of the [dynamicLibrary]. + /// + /// Can be inspected with `otool -D `. + /// + /// Can be modified with `install_name_tool`. + final Uri? installName; + + final List flags; + final Map defines; + final bool? pic; + final String? std; + final Language language; + final String? cppLinkStdLib; + final OptimizationLevel optimizationLevel; + + RunCLinker({ + required this.input, + required this.codeConfig, + this.linkerOptions, + this.logger, + this.sources = const [], + this.includes = const [], + this.forcedIncludes = const [], + required this.frameworks, + this.libraries = const [], + this.libraryDirectories = const [], + this.executable, + this.dynamicLibrary, + this.staticLibrary, + this.installName, + this.flags = const [], + this.defines = const {}, + this.pic, + this.std, + this.language = Language.c, + this.cppLinkStdLib, + required this.optimizationLevel, + }) : outDir = input.outputDirectory, + assert( + [executable, dynamicLibrary, staticLibrary].whereType().length == + 1, + ) { + if (codeConfig.targetOS == OS.windows && cppLinkStdLib != null) { + throw ArgumentError.value( + cppLinkStdLib, + 'cppLinkStdLib', + 'is not supported when targeting Windows', + ); + } + } + + late final _resolver = CompilerResolver( + codeConfig: codeConfig, + logger: logger, + ); + + Future compiler() async => await _resolver.resolveCompiler(); + + Future archiver() async => (await _resolver.resolveArchiver()).uri; + + Future linker() async => await _resolver.resolveLinker(); + + Future iosSdk(IOSSdk iosSdk, {required Logger? logger}) async { + if (iosSdk == IOSSdk.iPhoneOS) { + return (await iPhoneOSSdk.defaultResolver!.resolve( + logger: logger, + )).where((i) => i.tool == iPhoneOSSdk).first.uri; + } + assert(iosSdk == IOSSdk.iPhoneSimulator); + return (await iPhoneSimulatorSdk.defaultResolver!.resolve( + logger: logger, + )).where((i) => i.tool == iPhoneSimulatorSdk).first.uri; + } + + Future macosSdk({required Logger? logger}) async => + (await macosxSdk.defaultResolver!.resolve( + logger: logger, + )).where((i) => i.tool == macosxSdk).first.uri; + + Uri androidSysroot(ToolInstance compiler) => + compiler.uri.resolve('../sysroot/'); + + Future runLinker() async { + assert(linkerOptions != null); + final toolInstance_ = await linker(); + final tool = toolInstance_.tool; + if (tool.isClangLike || tool.isLdLike) { + await linkClangLike(tool: toolInstance_); + return; + } else { + throw UnimplementedError('This package does not know how to run $tool.'); + } + } + + Future linkClangLike({required ToolInstance tool}) async { + // Clang for Windows requires the MSVC Developer Environment. + final environment = await _resolver.resolveEnvironment(tool); + + assert(staticLibrary == null); + + // The Android Gradle plugin does not honor API level 19 and 20 when + // invoking clang. Mimic that behavior here. + // See https://github.com/dart-lang/native/issues/171. + final targetMacOSVersion = + codeConfig.targetOS == OS.macOS ? codeConfig.macOS.targetVersion : null; + + final Iterable sourceFiles = + sources.map((e) => e.toFilePath()).toList(); + final outFile = + dynamicLibrary != null ? outDir.resolveUri(dynamicLibrary!) : null; + + await runProcess( + executable: tool.uri, + environment: environment, + arguments: [ + if (installName != null) ...[ + '-install_name', + installName!.toFilePath(), + ], + if (targetMacOSVersion != null) ...[ + '-macos_version_min', + '$targetMacOSVersion', + ], + '-dylib', + ...['-arch', appleLdArchs[codeConfig.targetArchitecture]!], + if (pic != null) + if (tool.tool.isClangLike && codeConfig.targetOS != OS.windows) ...[ + if (pic!) ...[ + if (dynamicLibrary != null) '-fPIC', + if (executable != null) ...[ + // Generate position-independent code for executables. + '-fPIE', + // Tell the linker to generate a position-independent + // executable. + '-pie', + ], + ] else ...[ + // Disable generation of any kind of position-independent code. + '-fno-PIC', + '-fno-PIE', + // Tell the linker to generate a position-dependent executable. + if (executable != null) '-no-pie', + ], + ] else if (tool.tool.isLdLike) ...[ + if (pic!) ...[ + if (executable != null) '--pie', + ] else ...[ + if (executable != null) '--no-pie', + ], + ], + if (std != null) '-std=$std', + if (language == Language.cpp) ...[ + '-x', + 'c++', + '-l', + cppLinkStdLib ?? defaultCppLinkStdLib[codeConfig.targetOS]!, + ], + if (codeConfig.targetOS == OS.macOS) ...[ + '-syslibroot', + (await macosSdk(logger: logger)).toFilePath(), + ], + if (optimizationLevel != OptimizationLevel.unspecified) + optimizationLevel.clangFlag(), + ...linkerOptions?.toLinkerSyntax( + tool.tool, + (sourceFiles.any((source) => source.endsWith('.a')) || + linkerOptions!.includeAllSymbols) && + OS.current == OS.linux + ? ['--whole-archive'] + : [], + ) ?? + [], + // Support Android 15 page size by default, can be overridden by + // passing [flags]. + if (codeConfig.targetOS == OS.android) '-Wl,-z,max-page-size=16384', + ...flags, + for (final MapEntry(key: name, :value) in defines.entries) + if (value == null) '-D$name' else '-D$name=$value', + for (final include in includes) '-I${include.toFilePath()}', + for (final forcedInclude in forcedIncludes) + '-include${forcedInclude.toFilePath()}', + ...(linkerOptions != null + ? switch (OS.current) { + OS.linux => sourceFiles, + OS.macOS => sourceFiles.expand( + (source) => + source.endsWith('.a') + ? [ + if (linkerOptions!.includeAllSymbols) + linkerOptions!.toLinkerSyntax(tool.tool, [ + '-force_load', + ]).first, + source, + ] + : [source], + ), + OS() => sourceFiles, + } + : sourceFiles), + if (language == Language.objectiveC) ...[ + for (final framework in frameworks) ...['-framework', framework], + ], + if (executable != null) ...[ + '-o', + outDir.resolveUri(executable!).toFilePath(), + ] else if (dynamicLibrary != null) ...[ + '-o', + outFile!.toFilePath(), + ] else if (staticLibrary != null) ...[ + '-c', + '-o', + outFile!.toFilePath(), + ], + ...linkerOptions?.toLinkerSyntax(tool.tool, [ + ...[ + ...linkerOptions!.linkerFlags, + if (OS.current == OS.macOS) '-lSystem', + ], + if (linkerOptions!.stripDebug) + switch (OS.current) { + OS.linux => '--strip-debug', + OS.macOS => '-S', + OS() => throw UnimplementedError(), + }, + if (linkerOptions!.gcSections) + OS.current == OS.macOS ? '-dead_strip' : '--gc-sections', + if (linkerOptions!.linkerScript != null && OS.current == OS.linux) + '--version-script=${linkerOptions!.linkerScript!.toFilePath()}', + if ((sourceFiles.any((source) => source.endsWith('.a')) || + linkerOptions!.includeAllSymbols) && + OS.current == OS.linux) + '--no-whole-archive', + ]) ?? + [], + if (executable != null || dynamicLibrary != null) ...[ + if (codeConfig.targetOS case OS.android || OS.linux) + // 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. + if (linkerOptions != null) + '-rpath=\$ORIGIN' + else + '-Wl,-rpath=\$ORIGIN', + for (final directory in libraryDirectories) + '-L${directory.toFilePath()}', + for (final library in libraries) '-l$library', + ], + ], + logger: logger, + captureOutput: false, + throwOnUnexpectedExitCode: true, + ); + } + + static const appleLdArchs = { + Architecture.arm64: 'arm64', + Architecture.x64: 'x86_64', + }; + + static const defaultCppLinkStdLib = { + OS.android: 'c++_shared', + OS.fuchsia: 'c++', + OS.iOS: 'c++', + OS.linux: 'stdc++', + OS.macOS: 'c++', + }; +} diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart index da971a75d..935fe8f1a 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart @@ -19,14 +19,6 @@ void main() { Architecture.riscv64, ]; - const objdumpFileFormat = { - Architecture.arm: 'elf32-littlearm', - Architecture.arm64: 'elf64-littleaarch64', - Architecture.ia32: 'elf32-i386', - Architecture.x64: 'elf64-x86-64', - Architecture.riscv64: 'elf64-littleriscv', - }; - /// From https://docs.flutter.dev/reference/supported-platforms. const flutterAndroidNdkVersionLowestBestEffort = 19; @@ -60,21 +52,7 @@ void main() { linkMode, optimizationLevel: optimizationLevel, ); - if (Platform.isLinux) { - final machine = await readelfMachine(libUri.path); - expect(machine, contains(readElfMachine[target])); - } else if (Platform.isMacOS) { - final result = await runProcess( - executable: Uri.file('objdump'), - arguments: ['-T', libUri.path], - logger: logger, - ); - expect(result.exitCode, 0); - final machine = result.stdout - .split('\n') - .firstWhere((e) => e.contains('file format')); - expect(machine, contains(objdumpFileFormat[target])); - } + await checkArchitecture(libUri.path, target); if (linkMode == DynamicLoadingBundled()) { await expectPageSize(libUri, 16 * 1024); } @@ -183,3 +161,36 @@ Future buildLib( ); return libUri; } + +Future checkArchitecture(String libUri, Architecture target) async { + if (Platform.isLinux) { + final readElfMachine = switch (target) { + Architecture.arm => 'ARM', + Architecture.arm64 => 'AArch64', + Architecture.ia32 => 'Intel 80386', + Architecture.x64 => 'Advanced Micro Devices X86-64', + Architecture.riscv64 => 'RISC-V', + Architecture() => + throw UnimplementedError('No readelf output for $target yet.'), + }; + final machine = await readelfMachine(libUri); + expect(machine, contains(readElfMachine)); + } else if (Platform.isMacOS) { + final objdumpFileFormat = switch (target) { + Architecture.arm64 => 'mach-o arm64', + Architecture.x64 => 'mach-o 64-bit x86-64', + Architecture() => + throw UnimplementedError('No objdump output for $target yet.'), + }; + final result = await runProcess( + executable: Uri.file('objdump'), + arguments: ['-T', libUri], + logger: logger, + ); + expect(result.exitCode, 0); + final machine = result.stdout + .split('\n') + .firstWhere((e) => e.contains('file format')); + expect(machine, contains(objdumpFileFormat)); + } +} diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart index e5f438260..0796333f6 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -11,6 +11,7 @@ import 'package:native_toolchain_c/native_toolchain_c.dart'; import 'package:test/test.dart'; import '../helpers.dart'; +import 'cbuilder_cross_android_test.dart'; void main() { if (!Platform.isLinux) { @@ -84,8 +85,7 @@ void main() { final libUri = buildInput.outputDirectory.resolve( OS.linux.libraryFileName(name, linkMode), ); - final machine = await readelfMachine(libUri.path); - expect(machine, contains(readElfMachine[target])); + await checkArchitecture(libUri.path, target); }); } } diff --git a/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart b/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart index e4a7b29d9..ccc5f3065 100644 --- a/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart +++ b/pkgs/native_toolchain_c/test/clinker/build_testfiles.dart @@ -25,7 +25,7 @@ Future buildTestArchive( final logMessages = []; final logger = createCapturingLogger(logMessages); - assert(os == OS.linux); // Setup code input for other OSes. + assert(CLinker.supportedLinkingOSs.contains(os)); final buildInputBuilder = BuildInputBuilder() ..setupShared( @@ -42,6 +42,10 @@ Future buildTestArchive( targetArchitecture: architecture, linkModePreference: LinkModePreference.dynamic, cCompiler: cCompiler, + macOS: + os == OS.macOS + ? MacOSCodeConfig(targetVersion: defaultMacOSVersion) + : null, ), ); diff --git a/pkgs/native_toolchain_c/test/clinker/objects_test.dart b/pkgs/native_toolchain_c/test/clinker/objects_test.dart index d7e725d30..35fafd3a7 100644 --- a/pkgs/native_toolchain_c/test/clinker/objects_test.dart +++ b/pkgs/native_toolchain_c/test/clinker/objects_test.dart @@ -4,7 +4,7 @@ //TODO(mosuem): Enable for windows and mac. // See https://github.com/dart-lang/native/issues/1376. -@TestOn('linux') +@TestOn('linux || mac-os') library; import 'dart:io'; @@ -16,13 +16,13 @@ import '../helpers.dart'; import 'build_testfiles.dart'; Future main() async { - if (!Platform.isLinux) { + if (!(Platform.isLinux || Platform.isMacOS)) { // Avoid needing status files on Dart SDK CI. return; } final architecture = Architecture.current; - const os = OS.linux; + final os = OS.current; const name = 'mylibname'; test('link two objects', () async { @@ -47,6 +47,10 @@ Future main() async { targetArchitecture: architecture, linkModePreference: LinkModePreference.dynamic, cCompiler: cCompiler, + macOS: + os == OS.macOS + ? MacOSCodeConfig(targetVersion: defaultMacOSVersion) + : null, ), ); diff --git a/pkgs/native_toolchain_c/test/clinker/throws_test.dart b/pkgs/native_toolchain_c/test/clinker/throws_test.dart index da967bcb6..58e914525 100644 --- a/pkgs/native_toolchain_c/test/clinker/throws_test.dart +++ b/pkgs/native_toolchain_c/test/clinker/throws_test.dart @@ -10,49 +10,47 @@ import 'package:test/test.dart'; import '../helpers.dart'; Future main() async { - for (final os in OS.values) { - if (Platform.isLinux) { - // Is implemented. - continue; - } - - test('throws on some platforms', () async { - final tempUri = await tempDirForTest(); - final tempUri2 = await tempDirForTest(); - - final linkInputBuilder = - LinkInputBuilder() - ..setupShared( - packageName: 'testpackage', - packageRoot: tempUri, - outputFile: tempUri.resolve('output.json'), - outputDirectoryShared: tempUri2, - outputDirectory: tempUri, - ) - ..setupLink(assets: [], recordedUsesFile: null) - ..addExtension( - CodeAssetExtension( - targetOS: os, - targetArchitecture: Architecture.x64, - linkModePreference: LinkModePreference.dynamic, - cCompiler: cCompiler, - ), - ); - - final linkHookInput = LinkInput(linkInputBuilder.json); - - final cLinker = CLinker.library( - name: 'mylibname', - linkerOptions: LinkerOptions.manual(), - ); - await expectLater( - () => cLinker.run( - input: linkHookInput, - output: LinkOutputBuilder(), - logger: logger, - ), - throwsUnsupportedError, - ); - }); + if (Platform.isLinux || Platform.isMacOS) { + // Is implemented. + return; } + + test('throws on some platforms', () async { + final tempUri = await tempDirForTest(); + final tempUri2 = await tempDirForTest(); + + final linkInputBuilder = + LinkInputBuilder() + ..setupShared( + packageName: 'testpackage', + packageRoot: tempUri, + outputFile: tempUri.resolve('output.json'), + outputDirectoryShared: tempUri2, + outputDirectory: tempUri, + ) + ..setupLink(assets: [], recordedUsesFile: null) + ..addExtension( + CodeAssetExtension( + targetOS: OS.current, + targetArchitecture: Architecture.x64, + linkModePreference: LinkModePreference.dynamic, + cCompiler: cCompiler, + ), + ); + + final linkHookInput = LinkInput(linkInputBuilder.json); + + final cLinker = CLinker.library( + name: 'mylibname', + linkerOptions: LinkerOptions.manual(), + ); + await expectLater( + () => cLinker.run( + input: linkHookInput, + output: LinkOutputBuilder(), + logger: logger, + ), + throwsUnsupportedError, + ); + }); } diff --git a/pkgs/native_toolchain_c/test/clinker/treeshake_cross_test.dart b/pkgs/native_toolchain_c/test/clinker/treeshake_cross_test.dart index b3672ea47..86d60bdc8 100644 --- a/pkgs/native_toolchain_c/test/clinker/treeshake_cross_test.dart +++ b/pkgs/native_toolchain_c/test/clinker/treeshake_cross_test.dart @@ -4,7 +4,7 @@ //TODO(mosuem): Enable for windows and mac. // See https://github.com/dart-lang/native/issues/1376. -@TestOn('linux') +@TestOn('linux || mac-os') library; import 'dart:io'; @@ -15,18 +15,22 @@ import 'package:test/test.dart'; import 'treeshake_helper.dart'; Future main() async { - if (!Platform.isLinux) { + if (!(Platform.isLinux || Platform.isMacOS)) { // Avoid needing status files on Dart SDK CI. return; } - final architectures = [ - Architecture.arm, - Architecture.arm64, - Architecture.ia32, - Architecture.x64, - Architecture.riscv64, - ]..remove(Architecture.current); + final architectures = switch (OS.current) { + OS.linux => [ + Architecture.arm, + Architecture.arm64, + Architecture.ia32, + Architecture.x64, + Architecture.riscv64, + ], + OS.macOS => [Architecture.arm64, Architecture.x64], + OS() => throw UnsupportedError('No support for ${OS.current}'), + }..remove(Architecture.current); await runTests(architectures); } diff --git a/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart b/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart index 60486fb1d..59df5f905 100644 --- a/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart +++ b/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart @@ -12,6 +12,7 @@ import 'dart:io'; import 'package:native_toolchain_c/native_toolchain_c.dart'; import 'package:test/test.dart'; +import '../cbuilder/cbuilder_cross_android_test.dart' show checkArchitecture; import '../helpers.dart'; import 'build_testfiles.dart'; @@ -21,11 +22,12 @@ Future runTests(List architectures) async { assetName: '', sources: sources, linkerOptions: LinkerOptions.manual( - flags: ['--strip-debug', '-u', 'my_other_func'], + flags: ['-u', '${OS.current == OS.macOS ? '_' : ''}my_other_func'], gcSections: true, linkerScript: packageUri.resolve( 'test/clinker/testfiles/linker/symbols.lds', ), + includeAllSymbols: false, ), ); CLinker linkerAuto(List sources) => CLinker.library( @@ -41,7 +43,7 @@ Future runTests(List architectures) async { linkerOptions: LinkerOptions.treeshake(symbols: null), ); - const os = OS.linux; + final os = OS.current; late Map sizes; sizes = {}; @@ -79,6 +81,12 @@ Future runTests(List architectures) async { targetArchitecture: architecture, linkModePreference: LinkModePreference.dynamic, cCompiler: cCompiler, + macOS: + os == OS.macOS + ? MacOSCodeConfig( + targetVersion: defaultMacOSVersion, + ) + : null, ), ); @@ -95,8 +103,7 @@ Future runTests(List architectures) async { final asset = linkOutput.assets.code.first; final filePath = asset.file!.toFilePath(); - final machine = await readelfMachine(filePath); - expect(machine, contains(readElfMachine[architecture])); + await checkArchitecture(filePath, architecture); final symbols = await nmReadSymbols(asset); if (clinker.linker != linkerAutoEmpty) { @@ -107,7 +114,8 @@ Future runTests(List architectures) async { expect(symbols, contains('my_func')); } - final du = Process.runSync('du', ['-sb', filePath]).stdout as String; + final du = + Process.runSync('stat', ['-f%z', filePath]).stdout as String; final sizeInBytes = int.parse(du.split('\t')[0]); sizes[clinker.name] = sizeInBytes; }, diff --git a/pkgs/native_toolchain_c/test/clinker/treeshake_test.dart b/pkgs/native_toolchain_c/test/clinker/treeshake_test.dart index be651e488..5dd41854e 100644 --- a/pkgs/native_toolchain_c/test/clinker/treeshake_test.dart +++ b/pkgs/native_toolchain_c/test/clinker/treeshake_test.dart @@ -4,7 +4,7 @@ //TODO(mosuem): Enable for windows and mac. // See https://github.com/dart-lang/native/issues/1376. -@TestOn('linux') +@TestOn('linux || mac-os') library; import 'dart:io'; @@ -15,7 +15,7 @@ import 'package:test/test.dart'; import 'treeshake_helper.dart'; Future main() async { - if (!Platform.isLinux) { + if (!(Platform.isLinux || Platform.isMacOS)) { // Avoid needing status files on Dart SDK CI. return; } diff --git a/pkgs/native_toolchain_c/test/helpers.dart b/pkgs/native_toolchain_c/test/helpers.dart index bb2d28ed9..b3257512d 100644 --- a/pkgs/native_toolchain_c/test/helpers.dart +++ b/pkgs/native_toolchain_c/test/helpers.dart @@ -225,14 +225,6 @@ Future readelfMachine(String path) async { return result.split('\n').firstWhere((e) => e.contains('Machine:')); } -const readElfMachine = { - Architecture.arm: 'ARM', - Architecture.arm64: 'AArch64', - Architecture.ia32: 'Intel 80386', - Architecture.x64: 'Advanced Micro Devices X86-64', - Architecture.riscv64: 'RISC-V', -}; - Future readelf(String filePath, String flags) async { final result = await runProcess( executable: Uri.file('readelf'), @@ -248,7 +240,7 @@ Future nmReadSymbols(CodeAsset asset) async { final assetUri = asset.file!; final result = await runProcess( executable: Uri(path: 'nm'), - arguments: ['-D', assetUri.toFilePath()], + arguments: [assetUri.toFilePath()], logger: logger, ); @@ -260,7 +252,7 @@ Future expectSymbols({ required CodeAsset asset, required List symbols, }) async { - if (Platform.isLinux) { + if (Platform.isLinux || Platform.isMacOS) { final nmOutput = await nmReadSymbols(asset); expect(nmOutput, stringContainsInOrder(symbols));