| // Copyright (c) 2024, 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:code_assets/code_assets.dart'; | |
| import 'package:file/file.dart'; | |
| import 'package:file/local.dart'; | |
| import 'package:hooks/hooks.dart'; | |
| import 'package:logging/logging.dart'; | |
| import 'package:meta/meta.dart'; | |
| import 'package:process/process.dart'; | |
| import 'cbuilder.dart'; | |
| import 'clibrary.dart'; | |
| import 'ctool.dart'; | |
| import 'linker_options.dart'; | |
| import 'linkmode.dart'; | |
| import 'logger.dart'; | |
| import 'output_type.dart'; | |
| import 'run_cbuilder.dart'; | |
| /// Specification for linking an artifact with a C linker. | |
| class CLinker extends CTool implements Linker { | |
| final LinkerOptions? linkerOptions; | |
| CLinker.library({ | |
| required super.name, | |
| super.packageName, | |
| super.assetName, | |
| this.linkerOptions, | |
| super.sources = const [], | |
| super.includes = const [], | |
| super.forcedIncludes = const [], | |
| super.frameworks = CTool.defaultFrameworks, | |
| super.libraries = const [], | |
| super.libraryDirectories = CTool.defaultLibraryDirectories, | |
| @visibleForTesting super.installName, | |
| super.flags = const [], | |
| super.defines = const {}, | |
| super.pic = true, | |
| super.std, | |
| super.language = .c, | |
| super.cppLinkStdLib, | |
| super.linkModePreference, | |
| super.optimizationLevel = .o3, | |
| }) : super(type: OutputType.library); | |
| /// Runs the C Linker with on this C build spec. | |
| /// | |
| /// Completes with an error if the linking fails. | |
| /// | |
| /// If provided, uses [logger] to output logs. Otherwise, uses a default | |
| /// logger that streams [Level.WARNING] to stdout and higher levels to stderr. | |
| /// | |
| /// [linkerOptions] overrides the [CLinker.linkerOptions] of this [CLinker]. | |
| /// See [CLinker.linkerOptions] for more documentation. | |
| /// | |
| /// [linkModePreference] overrides the [CTool.linkModePreference] of this | |
| /// [CLinker]. See [CTool.linkModePreference] for more documentation. | |
| /// | |
| /// [sources] overrides the [CTool.sources] of this [CLinker]. See | |
| /// [CTool.sources] for more documentation. | |
| /// | |
| /// [defines] are merged with the [CTool.defines] of this [CLinker]. See | |
| /// [CTool.defines] for more documentation. | |
| /// | |
| /// If provided, uses [processManager] to spawn processes. Otherwise, uses a | |
| /// [LocalProcessManager] that spawns real processes. | |
| /// | |
| /// If provided, uses [fileSystem] to access the file system. Otherwise, uses | |
| /// a [LocalFileSystem] that accesses the real file system. | |
| /// | |
| /// If you're using [CBuilder] in a build hook and [CLinker] in a link hook, | |
| /// see [CLibrary] to combine them. | |
| @override | |
| Future<void> run({ | |
| required LinkInput input, | |
| required LinkOutputBuilder output, | |
| Logger? logger, | |
| ProcessManager? processManager, | |
| FileSystem? fileSystem, | |
| LinkerOptions? linkerOptions, | |
| LinkModePreference? linkModePreference, | |
| List<String>? sources, | |
| Map<String, String?>? defines, | |
| }) async { | |
| logger ??= createDefaultLogger(); | |
| processManager ??= const LocalProcessManager(); | |
| fileSystem ??= const LocalFileSystem(); | |
| final effectiveLinkerOptions = | |
| linkerOptions ?? this.linkerOptions ?? LinkerOptions.manual(); | |
| if (effectiveLinkerOptions.skipWholeLibrary) { | |
| logger.info('Skipping linking as no symbols are to be kept.'); | |
| return; | |
| } | |
| final outDir = input.outputDirectory; | |
| final packageRoot = input.packageRoot; | |
| await fileSystem.directory(outDir).create(recursive: true); | |
| final linkMode = getLinkMode( | |
| linkModePreference ?? | |
| this.linkModePreference ?? | |
| input.config.code.linkModePreference, | |
| ); | |
| final libUri = outDir.resolve( | |
| input.config.code.targetOS.libraryFileName(name, linkMode), | |
| ); | |
| final resolvedSources = [ | |
| for (final source in sources ?? this.sources) | |
| packageRoot.resolveUri(Uri.file(source)), | |
| ]; | |
| final includes = [ | |
| for (final directory in this.includes) | |
| packageRoot.resolveUri(Uri.file(directory)), | |
| ]; | |
| final libraryDirectories = [ | |
| for (final directory in this.libraryDirectories) | |
| outDir.resolveUri(Uri.file(directory)), | |
| ]; | |
| final task = RunCBuilder( | |
| input: input, | |
| codeConfig: input.config.code, | |
| linkerOptions: effectiveLinkerOptions, | |
| logger: logger, | |
| processManager: processManager, | |
| fileSystem: fileSystem, | |
| sources: resolvedSources, | |
| includes: includes, | |
| frameworks: frameworks, | |
| libraries: libraries, | |
| libraryDirectories: libraryDirectories, | |
| dynamicLibrary: linkMode == DynamicLoadingBundled() ? libUri : null, | |
| staticLibrary: linkMode == StaticLinking() ? libUri : null, | |
| // ignore: invalid_use_of_visible_for_testing_member | |
| installName: installName, | |
| flags: flags, | |
| defines: {...this.defines, ...?defines}, | |
| pic: pic, | |
| std: std, | |
| language: language, | |
| cppLinkStdLib: cppLinkStdLib, | |
| optimizationLevel: optimizationLevel, | |
| ); | |
| await task.run(); | |
| if (assetName != null) { | |
| output.assets.code.add( | |
| CodeAsset( | |
| package: packageName ?? input.packageName, | |
| name: assetName!, | |
| file: libUri, | |
| linkMode: linkMode, | |
| ), | |
| ); | |
| } | |
| final includeFiles = await Stream.fromIterable(includes) | |
| .asyncExpand( | |
| (include) => fileSystem! | |
| .directory(include.toFilePath()) | |
| .list(recursive: true) | |
| .where((entry) => entry is File) | |
| .map((file) => file.uri), | |
| ) | |
| .toList(); | |
| output.dependencies.addAll({ | |
| // Note: We use a Set here to deduplicate the dependencies. | |
| ...resolvedSources, | |
| ...includeFiles, | |
| }); | |
| } | |
| } |