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 e3599e21d..88dde7492 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart @@ -8,6 +8,7 @@ import '../native_toolchain/apple_clang.dart'; import '../native_toolchain/clang.dart'; import '../native_toolchain/gcc.dart'; import '../tool/tool.dart'; +import '../tool/tool_instance.dart'; /// Options to pass to the linker. /// @@ -15,6 +16,8 @@ import '../tool/tool.dart'; /// Alternatively, if the goal of the linking is to treeshake unused symbols, /// the [LinkerOptions.treeshake] constructor can be used. class LinkerOptions { + static const defaultLibraries = ['gcc', 'c', 'm', 'gcc_s']; + /// 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; @@ -50,9 +53,14 @@ class LinkerOptions { LinkerOptions.treeshake({ Iterable? flags, required Iterable? symbols, + Iterable libraries = defaultLibraries, }) : _linkerFlags = [ ...flags ?? [], '--strip-debug', + ...[ + '--as-needed', + ...libraries.expand((e) => ['-l$e']), + ], if (symbols != null) ...symbols.expand((e) => ['-u', e]), ].toList(), gcSections = true, @@ -115,17 +123,19 @@ extension LinkerOptionsExt on LinkerOptions { /// trick, which includes all symbols when linking object files. /// /// Throws if the [linker] is not supported. - Iterable postSourcesFlags( - Tool linker, + Future> postSourcesFlags( + ToolInstance linker, Iterable sourceFiles, - ) => - _toLinkerSyntax(linker, [ + ) async => + _toLinkerSyntax(linker.tool, [ + if (sourceFiles.any((source) => source.endsWith('.a')) || + _wholeArchiveSandwich) + '--no-whole-archive', ..._linkerFlags, + ...(await linker.tool.libraryPaths?.call() ?? []) + .expand((e) => ['-L$e']), 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 15954324e..25b608610 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -278,12 +278,14 @@ class RunCBuilder { '-l', cppLinkStdLib ?? defaultCppLinkStdLib[config.targetOS]! ], - ...linkerOptions?.preSourcesFlags(toolInstance.tool, sourceFiles) ?? [], ...flags, + ...linkerOptions?.preSourcesFlags(toolInstance.tool, sourceFiles) ?? [], 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()}', ...sourceFiles, + ...await linkerOptions?.postSourcesFlags(toolInstance, sourceFiles) ?? + [], if (language == Language.objectiveC) ...[ for (final framework in frameworks) ...[ '-framework', @@ -303,8 +305,6 @@ class RunCBuilder { '-o', outFile!.toFilePath(), ], - ...linkerOptions?.postSourcesFlags(toolInstance.tool, sourceFiles) ?? - [], ], logger: logger, captureOutput: false, diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/android_ndk.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/android_ndk.dart index 964a97a35..c2022e7cd 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/android_ndk.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/android_ndk.dart @@ -39,15 +39,15 @@ class _AndroidNdkResolver implements ToolResolver { final installLocationResolver = PathVersionResolver( wrappedResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'Android NDK', + tool: Tool(name: 'Android NDK'), wrappedResolver: PathToolResolver( - toolName: 'ndk-build', + tool: Tool(name: 'ndk-build'), executableName: Platform.isWindows ? 'ndk-build.cmd' : 'ndk-build', ), relativePath: Uri(path: ''), ), InstallLocationResolver( - toolName: 'Android NDK', + tool: Tool(name: 'Android NDK'), paths: [ if (Platform.isLinux) ...[ '\$HOME/Android/Sdk/ndk/*/', diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/apple_clang.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/apple_clang.dart index 9c7eb4c99..e393e95bb 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/apple_clang.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/apple_clang.dart @@ -15,7 +15,7 @@ final Tool appleClang = Tool( cliArguments: ['--version'], keepIf: ({required String stdout}) => stdout.contains('Apple clang'), wrappedResolver: PathToolResolver( - toolName: 'Apple Clang', + tool: Tool(name: 'Apple Clang'), executableName: 'clang', ), ), @@ -27,7 +27,7 @@ final Tool appleAr = Tool( name: 'Apple archiver', defaultResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'Apple archiver', + tool: Tool(name: 'Apple archiver'), wrappedResolver: appleClang.defaultResolver!, relativePath: Uri.file('ar'), ), @@ -39,7 +39,7 @@ final Tool appleLd = Tool( name: 'Apple linker', defaultResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'Apple linker', + tool: Tool(name: 'Apple linker'), wrappedResolver: appleClang.defaultResolver!, relativePath: Uri.file('ld'), ), @@ -53,7 +53,7 @@ final Tool otool = Tool( name: 'otool', defaultResolver: CliVersionResolver( wrappedResolver: PathToolResolver( - toolName: 'otool', + tool: Tool(name: 'otool'), executableName: 'otool', ), ), diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/clang.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/clang.dart index 910e3b33c..49aa3e095 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/clang.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/clang.dart @@ -15,7 +15,7 @@ final Tool clang = Tool( cliArguments: ['--version'], keepIf: ({required String stdout}) => !stdout.contains('Apple clang'), wrappedResolver: PathToolResolver( - toolName: 'Clang', + tool: Tool(name: 'Clang'), executableName: 'clang', ), ), @@ -30,7 +30,7 @@ final Tool llvmAr = Tool( defaultResolver: CliVersionResolver( wrappedResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'LLVM archiver', + tool: Tool(name: 'LLVM archiver'), wrappedResolver: clang.defaultResolver!, relativePath: Uri.file('llvm-ar'), ), @@ -46,7 +46,7 @@ final Tool lld = Tool( defaultResolver: CliVersionResolver( wrappedResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'LLD', + tool: Tool(name: 'LLD'), wrappedResolver: clang.defaultResolver!, relativePath: Uri.file('ld.lld'), ), diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/gcc.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/gcc.dart index c7bf2cd4b..e962435eb 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/gcc.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/gcc.dart @@ -2,10 +2,12 @@ // 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/native_assets_cli.dart'; import '../tool/tool.dart'; import '../tool/tool_resolver.dart'; +import '../utils/run_process.dart'; /// The GNU Compiler Collection for [Architecture.current]. /// @@ -77,7 +79,7 @@ Tool _gcc(String prefix) => Tool( name: gcc.name, defaultResolver: CliVersionResolver( wrappedResolver: PathToolResolver( - toolName: gcc.name, + tool: gcc, executableName: '$prefix-gcc', ), ), @@ -88,7 +90,7 @@ Tool _gnuArchiver(String prefix) { return Tool( name: gnuArchiver.name, defaultResolver: RelativeToolResolver( - toolName: gnuArchiver.name, + tool: gnuArchiver, wrappedResolver: gcc.defaultResolver!, relativePath: Uri.file('$prefix-gcc-ar'), ), @@ -100,9 +102,24 @@ Tool _gnuLinker(String prefix) { return Tool( name: gnuLinker.name, defaultResolver: RelativeToolResolver( - toolName: gnuLinker.name, + tool: Tool( + name: gnuLinker.name, + libraryPaths: () => parseSearchDirectoriesFor(Uri.file('$prefix-gcc')), + ), wrappedResolver: gcc.defaultResolver!, relativePath: Uri.file('$prefix-ld'), ), ); } + +Future> parseSearchDirectoriesFor(Uri uri) async { + final stdout = (await runProcess( + executable: uri, + arguments: ['-print-search-dirs'], + logger: Logger(''), + )) + .stdout; + const pattern = 'libraries: ='; + final start = stdout.indexOf(pattern); + return stdout.substring(start + pattern.length).split(':'); +} diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart index e8aef12ed..3b342a407 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart @@ -24,11 +24,11 @@ final Tool vswhere = Tool( wrappedResolver: ToolResolvers( [ PathToolResolver( - toolName: 'Visual Studio Locator', + tool: Tool(name: 'Visual Studio Locator'), executableName: 'vswhere.exe', ), InstallLocationResolver( - toolName: 'Visual Studio Locator', + tool: Tool(name: 'Visual Studio Locator'), paths: [ 'C:/Program Files \\(x86\\)/Microsoft Visual Studio/Installer/vswhere.exe', 'C:/Program Files/Microsoft Visual Studio/Installer/vswhere.exe', @@ -52,7 +52,7 @@ final Tool msvc = Tool( name: 'MSVC', defaultResolver: PathVersionResolver( wrappedResolver: RelativeToolResolver( - toolName: 'MSVC', + tool: Tool(name: 'MSVC'), wrappedResolver: visualStudio.defaultResolver!, relativePath: Uri(path: './VC/Tools/MSVC/*/'), ), @@ -80,7 +80,7 @@ Tool vcvars(ToolInstance toolInstance) { return Tool( name: fileName, defaultResolver: InstallLocationResolver( - toolName: fileName, + tool: Tool(name: fileName), paths: [ Glob.quote(batchScript.toFilePath().replaceAll('\\', '/')), ], @@ -91,7 +91,7 @@ Tool vcvars(ToolInstance toolInstance) { final Tool vcvars64 = Tool( name: 'vcvars64.bat', defaultResolver: RelativeToolResolver( - toolName: 'vcvars64.bat', + tool: Tool(name: 'vcvars64.bat'), wrappedResolver: visualStudio.defaultResolver!, relativePath: Uri(path: './VC/Auxiliary/Build/vcvars64.bat'), ), @@ -100,7 +100,7 @@ final Tool vcvars64 = Tool( final Tool vcvars32 = Tool( name: 'vcvars32.bat', defaultResolver: RelativeToolResolver( - toolName: 'vcvars32.bat', + tool: Tool(name: 'vcvars32.bat'), wrappedResolver: visualStudio.defaultResolver!, relativePath: Uri(path: './VC/Auxiliary/Build/vcvars32.bat'), ), @@ -114,7 +114,7 @@ final Tool vcvarsarm64 = Tool( // emulation on windows-arm64. name: 'vcvarsamd64_arm64.bat', defaultResolver: RelativeToolResolver( - toolName: 'vcvarsamd64_arm64.bat', + tool: Tool(name: 'vcvarsamd64_arm64.bat'), wrappedResolver: visualStudio.defaultResolver!, relativePath: Uri(path: './VC/Auxiliary/Build/vcvarsamd64_arm64.bat'), ), @@ -123,7 +123,7 @@ final Tool vcvarsarm64 = Tool( final Tool vcvarsall = Tool( name: 'vcvarsall.bat', defaultResolver: RelativeToolResolver( - toolName: 'vcvars32.bat', + tool: Tool(name: 'vcvars32.bat'), wrappedResolver: visualStudio.defaultResolver!, relativePath: Uri(path: './VC/Auxiliary/Build/vcvarsall.bat'), ), @@ -132,7 +132,7 @@ final Tool vcvarsall = Tool( final Tool vsDevCmd = Tool( name: 'VsDevCmd.bat', defaultResolver: RelativeToolResolver( - toolName: 'VsDevCmd.bat', + tool: Tool(name: 'VsDevCmd.bat'), wrappedResolver: visualStudio.defaultResolver!, relativePath: Uri(path: './Common7/Tools/VsDevCmd.bat'), ), @@ -246,7 +246,7 @@ Tool _msvcTool({ final hostArchName = _msvcArchNames[hostArchitecture]!; final targetArchName = _msvcArchNames[targetArchitecture]!; ToolResolver resolver = RelativeToolResolver( - toolName: executableName, + tool: Tool(name: executableName), wrappedResolver: msvc.defaultResolver!, relativePath: Uri( path: 'bin/Host$hostArchName/$targetArchName/$executableName', diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/xcode.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/xcode.dart index 48c25c3c0..daa639ea6 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/xcode.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/xcode.dart @@ -18,7 +18,7 @@ final Tool xcrun = Tool( name: 'xcrun', defaultResolver: CliVersionResolver( wrappedResolver: PathToolResolver( - toolName: 'xcrun', + tool: Tool(name: 'xcrun'), executableName: 'xcrun', ), ), diff --git a/pkgs/native_toolchain_c/lib/src/tool/tool.dart b/pkgs/native_toolchain_c/lib/src/tool/tool.dart index ff42f2855..fc74f082e 100644 --- a/pkgs/native_toolchain_c/lib/src/tool/tool.dart +++ b/pkgs/native_toolchain_c/lib/src/tool/tool.dart @@ -9,9 +9,12 @@ class Tool { ToolResolver? defaultResolver; + final Future> Function()? libraryPaths; + Tool({ required this.name, this.defaultResolver, + this.libraryPaths, }); @override 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 18f5f075c..92ef9c18e 100644 --- a/pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart +++ b/pkgs/native_toolchain_c/lib/src/tool/tool_resolver.dart @@ -27,26 +27,26 @@ abstract class ToolResolver { /// Uses `which` (`where` on Windows) to resolve a tool. class PathToolResolver extends ToolResolver { /// The [Tool.name] of the [Tool] to find on the `PATH`. - final String toolName; + final Tool tool; final String executableName; PathToolResolver({ - required this.toolName, + required this.tool, String? executableName, }) : executableName = executableName ?? - OS.current.executableFileName(toolName.toLowerCase()); + OS.current.executableFileName(tool.name.toLowerCase()); @override Future> resolve({required Logger? logger}) async { - logger?.finer('Looking for $toolName on PATH.'); + logger?.finer('Looking for $tool on PATH.'); final uri = await runWhich(logger: logger); if (uri == null) { - logger?.fine('Did not find $toolName on PATH.'); + logger?.fine('Did not find $tool on PATH.'); return []; } final toolInstances = [ - ToolInstance(tool: Tool(name: toolName), uri: uri), + ToolInstance(tool: tool, uri: uri), ]; logger?.fine('Found ${toolInstances.single}.'); return toolInstances; @@ -185,11 +185,11 @@ class ToolResolvers implements ToolResolver { } class InstallLocationResolver implements ToolResolver { - final String toolName; + final Tool tool; final List paths; InstallLocationResolver({ - required this.toolName, + required this.tool, required this.paths, }); @@ -197,18 +197,17 @@ class InstallLocationResolver implements ToolResolver { @override Future> resolve({required Logger? logger}) async { - logger?.finer('Looking for $toolName in $paths.'); + logger?.finer('Looking for $tool in $paths.'); final resolvedPaths = [ for (final path in paths) ...await tryResolvePath(path) ]; final toolInstances = [ - for (final uri in resolvedPaths) - ToolInstance(tool: Tool(name: toolName), uri: uri), + for (final uri in resolvedPaths) ToolInstance(tool: tool, uri: uri), ]; if (toolInstances.isNotEmpty) { logger?.fine('Found $toolInstances.'); } else { - logger?.finer('Found no $toolName in $paths.'); + logger?.finer('Found no $tool in $paths.'); } return toolInstances; } @@ -244,12 +243,12 @@ class InstallLocationResolver implements ToolResolver { } class RelativeToolResolver implements ToolResolver { - final String toolName; + final Tool tool; final ToolResolver wrappedResolver; final Uri relativePath; RelativeToolResolver({ - required this.toolName, + required this.tool, required this.wrappedResolver, required this.relativePath, }); @@ -258,7 +257,7 @@ class RelativeToolResolver implements ToolResolver { Future> resolve({required Logger? logger}) async { final otherToolInstances = await wrappedResolver.resolve(logger: logger); - logger?.finer('Looking for $toolName relative to $otherToolInstances ' + logger?.finer('Looking for $tool relative to $otherToolInstances ' 'with $relativePath.'); final globs = [ for (final toolInstance in otherToolInstances) @@ -275,7 +274,7 @@ class RelativeToolResolver implements ToolResolver { final result = [ for (final fileSystemEntity in fileSystemEntities) ToolInstance( - tool: Tool(name: toolName), + tool: tool, uri: fileSystemEntity.uri, ), ]; @@ -283,7 +282,7 @@ class RelativeToolResolver implements ToolResolver { if (result.isNotEmpty) { logger?.fine('Found $result.'); } else { - logger?.finer('Found no $toolName relative to $otherToolInstances.'); + logger?.finer('Found no $tool relative to $otherToolInstances.'); } return result; } diff --git a/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart b/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart index 85abb1996..21ff3b1c0 100644 --- a/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart +++ b/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart @@ -67,11 +67,11 @@ void main() { expect(await File.fromUri(barExeUri).exists(), true); expect(await File.fromUri(bazExeUri).exists(), true); final barResolver = InstallLocationResolver( - toolName: 'bar', + tool: Tool(name: 'bar'), paths: [barExeUri.toFilePath().unescape()], ); final bazResolver = RelativeToolResolver( - toolName: 'baz', + tool: Tool(name: 'baz'), wrappedResolver: barResolver, relativePath: Uri.file(bazExeName), ); @@ -94,9 +94,9 @@ void main() { final bazExeUri = tempUri.resolve(bazExeName); await File.fromUri(barExeUri).writeAsString('dummy'); final barResolver = InstallLocationResolver( - toolName: 'bar', paths: [barExeUri.toFilePath().unescape()]); + tool: Tool(name: 'bar'), paths: [barExeUri.toFilePath().unescape()]); final bazResolver = InstallLocationResolver( - toolName: 'baz', paths: [bazExeUri.toFilePath().unescape()]); + tool: Tool(name: 'baz'), paths: [bazExeUri.toFilePath().unescape()]); final barLogs = []; final bazLogs = []; await barResolver.resolve(logger: createCapturingLogger(barLogs)); diff --git a/pkgs/native_toolchain_c/test/tool/tool_test.dart b/pkgs/native_toolchain_c/test/tool/tool_test.dart index c14aff903..48a8aed15 100644 --- a/pkgs/native_toolchain_c/test/tool/tool_test.dart +++ b/pkgs/native_toolchain_c/test/tool/tool_test.dart @@ -14,12 +14,16 @@ void main() { expect(clang != androidNdk, true); expect( Tool(name: 'foo'), - Tool(name: 'foo', defaultResolver: PathToolResolver(toolName: 'foo')), + Tool( + name: 'foo', + defaultResolver: PathToolResolver(tool: Tool(name: 'foo'))), ); expect(Tool(name: 'foo') != Tool(name: 'bar'), true); expect( Tool(name: 'foo').hashCode, - Tool(name: 'foo', defaultResolver: PathToolResolver(toolName: 'foo')) + Tool( + name: 'foo', + defaultResolver: PathToolResolver(tool: Tool(name: 'foo'))) .hashCode, ); expect(Tool(name: 'foo').hashCode != Tool(name: 'bar').hashCode, true);