From d49684325644de8a042e4a60ece0bfcfcf33b726 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 4 Apr 2023 06:23:57 +0000 Subject: [PATCH 01/27] Initial version --- .github/workflows/c_compiler.yaml | 6 + .github/workflows/native_assets_cli.yaml | 8 + pkgs/c_compiler/README.md | 2 +- pkgs/c_compiler/lib/c_compiler.dart | 8 +- .../c_compiler/lib/src/cbuilder/cbuilder.dart | 132 +++++++ .../lib/src/cbuilder/compiler_resolver.dart | 163 +++++++++ pkgs/c_compiler/lib/src/cbuilder/target.dart | 97 ++++++ .../lib/src/native_toolchain/android_ndk.dart | 73 ++++ .../lib/src/native_toolchain/clang.dart | 14 + pkgs/c_compiler/lib/src/tool/tool.dart | 22 ++ pkgs/c_compiler/lib/src/tool/tool_error.dart | 12 + .../lib/src/tool/tool_instance.dart | 74 ++++ .../lib/src/tool/tool_requirement.dart | 92 +++++ .../lib/src/tool/tool_resolver.dart | 221 ++++++++++++ .../c_compiler/lib/src/utils/run_process.dart | 155 +++++++++ .../c_compiler/lib/src/utils/sem_version.dart | 18 + pkgs/c_compiler/pubspec.yaml | 10 +- pkgs/c_compiler/test/c_compiler_test.dart | 20 -- .../cbuilder/cbuilder_cross_android_test.dart | 87 +++++ .../cbuilder_cross_linux_host_test.dart | 87 +++++ .../test/cbuilder/cbuilder_test.dart | 88 +++++ .../cbuilder/testfiles/add/src/add.c} | 7 +- .../testfiles/hello_world/src/hello_world.c | 10 + .../test/native_toolchain/clang_test.dart | 18 + .../test/native_toolchain/ndk_test.dart | 19 + pkgs/native_assets_cli/README.md | 2 +- .../example/native_add/build.dart | 56 +++ .../example/native_add/ffigen.yaml | 20 ++ .../example/native_add/lib/native_add.dart | 5 + .../native_add/lib/src/native_add.dart | 9 + .../src/native_add_bindings_generated.dart | 16 + .../example/native_add/pubspec.yaml | 22 ++ .../native_add/src/native_add.c} | 7 +- .../example/native_add/src/native_add.h | 13 + .../lib/native_assets_cli.dart | 12 +- .../lib/src/model/asset.dart | 324 +++++++++++++++++ .../lib/src/model/build_config.dart | 221 ++++++++++++ .../lib/src/model/build_output.dart | 102 ++++++ .../lib/src/model/dependencies.dart | 53 +++ .../lib/src/model/ios_sdk.dart | 29 ++ .../lib/src/model/metadata.dart | 41 +++ .../lib/src/model/packaging.dart | 24 ++ .../lib/src/model/packaging_preference.dart | 77 +++++ .../lib/src/model/target.dart | 326 ++++++++++++++++++ .../lib/src/utils/datetime.dart | 9 + .../native_assets_cli/lib/src/utils/file.dart | 65 ++++ pkgs/native_assets_cli/lib/src/utils/map.dart | 15 + pkgs/native_assets_cli/lib/src/utils/uri.dart | 21 ++ .../native_assets_cli/lib/src/utils/yaml.dart | 18 + pkgs/native_assets_cli/pubspec.yaml | 10 +- .../test/example/native_add_test.dart | 61 ++++ .../test/model/asset_test.dart | 186 ++++++++++ .../test/model/build_config_test.dart | 232 +++++++++++++ .../test/model/build_output_test.dart | 97 ++++++ .../test/model/dependencies_test.dart | 84 +++++ .../test/model/metadata_test.dart | 26 ++ .../packaging_test.dart} | 12 +- .../test/model/target_test.dart | 49 +++ 58 files changed, 3644 insertions(+), 43 deletions(-) create mode 100644 pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart create mode 100644 pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart create mode 100644 pkgs/c_compiler/lib/src/cbuilder/target.dart create mode 100644 pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart create mode 100644 pkgs/c_compiler/lib/src/native_toolchain/clang.dart create mode 100644 pkgs/c_compiler/lib/src/tool/tool.dart create mode 100644 pkgs/c_compiler/lib/src/tool/tool_error.dart create mode 100644 pkgs/c_compiler/lib/src/tool/tool_instance.dart create mode 100644 pkgs/c_compiler/lib/src/tool/tool_requirement.dart create mode 100644 pkgs/c_compiler/lib/src/tool/tool_resolver.dart create mode 100644 pkgs/c_compiler/lib/src/utils/run_process.dart create mode 100644 pkgs/c_compiler/lib/src/utils/sem_version.dart delete mode 100644 pkgs/c_compiler/test/c_compiler_test.dart create mode 100644 pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart create mode 100644 pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart create mode 100644 pkgs/c_compiler/test/cbuilder/cbuilder_test.dart rename pkgs/c_compiler/{lib/src/c_compiler_base.dart => test/cbuilder/testfiles/add/src/add.c} (69%) create mode 100644 pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c create mode 100644 pkgs/c_compiler/test/native_toolchain/clang_test.dart create mode 100644 pkgs/c_compiler/test/native_toolchain/ndk_test.dart create mode 100644 pkgs/native_assets_cli/example/native_add/build.dart create mode 100644 pkgs/native_assets_cli/example/native_add/ffigen.yaml create mode 100644 pkgs/native_assets_cli/example/native_add/lib/native_add.dart create mode 100644 pkgs/native_assets_cli/example/native_add/lib/src/native_add.dart create mode 100644 pkgs/native_assets_cli/example/native_add/lib/src/native_add_bindings_generated.dart create mode 100644 pkgs/native_assets_cli/example/native_add/pubspec.yaml rename pkgs/native_assets_cli/{lib/src/native_assets_cli_base.dart => example/native_add/src/native_add.c} (69%) create mode 100644 pkgs/native_assets_cli/example/native_add/src/native_add.h create mode 100644 pkgs/native_assets_cli/lib/src/model/asset.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/build_config.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/build_output.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/dependencies.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/ios_sdk.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/metadata.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/packaging.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/packaging_preference.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/target.dart create mode 100644 pkgs/native_assets_cli/lib/src/utils/datetime.dart create mode 100644 pkgs/native_assets_cli/lib/src/utils/file.dart create mode 100644 pkgs/native_assets_cli/lib/src/utils/map.dart create mode 100644 pkgs/native_assets_cli/lib/src/utils/uri.dart create mode 100644 pkgs/native_assets_cli/lib/src/utils/yaml.dart create mode 100644 pkgs/native_assets_cli/test/example/native_add_test.dart create mode 100644 pkgs/native_assets_cli/test/model/asset_test.dart create mode 100644 pkgs/native_assets_cli/test/model/build_config_test.dart create mode 100644 pkgs/native_assets_cli/test/model/build_output_test.dart create mode 100644 pkgs/native_assets_cli/test/model/dependencies_test.dart create mode 100644 pkgs/native_assets_cli/test/model/metadata_test.dart rename pkgs/native_assets_cli/test/{native_assets_cli_test.dart => model/packaging_test.dart} (62%) create mode 100644 pkgs/native_assets_cli/test/model/target_test.dart diff --git a/.github/workflows/c_compiler.yaml b/.github/workflows/c_compiler.yaml index ccd23a7937..f67b0ba9ff 100644 --- a/.github/workflows/c_compiler.yaml +++ b/.github/workflows/c_compiler.yaml @@ -32,6 +32,9 @@ jobs: - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{matrix.sdk}} + - uses: nttld/setup-ndk@v1 + with: + ndk-version: r25b - run: dart pub get @@ -40,6 +43,9 @@ jobs: - run: dart format --output=none --set-exit-if-changed . if: ${{matrix.run-tests}} + - name: Install native toolchains + run: sudo apt-get install clang-14 gcc-i686-linux-gnu gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf + - run: dart test if: ${{matrix.run-tests}} diff --git a/.github/workflows/native_assets_cli.yaml b/.github/workflows/native_assets_cli.yaml index 34d2520a1b..4a4b7edc93 100644 --- a/.github/workflows/native_assets_cli.yaml +++ b/.github/workflows/native_assets_cli.yaml @@ -32,14 +32,22 @@ jobs: - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{matrix.sdk}} + - uses: nttld/setup-ndk@v1 + with: + ndk-version: r25b - run: dart pub get + - run: dart pub get + working-directory: pkgs/native_assets_cli/example/native_add/ - run: dart analyze --fatal-infos - run: dart format --output=none --set-exit-if-changed . if: ${{matrix.run-tests}} + - name: Install native toolchains + run: sudo apt-get install clang-14 gcc-i686-linux-gnu gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf + - run: dart test if: ${{matrix.run-tests}} diff --git a/pkgs/c_compiler/README.md b/pkgs/c_compiler/README.md index d8c8f7e619..3327ce6766 100644 --- a/pkgs/c_compiler/README.md +++ b/pkgs/c_compiler/README.md @@ -1,4 +1,4 @@ -[![package:c_compiler](https://github.com/dart-lang/native/actions/workflows/c_compiler.yml/badge.svg)](https://github.com/dart-lang/native/actions/workflows/c_compiler.yml) +[![package:c_compiler](https://github.com/dart-lang/native/actions/workflows/c_compiler.yaml/badge.svg)](https://github.com/dart-lang/native/actions/workflows/c_compiler.yaml) [![pub package](https://img.shields.io/pub/v/c_compiler.svg)](https://pub.dev/packages/c_compiler) [![Coverage Status](https://coveralls.io/repos/github/dart-lang/native/badge.svg?branch=main)](https://coveralls.io/github/dart-lang/tools?branch=main) diff --git a/pkgs/c_compiler/lib/c_compiler.dart b/pkgs/c_compiler/lib/c_compiler.dart index dcdff2381d..cb36b4a676 100644 --- a/pkgs/c_compiler/lib/c_compiler.dart +++ b/pkgs/c_compiler/lib/c_compiler.dart @@ -5,4 +5,10 @@ /// A library to invoke the native C compiler installed on the host machine. library; -export 'src/c_compiler_base.dart'; +export 'src/cbuilder/cbuilder.dart'; +export 'src/native_toolchain/android_ndk.dart' show androidNdk, androidNdkClang; +export 'src/native_toolchain/clang.dart' show clang; +export 'src/tool/tool.dart'; +export 'src/tool/tool_instance.dart'; +export 'src/tool/tool_requirement.dart'; +export 'src/tool/tool_resolver.dart'; diff --git a/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart new file mode 100644 index 0000000000..be68a87400 --- /dev/null +++ b/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart @@ -0,0 +1,132 @@ +// 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:cli_config/cli_config.dart'; +import 'package:logging/logging.dart'; + +import '../utils/run_process.dart'; +import 'compiler_resolver.dart'; +import 'target.dart'; + +class CBuilder { + final Config config; + final Logger logger; + final List sources; + final List includePaths; + final Uri? executable; + final Uri? dynamicLibrary; + final Uri? staticLibrary; + final Uri outDir; + final String target; + + CBuilder({ + required this.config, + required this.logger, + this.sources = const [], + this.includePaths = const [], + this.executable, + this.dynamicLibrary, + this.staticLibrary, + }) : outDir = config.path('out_dir'), + target = config.optionalString('target') ?? Target.current() { + if ([executable, dynamicLibrary, staticLibrary].whereType().length != + 1) { + throw ArgumentError( + 'Provide one of executable, dynamicLibrary, or staticLibrary.'); + } + } + + Uri? _compilerCached; + + Future compiler() async { + if (_compilerCached != null) { + return _compilerCached!; + } + final resolver = CompilerResolver(config: config, logger: logger); + _compilerCached = (await resolver.resolve()).first.uri; + return _compilerCached!; + } + + Uri? _archiverCached; + + Future archiver() async { + if (_archiverCached != null) { + return _archiverCached!; + } + final compiler_ = await compiler(); + final resolver = CompilerResolver(config: config, logger: logger); + _linkerCached = await resolver.resolveArchiver( + compiler_, + ); + return _linkerCached!; + } + + Uri? _linkerCached; + + Future linker() async { + if (_linkerCached != null) { + return _linkerCached!; + } + final compiler_ = await compiler(); + final resolver = CompilerResolver(config: config, logger: logger); + _linkerCached = await resolver.resolveLinker( + compiler_, + ); + return _linkerCached!; + } + + Future run() async { + final compiler_ = await compiler(); + final isStaticLib = staticLibrary != null; + Uri? archiver_; + if (isStaticLib) { + archiver_ = await archiver(); + } + + await RunProcess( + executable: compiler_.path, + arguments: [ + if (target.startsWith('android')) ...[ + // TODO(dacoharkes): How to solve linking issues? + // Workaround: + '-nostartfiles', + // Non-working fix: --sysroot=$NDKPATH/toolchains/llvm/prebuilt/linux-x86_64/sysroot. + // The sysroot should be discovered automatically after NDK 22. + '--target=${androidNdkClangTargetFlags[target]!}', + ], + ...sources.map((e) => e.path), + if (executable != null) ...[ + '-o', + outDir.resolveUri(executable!).path, + ], + if (dynamicLibrary != null) ...[ + '--shared', + '-o', + outDir.resolveUri(dynamicLibrary!).path, + ] else if (staticLibrary != null) ...[ + '-c', + '-o', + outDir.resolve('out.o').path, + ], + ], + ).run(logger: logger); + if (staticLibrary != null) { + await RunProcess( + executable: archiver_!.path, + arguments: [ + 'rc', + outDir.resolveUri(staticLibrary!).path, + outDir.resolve('out.o').path, + ], + ).run(logger: logger); + } + } + + static const androidNdkClangTargetFlags = { + Target.androidArm: 'armv7a-linux-androideabi', + Target.androidArm64: 'aarch64-linux-android', + Target.androidIA32: 'i686-linux-android', + Target.androidX64: 'x86_64-linux-android', + }; +} diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart new file mode 100644 index 0000000000..dc277a2a8f --- /dev/null +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -0,0 +1,163 @@ +// 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 'dart:io'; + +import 'package:cli_config/cli_config.dart'; +import 'package:logging/logging.dart'; + +import '../native_toolchain/android_ndk.dart'; +import '../native_toolchain/clang.dart'; +import '../tool/tool.dart'; +import '../tool/tool_instance.dart'; +import '../tool/tool_resolver.dart'; +import 'target.dart'; + +class CompilerResolver implements ToolResolver { + final Config config; + final Logger? logger; + + CompilerResolver({ + required this.config, + required this.logger, + }); + + @override + Future> resolve() async { + final tool = selectCompiler(); + ToolInstance? result; + result ??= await _tryLoadCompilerFromConfig(tool, _configKeyCC); + result ??= await _tryLoadCompilerFromConfig( + tool, + _configKeyNativeToolchainClang, + ); + result ??= await _tryLoadCompilerFromNativeToolchain( + tool, + ); + + if (result != null) { + return [result]; + } + const errorMessage = 'No C compiler found.'; + logger?.severe(errorMessage); + throw Exception(errorMessage); + } + + /// Select the right compiler for cross compiling to the specified target. + Tool selectCompiler() { + final target = config.optionalString('target') ?? Target.current(); + switch (target) { + case Target.linuxArm: + return armLinuxGnueabihfGcc; + case Target.linuxArm64: + return aarch64LinuxGnuGcc; + case Target.linuxIA32: + return i686LinuxGnuGcc; + case Target.linuxX64: + return clang; + case Target.androidArm: + case Target.androidArm64: + case Target.androidIA32: + case Target.androidX64: + return androidNdkClang; + } + throw Exception('No tool available for target: $target.'); + } + + /// Provided by launchers. + static const _configKeyCC = 'cc'; + + /// Provided by package:native_toolchain. + static const _configKeyNativeToolchainClang = 'deps.native_toolchain.clang'; + + Future _tryLoadCompilerFromConfig( + Tool tool, String configKey) async { + final configCcUri = config.optionalPath(_configKeyCC); + if (configCcUri != null) { + if (await File.fromUri(configCcUri).exists()) { + logger?.finer( + 'Using compiler ${configCcUri.path} from config[$_configKeyCC].'); + return ToolInstance(tool: tool, uri: configCcUri); + } else { + logger?.warning( + 'Compiler ${configCcUri.path} from config[$_configKeyCC] does not ' + 'exist.'); + } + } + return null; + } + + /// If a build is invoked + Future _tryLoadCompilerFromNativeToolchain(Tool tool) async { + final resolved = (await tool.defaultResolver!.resolve()) + .where((i) => i.tool == tool) + .toList() + ..sort(); + if (resolved.isEmpty) { + logger?.warning('Clang could not be found by package:native_toolchain.'); + return null; + } + return resolved.last; + } + + Future resolveLinker( + Uri compiler, + ) async { + if (compiler.pathSegments.last == 'clang') { + final lld = compiler.resolve('lld'); + if (await File.fromUri(lld).exists()) { + return lld; + } + } + const errorMessage = 'No native linker found.'; + logger?.severe(errorMessage); + throw Exception(errorMessage); + } + + Future resolveArchiver( + Uri compiler, + ) async { + final compilerExecutable = compiler.pathSegments.last; + if (compilerExecutable == 'clang') { + final ar = compiler.resolve('llvm-ar'); + if (await File.fromUri(ar).exists()) { + return ar; + } + } else if (compilerExecutable.contains('-gcc')) { + final ar = + compiler.resolve(compilerExecutable.replaceAll('-gcc', '-gcc-ar')); + if (await File.fromUri(ar).exists()) { + return ar; + } else { + print(ar); + } + } + final errorMessage = + 'No native linker found for compiler: $compilerExecutable $compiler.'; + logger?.severe(errorMessage); + throw Exception(errorMessage); + } +} + +final i686LinuxGnuGcc = Tool( + name: 'i686-linux-gnu-gcc', + defaultResolver: PathToolResolver(toolName: 'i686-linux-gnu-gcc'), +); + +final armLinuxGnueabihfGcc = Tool( + name: 'arm-linux-gnueabihf-gcc', + defaultResolver: PathToolResolver(toolName: 'arm-linux-gnueabihf-gcc'), +); + +final aarch64LinuxGnuGcc = Tool( + name: 'aarch64-linux-gnu-gcc', + defaultResolver: PathToolResolver(toolName: 'aarch64-linux-gnu-gcc'), +); + +final clangLike = [ + clang, + i686LinuxGnuGcc, + armLinuxGnueabihfGcc, + aarch64LinuxGnuGcc, +]; diff --git a/pkgs/c_compiler/lib/src/cbuilder/target.dart b/pkgs/c_compiler/lib/src/cbuilder/target.dart new file mode 100644 index 0000000000..0c9be2171c --- /dev/null +++ b/pkgs/c_compiler/lib/src/cbuilder/target.dart @@ -0,0 +1,97 @@ +// 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 'dart:ffi'; + +abstract class Target { + /// The application binary interface for Android on the Arm architecture. + static const String androidArm = 'android_arm'; + + /// The application binary interface for Android on the Arm64 architecture. + static const String androidArm64 = 'android_arm64'; + + /// The application binary interface for Android on the IA32 architecture. + static const String androidIA32 = 'android_ia32'; + + /// The application binary interface for android on the X64 architecture. + static const String androidX64 = 'android_x64'; + + /// The application binary interface for Fuchsia on the Arm64 architecture. + static const String fuchsiaArm64 = 'fuchsia_arm64'; + + /// The application binary interface for Fuchsia on the X64 architecture. + static const String fuchsiaX64 = 'fuchsia_x64'; + + /// The application binary interface for iOS on the Arm architecture. + static const String iosArm = 'ios_arm'; + + /// The application binary interface for iOS on the Arm64 architecture. + static const String iosArm64 = 'ios_arm64'; + + /// The application binary interface for iOS on the X64 architecture. + static const String iosX64 = 'ios_x64'; + + /// The application binary interface for Linux on the Arm architecture. + /// + /// Does not distinguish between hard and soft fp. Currently, no uses of Abi + /// require this distinction. + static const String linuxArm = 'linux_arm'; + + /// The application binary interface for linux on the Arm64 architecture. + static const String linuxArm64 = 'linux_arm64'; + + /// The application binary interface for linux on the IA32 architecture. + static const String linuxIA32 = 'linux_ia32'; + + /// The application binary interface for linux on the X64 architecture. + static const String linuxX64 = 'linux_x64'; + + /// The application binary interface for linux on 32-bit RISC-V. + static const String linuxRiscv32 = 'linux_riscv32'; + + /// The application binary interface for linux on 64-bit RISC-V. + static const String linuxRiscv64 = 'linux_riscv64'; + + /// The application binary interface for MacOS on the Arm64 architecture. + static const String macosArm64 = 'macos_arm64'; + + /// The application binary interface for MacOS on the X64 architecture. + static const String macosX64 = 'macos_x64'; + + /// The application binary interface for Windows on the Arm64 architecture. + static const String windowsArm64 = 'windows_arm64'; + + /// The application binary interface for Windows on the IA32 architecture. + static const String windowsIA32 = 'windows_ia32'; + + /// The application binary interface for Windows on the X64 architecture. + static const String windowsX64 = 'windows_x64'; + + static const List values = [ + androidArm, + androidArm64, + androidIA32, + androidX64, + fuchsiaArm64, + fuchsiaX64, + iosArm, + iosArm64, + iosX64, + linuxArm, + linuxArm64, + linuxIA32, + linuxX64, + linuxRiscv32, + linuxRiscv64, + macosArm64, + macosX64, + windowsArm64, + windowsIA32, + windowsX64, + ]; + + /// Returns the target of the host machine. + static String current() => + values.firstWhere((e) => e == Abi.current().toString()); +} diff --git a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart new file mode 100644 index 0000000000..2951435790 --- /dev/null +++ b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart @@ -0,0 +1,73 @@ +// 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 'dart:io'; + +import '../tool/tool.dart'; +import '../tool/tool_instance.dart'; +import '../tool/tool_resolver.dart'; + +final androidNdk = Tool( + name: 'Android NDK', + defaultResolver: AndroidNdkResolver(), +); + +/// A clang that knows how to target Android. +final androidNdkClang = Tool( + name: 'Android NDK Clang', + defaultResolver: AndroidNdkResolver(), +); + +class AndroidNdkResolver implements ToolResolver { + final installLocationResolver = PathVersionResolver( + wrappedResolver: ToolResolvers([ + RelativeToolResolver( + toolName: 'Android NDK', + wrappedResolver: PathToolResolver(toolName: 'ndk-build'), + relativePath: Uri(path: '.'), + ), + InstallLocationResolver( + toolName: 'Android NDK', + paths: [ + if (Platform.isLinux) ...[ + '\$HOME/Android/Sdk/ndk/*/', + '\$HOME/Android/Sdk/ndk-bundle/', + ], + ], + ), + ]), + ); + + @override + Future> resolve() async { + final ndkInstances = await installLocationResolver.resolve(); + + return [ + for (final ndkInstance in ndkInstances) ...[ + ndkInstance, + ...await tryResolveClang(ndkInstance) + ] + ]; + } + + Future> tryResolveClang( + ToolInstance androidNdkInstance) async { + final result = []; + final prebuiltUri = + androidNdkInstance.uri.resolve('toolchains/llvm/prebuilt/'); + final prebuiltDir = Directory.fromUri(prebuiltUri); + final hostArchDirs = + (await prebuiltDir.list().toList()).whereType().toList(); + for (final hostArchDir in hostArchDirs) { + final clangUri = hostArchDir.uri.resolve('bin/clang'); + if (await File.fromUri(clangUri).exists()) { + result.add(await CliVersionResolver.lookupVersion(ToolInstance( + tool: androidNdkClang, + uri: clangUri, + ))); + } + } + return result; + } +} diff --git a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart new file mode 100644 index 0000000000..f62a4fe0de --- /dev/null +++ b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart @@ -0,0 +1,14 @@ +// 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 '../tool/tool.dart'; +import '../tool/tool_resolver.dart'; + +final Tool clang = Tool( + name: 'Clang', + defaultResolver: CliVersionResolver( + wrappedResolver: ToolResolvers([ + PathToolResolver(toolName: 'Clang'), + ]), + )); diff --git a/pkgs/c_compiler/lib/src/tool/tool.dart b/pkgs/c_compiler/lib/src/tool/tool.dart new file mode 100644 index 0000000000..13360a5ed8 --- /dev/null +++ b/pkgs/c_compiler/lib/src/tool/tool.dart @@ -0,0 +1,22 @@ +// 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 'tool_resolver.dart'; + +class Tool { + final String name; + + ToolResolver? defaultResolver; + + Tool({ + required this.name, + this.defaultResolver, + }); + + @override + bool operator ==(Object other) => other is Tool && name == other.name; + + @override + int get hashCode => name.hashCode * 41; +} diff --git a/pkgs/c_compiler/lib/src/tool/tool_error.dart b/pkgs/c_compiler/lib/src/tool/tool_error.dart new file mode 100644 index 0000000000..93a351b014 --- /dev/null +++ b/pkgs/c_compiler/lib/src/tool/tool_error.dart @@ -0,0 +1,12 @@ +// 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. + +/// The operation could not be performed due to a configuration error on the +/// host system. +class ToolError extends Error { + final String message; + ToolError(this.message); + @override + String toString() => 'System not configured correctly: $message'; +} diff --git a/pkgs/c_compiler/lib/src/tool/tool_instance.dart b/pkgs/c_compiler/lib/src/tool/tool_instance.dart new file mode 100644 index 0000000000..765b9ef5e6 --- /dev/null +++ b/pkgs/c_compiler/lib/src/tool/tool_instance.dart @@ -0,0 +1,74 @@ +// 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:pub_semver/pub_semver.dart'; + +import 'tool.dart'; + +class ToolInstance implements Comparable { + /// The name of the tool. + final Tool tool; + + /// The path of the native tool on the system. + final Uri uri; + + /// The version of the native tool. + /// + /// Can be null if version is hard to determine. + final Version? version; + + ToolInstance({ + required this.tool, + required this.uri, + this.version, + }); + + ToolInstance copyWith({Uri? uri, Version? version}) => ToolInstance( + tool: tool, + uri: uri ?? this.uri, + version: version ?? this.version, + ); + + @override + String toString() => 'ToolInstance(${tool.name}, $version, $uri)'; + + /// The path of this native tool. + /// + /// We use forward slashes on also on Windows because that's easier with + /// escaping when passing as command line arguments. + /// Using this because `windows: false` puts a `/` in front of `C:/`. + String get path => + uri.toFilePath().replaceAll(r'\', r'/').replaceAll(' ', '\\ '); + + @override + int compareTo(ToolInstance other) { + final nameCompare = tool.name.compareTo(other.tool.name); + if (nameCompare != 0) { + return nameCompare; + } + final version = this.version; + if (version == null) { + return -1; + } + final otherVersion = other.version; + if (otherVersion == null) { + return 1; + } + final versionCompare = version.compareTo(otherVersion); + if (versionCompare != 0) { + return versionCompare; + } + return uri.toFilePath().compareTo(other.uri.toFilePath()); + } + + @override + bool operator ==(Object other) => + other is ToolInstance && + tool == other.tool && + uri == other.uri && + version == other.version; + + @override + int get hashCode => tool.hashCode ^ uri.hashCode ^ version.hashCode; +} diff --git a/pkgs/c_compiler/lib/src/tool/tool_requirement.dart b/pkgs/c_compiler/lib/src/tool/tool_requirement.dart new file mode 100644 index 0000000000..9e4881742b --- /dev/null +++ b/pkgs/c_compiler/lib/src/tool/tool_requirement.dart @@ -0,0 +1,92 @@ +// 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:pub_semver/pub_semver.dart'; + +import 'tool.dart'; +import 'tool_instance.dart'; + +abstract class Requirement { + /// Tries to satisfy this requirement. + /// + /// If the requirement can be satisfied, returns the set of tools that + /// satisfy the requirement. + /// + /// Currently does not check that we only use a single version of a tool. + List? satisfy(List allAvailableTools); +} + +class ToolRequirement implements Requirement { + final Tool tool; + + final Version? minimumVersion; + + ToolRequirement( + this.tool, { + this.minimumVersion, + }); + + @override + String toString() => + 'ToolRequirement(${tool.name}, minimumVersion: $minimumVersion)'; + + @override + List? satisfy(List availableToolInstances) { + final candidates = []; + for (final instance in availableToolInstances) { + if (instance.tool == tool) { + final minimumVersion_ = minimumVersion; + if (minimumVersion_ == null) { + candidates.add(instance); + } else { + final version = instance.version; + if (version != null && version >= minimumVersion_) { + candidates.add(instance); + } + } + } + } + if (candidates.isEmpty) { + return null; + } + candidates.sort(); + return [candidates.last]; + } +} + +class RequireOne implements Requirement { + final List alternatives; + + RequireOne(this.alternatives); + + @override + List? satisfy(List allAvailableTools) { + for (final alternative in alternatives) { + final result = alternative.satisfy(allAvailableTools); + if (result != null) { + return result; + } + } + return null; + } +} + +class RequireAll implements Requirement { + final List requirements; + + RequireAll(this.requirements); + + @override + List? satisfy(List allAvailableTools) { + final result = []; + for (final requirement in requirements) { + final requirementResult = requirement.satisfy(allAvailableTools); + if (requirementResult == null) { + return null; + } + result.addAll(requirementResult); + } + return result; + } +} diff --git a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart new file mode 100644 index 0000000000..983944180a --- /dev/null +++ b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart @@ -0,0 +1,221 @@ +// 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 'dart:convert'; +import 'dart:io'; + +import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; +import 'package:pub_semver/pub_semver.dart'; + +import '../utils/run_process.dart'; +import '../utils/sem_version.dart'; +import 'tool.dart'; +import 'tool_error.dart'; +import 'tool_instance.dart'; + +abstract class ToolResolver { + /// Resolves tools on the host system. + Future> resolve(); +} + +/// Tries to resolve a tool on the `PATH`. +/// +/// 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; + + PathToolResolver({required this.toolName}); + + @override + Future> resolve() async { + final uri = await runWhich(); + + return [ + if (uri != null) ToolInstance(tool: Tool(name: toolName), uri: uri), + ]; + } + + String get executableName { + final executableName = toolName.toLowerCase(); + if (Platform.isWindows) { + return '$executableName.exe'; + } + return executableName; + } + + static String get which => Platform.isWindows ? 'where' : 'which'; + + Future runWhich() async { + final process = await runProcess( + executable: which, + arguments: [executableName], + throwOnFailure: false, + ); + if (process.exitCode == 0) { + final file = File(LineSplitter.split(process.stdout).first); + final uri = File(await file.resolveSymbolicLinks()).uri; + return uri; + } + if (process.exitCode == 1) { + // The exit code for executable not being on the `PATH`. + return null; + } + throw ToolError('`$which $executableName` returned unexpected exit code: ' + '${process.exitCode}.'); + } +} + +class CliVersionResolver implements ToolResolver { + ToolResolver wrappedResolver; + + CliVersionResolver({required this.wrappedResolver}); + + @override + Future> resolve() async { + final toolInstances = await wrappedResolver.resolve(); + + return [ + for (final toolInstance in toolInstances) + await lookupVersion(toolInstance) + ]; + } + + static Future lookupVersion(ToolInstance toolInstance) async { + if (toolInstance.version != null) { + return toolInstance; + } + return toolInstance.copyWith( + version: await executableVersion(toolInstance.uri), + ); + } + + static Future executableVersion( + Uri executable, { + String argument = '--version', + int expectedExitCode = 0, + }) async { + final executablePath = executable.toFilePath(); + final process = await runProcess( + executable: executablePath, + arguments: [argument], + throwOnFailure: expectedExitCode == 0, + ); + if (process.exitCode != expectedExitCode) { + throw ToolError( + '`$executablePath $argument` returned unexpected exit code: ' + '${process.exitCode}.'); + } + return versionFromString(process.stdout)!; + } +} + +class PathVersionResolver implements ToolResolver { + ToolResolver wrappedResolver; + + PathVersionResolver({required this.wrappedResolver}); + + @override + Future> resolve() async { + final toolInstances = await wrappedResolver.resolve(); + + return [ + for (final toolInstance in toolInstances) lookupVersion(toolInstance) + ]; + } + + static ToolInstance lookupVersion(ToolInstance toolInstance) { + if (toolInstance.version != null) { + return toolInstance; + } + return toolInstance.copyWith( + version: version(toolInstance.uri), + ); + } + + static Version? version(Uri uri) { + final versionString = uri.pathSegments.where((e) => e != '').last; + final version = versionFromString(versionString); + return version; + } +} + +class ToolResolvers implements ToolResolver { + final List resolvers; + + ToolResolvers(this.resolvers); + + @override + Future> resolve() async => + [for (final resolver in resolvers) ...await resolver.resolve()]; +} + +class InstallLocationResolver implements ToolResolver { + final String toolName; + final List paths; + + InstallLocationResolver({ + required this.toolName, + required this.paths, + }); + + static const home = '\$HOME'; + + @override + Future> resolve() async => + [for (final path in paths) ...await tryResolvePath(path)] + .map((uri) => ToolInstance(tool: Tool(name: toolName), uri: uri)) + .toList(); + + Future> tryResolvePath(String path) async { + if (path.startsWith(home)) { + final homeDir_ = homeDir; + if (homeDir_ == null) { + return []; + } + path = path.replaceAll('$home/', homeDir!.path); + } + + final result = []; + final fileSystemEntities = await Glob(path).list().toList(); + for (final fileSystemEntity in fileSystemEntities) { + if (await fileSystemEntity.exists()) { + result.add(fileSystemEntity.uri); + } + } + return result; + } + + static final Uri? homeDir = () { + final path = + Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; + if (path == null) return null; + return Directory(path).uri; + }(); +} + +class RelativeToolResolver implements ToolResolver { + final String toolName; + final ToolResolver wrappedResolver; + final Uri relativePath; + + RelativeToolResolver({ + required this.toolName, + required this.wrappedResolver, + required this.relativePath, + }); + + @override + Future> resolve() async { + final otherToolInstances = await wrappedResolver.resolve(); + return [ + for (final toolInstance in otherToolInstances) + ToolInstance( + tool: Tool(name: toolName), + uri: toolInstance.uri.resolveUri(relativePath), + ), + ]; + } +} diff --git a/pkgs/c_compiler/lib/src/utils/run_process.dart b/pkgs/c_compiler/lib/src/utils/run_process.dart new file mode 100644 index 0000000000..3dcc1cbf21 --- /dev/null +++ b/pkgs/c_compiler/lib/src/utils/run_process.dart @@ -0,0 +1,155 @@ +// 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 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; + +/// Runs a process async and captures the exit code and standard out. +Future runProcess({ + required String executable, + required List arguments, + Uri? workingDirectory, + Map? environment, + bool throwOnFailure = true, +}) async { + final stdoutBuffer = []; + final stderrBuffer = []; + final stdoutCompleter = Completer(); + final stderrCompleter = Completer(); + final process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory?.toFilePath(), + environment: environment, + ); + + process.stdout.transform(utf8.decoder).listen( + stdoutBuffer.add, + onDone: stdoutCompleter.complete, + ); + process.stderr.transform(utf8.decoder).listen( + stderrBuffer.add, + onDone: stderrCompleter.complete, + ); + + final exitCode = await process.exitCode; + await stdoutCompleter.future; + final stdout = stdoutBuffer.join(); + await stderrCompleter.future; + final stderr = stderrBuffer.join(); + final result = RunProcessResult( + pid: process.pid, + command: '$executable ${arguments.join(' ')}', + exitCode: exitCode, + stdout: stdout, + stderr: stderr, + ); + if (throwOnFailure && result.exitCode != 0) { + throw Exception(result); + } + return result; +} + +class RunProcessResult extends ProcessResult { + final String command; + + final int _exitCode; + + // For some reason super.exitCode returns 0. + @override + int get exitCode => _exitCode; + + final String _stderrString; + + @override + String get stderr => _stderrString; + + final String _stdoutString; + + @override + String get stdout => _stdoutString; + + RunProcessResult({ + required int pid, + required this.command, + required int exitCode, + required String stderr, + required String stdout, + }) : _exitCode = exitCode, + _stderrString = stderr, + _stdoutString = stdout, + super(pid, exitCode, stdout, stderr); + + @override + String toString() => '''command: $command +exitCode: $exitCode +stdout: $stdout +stderr: $stderr'''; +} + +/// A task that when run executes a process. +class RunProcess { + final String executable; + final List arguments; + final Uri? workingDirectory; + final Map? environment; + final bool includeParentEnvironment; + final bool throwOnFailure; + + RunProcess({ + required this.executable, + this.arguments = const [], + this.workingDirectory, + this.environment, + this.includeParentEnvironment = true, + this.throwOnFailure = true, + }); + + String get commandString { + final printWorkingDir = + workingDirectory != null && workingDirectory != Directory.current.uri; + return [ + if (printWorkingDir) '(cd ${workingDirectory!.path};', + ...?environment?.entries.map((entry) => '${entry.key}=${entry.value}'), + executable, + ...arguments.map((a) => a.contains(' ') ? "'$a'" : a), + if (printWorkingDir) ')', + ].join(' '); + } + + Future run({Logger? logger}) async { + final workingDirectoryString = workingDirectory?.toFilePath(); + + logger?.info('Running `$commandString`.'); + final process = await Process.start( + executable, + arguments, + runInShell: true, + workingDirectory: workingDirectoryString, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + ).then((process) { + process.stdout + .transform(utf8.decoder) + .forEach((s) => logger?.fine(' $s')); + process.stderr + .transform(utf8.decoder) + .forEach((s) => logger?.severe(' $s')); + return process; + }); + final exitCode = await process.exitCode; + if (exitCode != 0) { + final message = + 'Command `$commandString` failed with exit code $exitCode.'; + logger?.severe(message); + if (throwOnFailure) { + throw Exception(message); + } + } + logger?.fine('Command `$commandString` done.'); + } +} diff --git a/pkgs/c_compiler/lib/src/utils/sem_version.dart b/pkgs/c_compiler/lib/src/utils/sem_version.dart new file mode 100644 index 0000000000..1ee493ff85 --- /dev/null +++ b/pkgs/c_compiler/lib/src/utils/sem_version.dart @@ -0,0 +1,18 @@ +// 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:pub_semver/pub_semver.dart'; + +Version? versionFromString(String containsVersion) { + final match = _semverRegex.firstMatch(containsVersion); + if (match == null) { + return null; + } + return Version(int.parse(match.group(1)!), int.parse(match.group(2)!), + int.parse(match.group(3)!), + pre: match.group(4), build: match.group(5)); +} + +final _semverRegex = RegExp( + r'(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?'); diff --git a/pkgs/c_compiler/pubspec.yaml b/pkgs/c_compiler/pubspec.yaml index b224592db8..c7b89d719f 100644 --- a/pkgs/c_compiler/pubspec.yaml +++ b/pkgs/c_compiler/pubspec.yaml @@ -1,11 +1,19 @@ name: c_compiler description: A library to invoke the native C compiler installed on the host machine. version: 0.1.0-dev -repository: https://github.com/dart-lang/native/c_compiler +repository: https://github.com/dart-lang/native/tree/main/pkgs/c_compiler environment: sdk: ">=2.19.3 <4.0.0" +publish_to: none + +dependencies: + cli_config: ^0.1.1 + glob: ^2.1.1 + logging: ^1.1.1 + pub_semver: ^2.1.3 + dev_dependencies: dart_flutter_team_lints: ^1.0.0 test: ^1.21.0 diff --git a/pkgs/c_compiler/test/c_compiler_test.dart b/pkgs/c_compiler/test/c_compiler_test.dart deleted file mode 100644 index 65006b3499..0000000000 --- a/pkgs/c_compiler/test/c_compiler_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -// 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:c_compiler/c_compiler.dart'; -import 'package:test/test.dart'; - -void main() { - group('A group of tests', () { - final awesome = Awesome(); - - setUp(() { - // Additional setup goes here. - }); - - test('First Test', () { - expect(awesome.isAwesome, isTrue); - }); - }); -} diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart new file mode 100644 index 0000000000..0d5050aced --- /dev/null +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -0,0 +1,87 @@ +// 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 'dart:io'; + +import 'package:c_compiler/c_compiler.dart'; +import 'package:c_compiler/src/cbuilder/target.dart'; +import 'package:cli_config/cli_config.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; + +void main() { + final logger = Logger('')..level = Level.ALL; + + const targets = [ + Target.androidArm, + Target.androidArm64, + Target.androidIA32, + Target.androidX64, + ]; + + const readElfMachine = { + Target.androidArm: 'ARM', + Target.androidArm64: 'AArch64', + Target.androidIA32: 'Intel 80386', + Target.androidX64: 'Advanced Micro Devices X86-64', + }; + + for (final packaging in ['dynamic', 'static']) { + for (final target in targets) { + test('Cbuilder $packaging library $target', () async { + await inTempDir((tempUri) async { + final packageUri = Directory.current.uri; + final addCUri = + packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); + final Uri libRelativeUri; + if (packaging == 'dynamic') { + libRelativeUri = Uri.file('libadd.so'); + } else { + libRelativeUri = Uri.file('libadd.a'); + } + + final config = Config(fileParsed: { + 'out_dir': tempUri.path, + 'target': target, + }); + + final cbuilder = CBuilder( + config: config, + logger: logger, + sources: [addCUri], + dynamicLibrary: packaging == 'dynamic' ? libRelativeUri : null, + staticLibrary: packaging == 'static' ? libRelativeUri : null, + ); + await cbuilder.run(); + + final libUri = tempUri.resolveUri(libRelativeUri); + final result = await Process.run('readelf', ['-h', libUri.path]); + expect(result.exitCode, 0); + final machine = (result.stdout as String) + .split('\n') + .firstWhere((e) => e.contains('Machine:')); + expect(machine, contains(readElfMachine[target])); + expect(result.exitCode, 0); + }); + }); + } + } +} + +const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; + +Future inTempDir( + Future Function(Uri tempUri) fun, { + String? prefix, +}) async { + final tempDir = await Directory.systemTemp.createTemp(prefix); + try { + await fun(tempDir.uri); + } finally { + if (!Platform.environment.containsKey(keepTempKey) || + Platform.environment[keepTempKey]!.isEmpty) { + await tempDir.delete(recursive: true); + } + } +} diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart new file mode 100644 index 0000000000..68f23c3a42 --- /dev/null +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -0,0 +1,87 @@ +// 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 'dart:io'; + +import 'package:c_compiler/c_compiler.dart'; +import 'package:c_compiler/src/cbuilder/target.dart'; +import 'package:cli_config/cli_config.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; + +void main() { + final logger = Logger('')..level = Level.ALL; + + const targets = [ + Target.linuxArm, + Target.linuxArm64, + Target.linuxIA32, + Target.linuxX64 + ]; + + const readElfMachine = { + Target.linuxArm: 'ARM', + Target.linuxArm64: 'AArch64', + Target.linuxIA32: 'Intel 80386', + Target.linuxX64: 'Advanced Micro Devices X86-64', + }; + + for (final packaging in ['dynamic', 'static']) { + for (final target in targets) { + test('Cbuilder $packaging library linux $target', () async { + await inTempDir((tempUri) async { + final packageUri = Directory.current.uri; + final addCUri = + packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); + final Uri libRelativeUri; + if (packaging == 'dynamic') { + libRelativeUri = Uri.file('libadd.so'); + } else { + libRelativeUri = Uri.file('libadd.a'); + } + + final config = Config(fileParsed: { + 'out_dir': tempUri.path, + 'target': target, + }); + + final cbuilder = CBuilder( + config: config, + logger: logger, + sources: [addCUri], + dynamicLibrary: packaging == 'dynamic' ? libRelativeUri : null, + staticLibrary: packaging == 'static' ? libRelativeUri : null, + ); + await cbuilder.run(); + + final libUri = tempUri.resolveUri(libRelativeUri); + final result = await Process.run('readelf', ['-h', libUri.path]); + expect(result.exitCode, 0); + final machine = (result.stdout as String) + .split('\n') + .firstWhere((e) => e.contains('Machine:')); + expect(machine, contains(readElfMachine[target])); + expect(result.exitCode, 0); + }); + }); + } + } +} + +const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; + +Future inTempDir( + Future Function(Uri tempUri) fun, { + String? prefix, +}) async { + final tempDir = await Directory.systemTemp.createTemp(prefix); + try { + await fun(tempDir.uri); + } finally { + if (!Platform.environment.containsKey(keepTempKey) || + Platform.environment[keepTempKey]!.isEmpty) { + await tempDir.delete(recursive: true); + } + } +} diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart new file mode 100644 index 0000000000..c260e41f09 --- /dev/null +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -0,0 +1,88 @@ +// 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 'dart:ffi'; +import 'dart:io'; + +import 'package:c_compiler/c_compiler.dart'; +import 'package:cli_config/cli_config.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; + +void main() { + final logger = Logger('')..level = Level.ALL; + + test('Cbuilder executable', () async { + await inTempDir((tempUri) async { + final packageUri = Directory.current.uri; + final helloWorldCUri = packageUri + .resolve('test/cbuilder/testfiles/hello_world/src/hello_world.c'); + if (!await File.fromUri(helloWorldCUri).exists()) { + throw Exception('Run the test from the root directory.'); + } + final executableRelativeUri = Uri.file('hello_world'); + + final config = Config(fileParsed: { + 'out_dir': tempUri.path, + }); + final cbuilder = CBuilder( + config: config, + logger: logger, + sources: [helloWorldCUri], + executable: executableRelativeUri, + ); + await cbuilder.run(); + + final executableUri = tempUri.resolveUri(executableRelativeUri); + expect(await File.fromUri(executableUri).exists(), true); + final result = await Process.run(executableUri.path, []); + expect(result.exitCode, 0); + expect(result.stdout, 'Hello world.\n'); + }); + }); + + test('Cbuilder dylib', () async { + await inTempDir((tempUri) async { + final packageUri = Directory.current.uri; + final addCUri = + packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); + final dylibRelativeUri = Uri.file('libadd.so'); + + final config = Config(fileParsed: { + 'out_dir': tempUri.path, + }); + + final cbuilder = CBuilder( + config: config, + logger: logger, + sources: [addCUri], + dynamicLibrary: dylibRelativeUri, + ); + await cbuilder.run(); + + final dylibUri = tempUri.resolveUri(dylibRelativeUri); + final dylib = DynamicLibrary.open(dylibUri.path); + final add = dylib.lookupFunction('add'); + expect(add(1, 2), 3); + }); + }); +} + +const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; + +Future inTempDir( + Future Function(Uri tempUri) fun, { + String? prefix, +}) async { + final tempDir = await Directory.systemTemp.createTemp(prefix); + try { + await fun(tempDir.uri); + } finally { + if (!Platform.environment.containsKey(keepTempKey) || + Platform.environment[keepTempKey]!.isEmpty) { + await tempDir.delete(recursive: true); + } + } +} diff --git a/pkgs/c_compiler/lib/src/c_compiler_base.dart b/pkgs/c_compiler/test/cbuilder/testfiles/add/src/add.c similarity index 69% rename from pkgs/c_compiler/lib/src/c_compiler_base.dart rename to pkgs/c_compiler/test/cbuilder/testfiles/add/src/add.c index 57ba552dc1..6bfba2fb9b 100644 --- a/pkgs/c_compiler/lib/src/c_compiler_base.dart +++ b/pkgs/c_compiler/test/cbuilder/testfiles/add/src/add.c @@ -2,7 +2,8 @@ // 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. -/// Checks if you are awesome. Spoiler: you are. -class Awesome { - bool get isAwesome => true; +#include + +int32_t add(int32_t a, int32_t b) { + return a+b; } diff --git a/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c b/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c new file mode 100644 index 0000000000..6af8a8b19d --- /dev/null +++ b/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c @@ -0,0 +1,10 @@ +// 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. + +#include + +int main() { + printf("Hello world.\n"); + return 0; +} \ No newline at end of file diff --git a/pkgs/c_compiler/test/native_toolchain/clang_test.dart b/pkgs/c_compiler/test/native_toolchain/clang_test.dart new file mode 100644 index 0000000000..490963e38c --- /dev/null +++ b/pkgs/c_compiler/test/native_toolchain/clang_test.dart @@ -0,0 +1,18 @@ +// 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:c_compiler/src/native_toolchain/clang.dart'; +import 'package:c_compiler/src/tool/tool_requirement.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + test('clang smoke test', () async { + final requirement = + ToolRequirement(clang, minimumVersion: Version(14, 0, 0)); + final resolved = await clang.defaultResolver!.resolve(); + final satisfied = requirement.satisfy(resolved); + print(satisfied); + }); +} diff --git a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart new file mode 100644 index 0000000000..146cdbb16a --- /dev/null +++ b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart @@ -0,0 +1,19 @@ +// 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:c_compiler/src/native_toolchain/android_ndk.dart'; +import 'package:c_compiler/src/tool/tool_requirement.dart'; +import 'package:test/test.dart'; + +void main() { + test('NDK smoke test', () async { + final requirement = RequireAll([ + ToolRequirement(androidNdk), + ToolRequirement(androidNdkClang), + ]); + final resolved = await androidNdk.defaultResolver!.resolve(); + final satisfied = requirement.satisfy(resolved); + print(satisfied); + }); +} diff --git a/pkgs/native_assets_cli/README.md b/pkgs/native_assets_cli/README.md index 7ca67f0876..706459f7c7 100644 --- a/pkgs/native_assets_cli/README.md +++ b/pkgs/native_assets_cli/README.md @@ -1,4 +1,4 @@ -[![package:native_assets_cli](https://github.com/dart-lang/native/actions/workflows/native_assets_cli.yml/badge.svg)](https://github.com/dart-lang/native/actions/workflows/native_assets_cli.yml) +[![package:native_assets_cli](https://github.com/dart-lang/native/actions/workflows/native_assets_cli.yaml/badge.svg)](https://github.com/dart-lang/native/actions/workflows/native_assets_cli.yaml) [![pub package](https://img.shields.io/pub/v/native_assets_cli.svg)](https://pub.dev/packages/native_assets_cli) [![Coverage Status](https://coveralls.io/repos/github/dart-lang/native/badge.svg?branch=main)](https://coveralls.io/github/dart-lang/tools?branch=main) diff --git a/pkgs/native_assets_cli/example/native_add/build.dart b/pkgs/native_assets_cli/example/native_add/build.dart new file mode 100644 index 0000000000..07c4aff12d --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/build.dart @@ -0,0 +1,56 @@ +// 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 'dart:io'; + +import 'package:c_compiler/c_compiler.dart'; +import 'package:cli_config/cli_config.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'native_add'; +const assetName = + 'package:$packageName/src/${packageName}_bindings_generated.dart'; +const sourcePaths = [ + 'src/$packageName.c', +]; + +void main(List args) async { + final logger = Logger(''); + final config = await Config.fromArgs(args: args); + final nativeAssetsConfig = BuildConfig.fromConfig(config); + final outDir = nativeAssetsConfig.outDir; + final packageRoot = nativeAssetsConfig.packageRoot; + await Directory.fromUri(outDir).create(recursive: true); + final packaging = nativeAssetsConfig.packaging.preferredPackaging.first; + final libUri = outDir.resolve( + nativeAssetsConfig.target.os.libraryFileName(packageName, packaging)); + final sources = [for (final path in sourcePaths) packageRoot.resolve(path)]; + + final task = CBuilder( + config: config, + logger: logger, + sources: sources, + dynamicLibrary: packaging == Packaging.dynamic ? libUri : null, + staticLibrary: packaging == Packaging.static ? libUri : null, + ); + await task.run(); + + final buildOutput = BuildOutput( + timestamp: DateTime.now(), + assets: [ + Asset( + name: assetName, + packaging: packaging, + target: nativeAssetsConfig.target, + path: AssetAbsolutePath(libUri), + ) + ], + dependencies: Dependencies([ + ...sources, + packageRoot.resolve('build.dart'), + ]), + ); + await buildOutput.writeToFile(outDir: outDir); +} diff --git a/pkgs/native_assets_cli/example/native_add/ffigen.yaml b/pkgs/native_assets_cli/example/native_add/ffigen.yaml new file mode 100644 index 0000000000..b8716bd2d5 --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/ffigen.yaml @@ -0,0 +1,20 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: NativeAddBindings +description: | + Bindings for `src/native_add.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: "lib/src/native_add_bindings_generated.dart" +headers: + entry-points: + - "src/native_add.h" + include-directives: + - "src/native_add.h" +preamble: | + // 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. +comments: + style: any + length: full +ffi-native: diff --git a/pkgs/native_assets_cli/example/native_add/lib/native_add.dart b/pkgs/native_assets_cli/example/native_add/lib/native_add.dart new file mode 100644 index 0000000000..9eda681ab4 --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/lib/native_add.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/native_add.dart'; diff --git a/pkgs/native_assets_cli/example/native_add/lib/src/native_add.dart b/pkgs/native_assets_cli/example/native_add/lib/src/native_add.dart new file mode 100644 index 0000000000..8d05091683 --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/lib/src/native_add.dart @@ -0,0 +1,9 @@ +// 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 'native_add_bindings_generated.dart' as bindings; + +// TODO(dacoharkes): This will fail lookup until the Dart SDK consumes the +// output of `build.dart`. +int add(int a, int b) => bindings.add(a, b); diff --git a/pkgs/native_assets_cli/example/native_add/lib/src/native_add_bindings_generated.dart b/pkgs/native_assets_cli/example/native_add/lib/src/native_add_bindings_generated.dart new file mode 100644 index 0000000000..2c09b35e0f --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/lib/src/native_add_bindings_generated.dart @@ -0,0 +1,16 @@ +// 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. + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +// ignore: deprecated_member_use +@ffi.FfiNative('add') +external int add( + int a, + int b, +); diff --git a/pkgs/native_assets_cli/example/native_add/pubspec.yaml b/pkgs/native_assets_cli/example/native_add/pubspec.yaml new file mode 100644 index 0000000000..86acd0dd0a --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/pubspec.yaml @@ -0,0 +1,22 @@ +publish_to: none + +name: native_add +description: Sums two numbers with native code. +version: 0.1.0 +repository: https://github.com/dcharkes/native_assets_cli + +environment: + sdk: ">=2.19.3 <4.0.0" + +dependencies: + c_compiler: + path: ../../../c_compiler/ + cli_config: ^0.1.1 + logging: ^1.1.1 + native_assets_cli: + path: ../../ + +dev_dependencies: + ffigen: ^7.2.8 + lints: ^2.0.0 + test: ^1.21.0 diff --git a/pkgs/native_assets_cli/lib/src/native_assets_cli_base.dart b/pkgs/native_assets_cli/example/native_add/src/native_add.c similarity index 69% rename from pkgs/native_assets_cli/lib/src/native_assets_cli_base.dart rename to pkgs/native_assets_cli/example/native_add/src/native_add.c index 57ba552dc1..cf21076d1d 100644 --- a/pkgs/native_assets_cli/lib/src/native_assets_cli_base.dart +++ b/pkgs/native_assets_cli/example/native_add/src/native_add.c @@ -2,7 +2,8 @@ // 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. -/// Checks if you are awesome. Spoiler: you are. -class Awesome { - bool get isAwesome => true; +#include "native_add.h" + +int32_t add(int32_t a, int32_t b) { + return a + b; } diff --git a/pkgs/native_assets_cli/example/native_add/src/native_add.h b/pkgs/native_assets_cli/example/native_add/src/native_add.h new file mode 100644 index 0000000000..5824f98a7d --- /dev/null +++ b/pkgs/native_assets_cli/example/native_add/src/native_add.h @@ -0,0 +1,13 @@ +// 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. + +#include + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_cli/lib/native_assets_cli.dart b/pkgs/native_assets_cli/lib/native_assets_cli.dart index e440c38a74..2986e641ef 100644 --- a/pkgs/native_assets_cli/lib/native_assets_cli.dart +++ b/pkgs/native_assets_cli/lib/native_assets_cli.dart @@ -4,6 +4,14 @@ /// A library that contains the argument and file formats for implementing a /// native assets CLI. -library; +library native_assets_cli; -export 'src/native_assets_cli_base.dart'; +export 'src/model/asset.dart'; +export 'src/model/build_config.dart'; +export 'src/model/build_output.dart'; +export 'src/model/dependencies.dart'; +export 'src/model/ios_sdk.dart'; +export 'src/model/metadata.dart'; +export 'src/model/packaging.dart'; +export 'src/model/packaging_preference.dart'; +export 'src/model/target.dart'; diff --git a/pkgs/native_assets_cli/lib/src/model/asset.dart b/pkgs/native_assets_cli/lib/src/model/asset.dart new file mode 100644 index 0000000000..c65084e6ff --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/asset.dart @@ -0,0 +1,324 @@ +// 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:yaml/yaml.dart'; + +import '../utils/uri.dart'; +import '../utils/yaml.dart'; +import 'packaging.dart'; +import 'target.dart'; + +abstract class AssetPath { + factory AssetPath(String pathType, Uri? uri) { + switch (pathType) { + case AssetAbsolutePath._pathTypeValue: + return AssetAbsolutePath(uri!); + case AssetRelativePath._pathTypeValue: + return AssetRelativePath(uri!); + case AssetSystemPath._pathTypeValue: + return AssetSystemPath(uri!); + case AssetInExecutable._pathTypeValue: + return AssetInExecutable(); + case AssetInProcess._pathTypeValue: + return AssetInProcess(); + } + throw FormatException('Unknown pathType: $pathType.'); + } + + factory AssetPath.fromYaml(YamlMap yamlMap) { + final pathType = yamlMap[_pathTypeKey] as String; + final uriString = yamlMap[_uriKey] as String?; + final uri = uriString != null ? Uri(path: uriString) : null; + return AssetPath(pathType, uri); + } + + Map toYaml(); + List toDartConst(); + + static const _pathTypeKey = 'path_type'; + static const _uriKey = 'uri'; + + Future exists(); +} + +/// Asset at absolute path [uri]. +class AssetAbsolutePath implements AssetPath { + final Uri uri; + + AssetAbsolutePath(this.uri); + + static const _pathTypeValue = 'absolute'; + + @override + Map toYaml() => { + AssetPath._pathTypeKey: _pathTypeValue, + AssetPath._uriKey: uri.toFilePath(), + }; + + @override + List toDartConst() => [_pathTypeValue, uri.toFilePath()]; + + @override + int get hashCode => uri.hashCode ^ 5; + + @override + bool operator ==(Object other) { + if (other is! AssetAbsolutePath) { + return false; + } + return uri == other.uri; + } + + @override + Future exists() => uri.fileSystemEntity.exists(); +} + +/// Asset is avaliable on a relative path. +/// +/// If [Packaging] of an [Asset] is [Packaging.dynamic], +/// `Platform.script.resolve(uri)` will be used to load the asset at runtime. +class AssetRelativePath implements AssetPath { + final Uri uri; + + AssetRelativePath(this.uri); + + static const _pathTypeValue = 'relative'; + + @override + Map toYaml() => { + AssetPath._pathTypeKey: _pathTypeValue, + AssetPath._uriKey: uri.toFilePath(), + }; + + @override + List toDartConst() => [_pathTypeValue, uri.toFilePath()]; + + @override + int get hashCode => uri.hashCode ^ 39; + + @override + bool operator ==(Object other) { + if (other is! AssetRelativePath) { + return false; + } + return uri == other.uri; + } + + @override + Future exists() => uri.fileSystemEntity.exists(); +} + +/// Asset is avaliable on the system `PATH`. +/// +/// [uri] only contains a file name. +class AssetSystemPath implements AssetPath { + final Uri uri; + + AssetSystemPath(this.uri); + + static const _pathTypeValue = 'system'; + + @override + Map toYaml() => { + AssetPath._pathTypeKey: _pathTypeValue, + AssetPath._uriKey: uri.toFilePath(), + }; + + @override + List toDartConst() => [_pathTypeValue, uri.toFilePath()]; + + @override + int get hashCode => uri.hashCode ^ 13; + + @override + bool operator ==(Object other) { + if (other is! AssetSystemPath) { + return false; + } + return uri == other.uri; + } + + @override + Future exists() => Future.value(true); +} + +/// Asset is loaded in the process and symbols are available through +/// `DynamicLibrary.process()`. +class AssetInProcess implements AssetPath { + AssetInProcess._(); + + static final AssetInProcess _singleton = AssetInProcess._(); + + factory AssetInProcess() => _singleton; + + static const _pathTypeValue = 'process'; + + @override + Map toYaml() => { + AssetPath._pathTypeKey: _pathTypeValue, + }; + + @override + List toDartConst() => [_pathTypeValue]; + + @override + Future exists() => Future.value(true); +} + +/// Asset is embedded in executable and symbols are available through +/// `DynamicLibrary.executable()`. +class AssetInExecutable implements AssetPath { + AssetInExecutable._(); + + static final AssetInExecutable _singleton = AssetInExecutable._(); + + factory AssetInExecutable() => _singleton; + + static const _pathTypeValue = 'executable'; + + @override + Map toYaml() => { + AssetPath._pathTypeKey: _pathTypeValue, + }; + + @override + List toDartConst() => [_pathTypeValue]; + + @override + Future exists() => Future.value(true); +} + +class Asset { + final Packaging packaging; + final String name; + final Target target; + final AssetPath path; + + Asset({ + required this.name, + required this.packaging, + required this.target, + required this.path, + }); + + factory Asset.fromYaml(YamlMap yamlMap) => Asset( + name: yamlMap[_nameKey] as String, + path: AssetPath.fromYaml(yamlMap[_pathKey] as YamlMap), + target: Target.fromString(yamlMap[_targetKey] as String), + packaging: Packaging.fromName(yamlMap[_packagingKey] as String), + ); + + static List listFromYamlString(String yaml) { + final yamlObject = loadYaml(yaml); + if (yamlObject == null) { + return []; + } + return [ + for (final yamlElement in yamlObject as YamlList) + Asset.fromYaml(yamlElement as YamlMap), + ]; + } + + static List listFromYamlList(YamlList yamlList) => [ + for (final yamlElement in yamlList) + Asset.fromYaml(yamlElement as YamlMap), + ]; + + Asset copyWith({ + Packaging? packaging, + String? name, + Target? target, + AssetPath? path, + }) => + Asset( + name: name ?? this.name, + packaging: packaging ?? this.packaging, + target: target ?? this.target, + path: path ?? this.path, + ); + + @override + bool operator ==(Object other) { + if (other is! Asset) { + return false; + } + return other.name == name && + other.packaging == packaging && + other.target == target && + other.path == path; + } + + @override + int get hashCode => + name.hashCode ^ packaging.hashCode ^ target.hashCode ^ path.hashCode; + + Map toYaml() => { + _nameKey: name, + _packagingKey: packaging.name, + _pathKey: path.toYaml(), + _targetKey: target.toString(), + }; + + Map> toDartConst() => { + name: path.toDartConst(), + }; + + String toYamlString() => yamlEncode(toYaml()); + + static const _nameKey = 'name'; + static const _packagingKey = 'packaging'; + static const _pathKey = 'path'; + static const _targetKey = 'target'; + + Future exists() => path.exists(); + + @override + String toString() => 'Asset(${toYaml()})'; +} + +extension AssetIterable on Iterable { + List toYaml() => [for (final item in this) item.toYaml()]; + + String toYamlString() => yamlEncode(toYaml()); + + Iterable wherePackaging(Packaging packaging) => + where((e) => e.packaging == packaging); + + Map> get assetsPerTarget { + final result = >{}; + for (final asset in this) { + final assets = result[asset.target] ?? []; + assets.add(asset); + result[asset.target] = assets; + } + return result; + } + + Map>> toDartConst() => { + for (final entry in assetsPerTarget.entries) + entry.key.toString(): + _combineMaps(entry.value.map((e) => e.toDartConst()).toList()) + }; + + Map toNativeAssetsFileEncoding() => { + 'format-version': [1, 0, 0], + 'native-assets': toDartConst(), + }; + + String toNativeAssetsFile() => yamlEncode(toNativeAssetsFileEncoding()); + + Future allExist() async { + final allResults = await Future.wait(map((e) => e.exists())); + final missing = allResults.contains(false); + return !missing; + } +} + +Map _combineMaps(Iterable> maps) { + final result = {}; + for (final map in maps) { + result.addAll(map); + } + return result; +} diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart new file mode 100644 index 0000000000..f6584e3f28 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -0,0 +1,221 @@ +// 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:cli_config/cli_config.dart'; +import 'package:collection/collection.dart'; + +import '../utils/map.dart'; +import '../utils/yaml.dart'; +import 'ios_sdk.dart'; +import 'metadata.dart'; +import 'packaging_preference.dart'; +import 'target.dart'; + +class BuildConfig { + /// The folder in which all output and intermediate artifacts should be + /// placed. + Uri get outDir => _outDir; + late final Uri _outDir; + + /// The root of the package the native assets are built for. + /// + /// Often a package's native assets are built because a package is a + /// dependency of another. For this it is convenient to know the packageRoot. + Uri get packageRoot => _packageRoot; + late final Uri _packageRoot; + + /// The target that is being compiled for. + Target get target => _target; + late final Target _target; + + /// When compiling for iOS, whether to target device or simulator. + /// + /// Required when [target.os] equals [OS.iOS]. + IOSSdk? get targetIOSSdk => _targetIOSSdk; + late final IOSSdk? _targetIOSSdk; + + /// Path to a C compiler. + Uri? get cc => _cc; + late final Uri? _cc; + + /// Path to a native linker. + Uri? get ld => _ld; + late final Uri? _ld; + + /// Preferred packaging method for library. + PackagingPreference get packaging => _packaging; + late final PackagingPreference _packaging; + + /// Metadata from direct dependencies. + /// + /// The key in the map is the package name of the dependency. + /// + /// The key in the nested map is the key for the metadata from the dependency. + Map? get dependencyMetadata => _dependencyMetadata; + late final Map? _dependencyMetadata; + + factory BuildConfig({ + required Uri outDir, + required Uri packageRoot, + required Target target, + IOSSdk? targetIOSSdk, + Uri? cc, + Uri? ld, + required PackagingPreference packaging, + Map? dependencyMetadata, + }) { + final nonValidated = BuildConfig._() + .._outDir = outDir + .._packageRoot = packageRoot + .._target = target + .._targetIOSSdk = targetIOSSdk + .._cc = cc + .._ld = ld + .._packaging = packaging + .._dependencyMetadata = dependencyMetadata; + final parsedConfigFile = nonValidated.toYaml(); + final config = Config(fileParsed: parsedConfigFile); + return BuildConfig.fromConfig(config); + } + + BuildConfig._(); + + factory BuildConfig.fromConfig(Config config) { + final result = BuildConfig._(); + final configExceptions = []; + for (final f in result._readFieldsFromConfig()) { + try { + f(config); + } on FormatException catch (e, st) { + configExceptions.add(e); + configExceptions.add(st); + } + } + + if (configExceptions.isNotEmpty) { + throw FormatException('Configuration is not in the right format. ' + 'FormatExceptions: $configExceptions'); + } + + return result; + } + + static const outDirConfigKey = 'out_dir'; + static const packageRootConfigKey = 'package_root'; + static const ccConfigKey = 'cc'; + static const ldConfigKey = 'ld'; + static const dependencyMetadataConfigKey = 'dependency_metadata'; + + List _readFieldsFromConfig() { + var targetSet = false; + return [ + (config) => _outDir = config.path(outDirConfigKey), + (config) => _packageRoot = config.path(packageRootConfigKey), + (config) { + _target = Target.fromString( + config.string( + Target.configKey, + validValues: Target.values.map((e) => '$e'), + ), + ); + targetSet = true; + }, + (config) => _targetIOSSdk = (targetSet && _target.os == OS.iOS) + ? IOSSdk.fromString( + config.string( + IOSSdk.configKey, + validValues: IOSSdk.values.map((e) => '$e'), + ), + ) + : null, + (config) => _cc = config.optionalPath(ccConfigKey, mustExist: true), + (config) => _ld = config.optionalPath(ldConfigKey, mustExist: true), + (config) => _packaging = PackagingPreference.fromString( + config.string( + PackagingPreference.configKey, + validValues: PackagingPreference.values.map((e) => '$e'), + ), + ), + (config) => + _dependencyMetadata = _readDependencyMetadataFromConfig(config) + ]; + } + + Map? _readDependencyMetadataFromConfig(Config config) { + final fileValue = + config.valueOf?>(dependencyMetadataConfigKey); + if (fileValue == null) { + return null; + } + final result = {}; + for (final entry in fileValue.entries) { + final packageName = entry.key; + final defines = entry.value; + if (defines is! Map) { + throw FormatException("Unexpected value '$defines' for key " + "'$dependencyMetadataConfigKey.$packageName' in config file. " + 'Expected a Map.'); + } + final packageResult = {}; + for (final entry2 in defines.entries) { + final key = entry2.key; + assert(key is String); + final value = entry2.value; + assert(value != null); + packageResult[key as String] = value as Object; + } + result[packageName as String] = Metadata(packageResult.sortOnKey()); + } + return result.sortOnKey(); + } + + Map toYaml() => { + outDirConfigKey: _outDir.path, + packageRootConfigKey: _packageRoot.path, + Target.configKey: _target.toString(), + if (_targetIOSSdk != null) IOSSdk.configKey: _targetIOSSdk.toString(), + if (_cc != null) ccConfigKey: _cc!.path, + if (_ld != null) ldConfigKey: _ld!.path, + PackagingPreference.configKey: _packaging.toString(), + if (_dependencyMetadata != null) + dependencyMetadataConfigKey: { + for (final entry in _dependencyMetadata!.entries) + entry.key: entry.value.toYaml(), + }, + }.sortOnKey(); + + String toYamlString() => yamlEncode(toYaml()); + + @override + bool operator ==(Object other) { + if (other is! BuildConfig) { + return false; + } + if (other._outDir != _outDir) return false; + if (other._packageRoot != _packageRoot) return false; + if (other._target != _target) return false; + if (other._targetIOSSdk != _targetIOSSdk) return false; + if (other._cc != _cc) return false; + if (other._ld != _ld) return false; + if (other._packaging != _packaging) return false; + if (!DeepCollectionEquality() + .equals(other._dependencyMetadata, _dependencyMetadata)) return false; + return true; + } + + // Ordering of fields doesn't matter. + @override + int get hashCode => + _outDir.hashCode ^ + _packageRoot.hashCode ^ + _target.hashCode ^ + _targetIOSSdk.hashCode ^ + _cc.hashCode ^ + _ld.hashCode ^ + _packaging.hashCode ^ + DeepCollectionEquality().hash(_dependencyMetadata); + + @override + String toString() => 'BuildConfig(${toYaml()})'; +} diff --git a/pkgs/native_assets_cli/lib/src/model/build_output.dart b/pkgs/native_assets_cli/lib/src/model/build_output.dart new file mode 100644 index 0000000000..35933b4fc3 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/build_output.dart @@ -0,0 +1,102 @@ +// 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 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:yaml/yaml.dart'; + +import '../utils/datetime.dart'; +import '../utils/file.dart'; +import '../utils/map.dart'; +import '../utils/yaml.dart'; +import 'asset.dart'; +import 'dependencies.dart'; +import 'metadata.dart'; + +class BuildOutput { + /// Time the build this output belongs to started. + /// + /// Rounded down to whole seconds, because [File.lastModified] is rounded + /// to whole seconds and caching logic compares these timestamps. + final DateTime timestamp; + final List assets; + final Dependencies dependencies; + final Metadata metadata; + + BuildOutput({ + required DateTime timestamp, + this.assets = const [], + this.dependencies = const Dependencies([]), + this.metadata = const Metadata({}), + }) : timestamp = timestamp.roundDownToSeconds(); + + static const _assetsKey = 'assets'; + static const _dependenciesKey = 'dependencies'; + static const _metadataKey = 'metadata'; + static const _timestampKey = 'timestamp'; + + factory BuildOutput.fromYamlString(String yaml) { + final yamlObject = loadYaml(yaml) as YamlMap; + return BuildOutput.fromYaml(yamlObject); + } + + factory BuildOutput.fromYaml(YamlMap yamlMap) => BuildOutput( + timestamp: DateTime.parse(yamlMap[_timestampKey] as String), + assets: Asset.listFromYamlList(yamlMap[_assetsKey] as YamlList), + dependencies: + Dependencies.fromYaml(yamlMap[_dependenciesKey] as YamlList?), + metadata: Metadata.fromYaml(yamlMap[_metadataKey] as YamlMap?), + ); + + Map toYaml() => { + _timestampKey: timestamp.toString(), + _assetsKey: assets.toYaml(), + _dependenciesKey: dependencies.toYaml(), + _metadataKey: metadata.toYaml(), + }..sortOnKey(); + + String toYamlString() => yamlEncode(toYaml()); + + static const fileName = 'build_output.yaml'; + + /// Writes the YAML file from [outDir]/[fileName]. + static Future readFromFile({required Uri outDir}) async { + final buildOutputUri = outDir.resolve(fileName); + final buildOutputFile = File.fromUri(buildOutputUri); + if (!await buildOutputFile.exists()) { + return null; + } + return BuildOutput.fromYamlString(await buildOutputFile.readAsString()); + } + + /// Writes the [toYamlString] to [outDir]/[fileName]. + Future writeToFile({required Uri outDir}) async { + final buildOutputUri = outDir.resolve(fileName); + await File.fromUri(buildOutputUri) + .writeAsStringCreateDirectory(toYamlString()); + } + + @override + String toString() => toYamlString(); + + @override + bool operator ==(Object other) { + if (other is! BuildOutput) { + return false; + } + return other.timestamp == timestamp && + ListEquality().equals(other.assets, assets) && + other.dependencies == dependencies && + other.metadata == metadata; + } + + @override + int get hashCode => Object.hash( + timestamp.hashCode, + ListEquality().hash(assets), + dependencies, + metadata, + ); +} diff --git a/pkgs/native_assets_cli/lib/src/model/dependencies.dart b/pkgs/native_assets_cli/lib/src/model/dependencies.dart new file mode 100644 index 0000000000..db372c9577 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/dependencies.dart @@ -0,0 +1,53 @@ +// 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:collection/collection.dart'; +import 'package:yaml/yaml.dart'; + +import '../utils/file.dart'; +import '../utils/uri.dart'; +import '../utils/yaml.dart'; + +class Dependencies { + final List dependencies; + + const Dependencies(this.dependencies); + + factory Dependencies.fromYamlString(String yamlString) { + final yaml = loadYaml(yamlString); + if (yaml is YamlList) { + return Dependencies.fromYaml(yaml); + } + return Dependencies([]); + } + + factory Dependencies.fromYaml(YamlList? yamlList) => Dependencies([ + if (yamlList != null) + for (final dependency in yamlList) + fileSystemPathToUri(dependency as String), + ]); + + List toYaml() => [ + for (final dependency in dependencies) dependency.path, + ]; + + String toYamlString() => yamlEncode(toYaml()); + + @override + String toString() => toYamlString(); + + Future lastModified() => + dependencies.map((u) => u.fileSystemEntity).lastModified(); + + @override + bool operator ==(Object other) { + if (other is! Dependencies) { + return false; + } + return ListEquality().equals(other.dependencies, dependencies); + } + + @override + int get hashCode => ListEquality().hash(dependencies); +} diff --git a/pkgs/native_assets_cli/lib/src/model/ios_sdk.dart b/pkgs/native_assets_cli/lib/src/model/ios_sdk.dart new file mode 100644 index 0000000000..7d3deeb01d --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/ios_sdk.dart @@ -0,0 +1,29 @@ +// 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. + +/// For an iOS target, a build is either done for the device or the simulator. +/// +/// Only fat binaries or xcframeworks can contain both targets. +class IOSSdk { + final String xcodebuildSdk; + + const IOSSdk._(this.xcodebuildSdk); + + static const iPhoneOs = IOSSdk._('iphoneos'); + static const iPhoneSimulator = IOSSdk._('iphonesimulator'); + + static const values = [ + iPhoneOs, + iPhoneSimulator, + ]; + + factory IOSSdk.fromString(String target) => + values.firstWhere((e) => e.xcodebuildSdk == target); + + /// The `package:config` key preferably used. + static const String configKey = 'target_ios_sdk'; + + @override + String toString() => xcodebuildSdk; +} diff --git a/pkgs/native_assets_cli/lib/src/model/metadata.dart b/pkgs/native_assets_cli/lib/src/model/metadata.dart new file mode 100644 index 0000000000..cf828d20aa --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/metadata.dart @@ -0,0 +1,41 @@ +// 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:collection/collection.dart'; +import 'package:yaml/yaml.dart'; + +import '../utils/map.dart'; +import '../utils/yaml.dart'; + +class Metadata { + final Map metadata; + + const Metadata(this.metadata); + + factory Metadata.fromYaml(YamlMap? yamlMap) => + Metadata(yamlMap?.cast() ?? {}); + + factory Metadata.fromYamlString(String yaml) { + final yamlObject = loadYaml(yaml) as YamlMap; + return Metadata.fromYaml(yamlObject); + } + + Map toYaml() => metadata..sortOnKey(); + + String toYamlString() => yamlEncode(toYaml()); + + @override + bool operator ==(Object other) { + if (other is! Metadata) { + return false; + } + return DeepCollectionEquality().equals(other.metadata, metadata); + } + + @override + int get hashCode => DeepCollectionEquality().hash(metadata); + + @override + String toString() => 'Metadata(${toYaml()})'; +} diff --git a/pkgs/native_assets_cli/lib/src/model/packaging.dart b/pkgs/native_assets_cli/lib/src/model/packaging.dart new file mode 100644 index 0000000000..5cc734a4ae --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/packaging.dart @@ -0,0 +1,24 @@ +// 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. + +class Packaging { + final String name; + + const Packaging._(this.name); + + static const Packaging dynamic = Packaging._('dynamic'); + static const Packaging static = Packaging._('static'); + + /// Known values for [Packaging]. + static const List values = [ + dynamic, + static, + ]; + + factory Packaging.fromName(String name) => + values.where((element) => element.name == name).first; + + @override + String toString() => name; +} diff --git a/pkgs/native_assets_cli/lib/src/model/packaging_preference.dart b/pkgs/native_assets_cli/lib/src/model/packaging_preference.dart new file mode 100644 index 0000000000..d4660ba2e0 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/packaging_preference.dart @@ -0,0 +1,77 @@ +// 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 'packaging.dart'; + +class PackagingPreference { + final String name; + final String description; + final List preferredPackaging; + final List potentialPackaging; + + const PackagingPreference( + this.name, + this.description, { + required this.preferredPackaging, + required this.potentialPackaging, + }); + + factory PackagingPreference.fromString(String name) => + values.where((element) => element.name == name).first; + + static const dynamic = PackagingPreference( + 'dynamic', + '''Provide native assets as dynamic libraries. +Fails if not all native assets can only be provided as static library. +Required to run Dart in JIT mode.''', + preferredPackaging: [Packaging.dynamic], + potentialPackaging: [Packaging.dynamic], + ); + static const static = PackagingPreference( + 'static', + '''Provide native assets as static libraries. +Fails if not all native assets can only be provided as dynamic library. +Required for potential link-time tree-shaking of native code. +Therefore, preferred to in Dart AOT mode.''', + preferredPackaging: [Packaging.static], + potentialPackaging: [Packaging.static], + ); + static const preferDynamic = PackagingPreference( + 'prefer-dynamic', + '''Provide native assets as dynamic libraries, if possible. +Otherwise, build native assets as static libraries.''', + preferredPackaging: [Packaging.dynamic], + potentialPackaging: Packaging.values, + ); + static const preferStatic = PackagingPreference( + 'prefer-static', + '''Provide native assets as static libraries, if possible. +Otherwise, build native assets as dynamic libraries. +Preferred for AOT compilation, if there are any native assets which can only be +provided as dynamic libraries.''', + preferredPackaging: [Packaging.static], + potentialPackaging: Packaging.values, + ); + static const all = PackagingPreference( + 'all', + '''Provide native assets as both dynamic and static libraries if supported. +Mostly useful for testing the build scripts.''', + preferredPackaging: Packaging.values, + potentialPackaging: Packaging.values, + ); + + static const values = [ + dynamic, + static, + preferDynamic, + preferStatic, + all, + ]; + + /// The `package:config` key preferably used. + static const String configKey = 'packaging'; + + @override + String toString() => name; +} diff --git a/pkgs/native_assets_cli/lib/src/model/target.dart b/pkgs/native_assets_cli/lib/src/model/target.dart new file mode 100644 index 0000000000..c9d0c204b4 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/target.dart @@ -0,0 +1,326 @@ +// 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 'dart:ffi' show Abi; +import 'dart:io'; + +import 'packaging.dart'; + +/// The hardware architectures the Dart VM runs on. +class Architecture { + /// This architecture as used in [Platform.version]. + final String dartPlatform; + + const Architecture._(this.dartPlatform); + + factory Architecture.fromAbi(Abi abi) => _abiToArch[abi]!; + + static const Architecture arm = Architecture._('arm'); + static const Architecture arm64 = Architecture._('arm64'); + static const Architecture ia32 = Architecture._('ia32'); + static const Architecture riscv32 = Architecture._('riscv32'); + static const Architecture riscv64 = Architecture._('riscv64'); + static const Architecture x64 = Architecture._('x64'); + + /// Known values for [Architecture]. + static const List values = [ + arm, + arm64, + ia32, + riscv32, + riscv64, + x64, + ]; + + static const _abiToArch = { + Abi.androidArm: Architecture.arm, + Abi.androidArm64: Architecture.arm64, + Abi.androidIA32: Architecture.ia32, + Abi.androidX64: Architecture.x64, + Abi.fuchsiaArm64: Architecture.arm64, + Abi.fuchsiaX64: Architecture.x64, + Abi.iosArm: Architecture.arm, + Abi.iosArm64: Architecture.arm64, + Abi.iosX64: Architecture.x64, + Abi.linuxArm: Architecture.arm, + Abi.linuxArm64: Architecture.arm64, + Abi.linuxIA32: Architecture.ia32, + Abi.linuxRiscv32: Architecture.riscv32, + Abi.linuxRiscv64: Architecture.riscv64, + Abi.linuxX64: Architecture.x64, + Abi.macosArm64: Architecture.arm64, + Abi.macosX64: Architecture.x64, + Abi.windowsArm64: Architecture.arm64, + Abi.windowsIA32: Architecture.ia32, + Abi.windowsX64: Architecture.x64, + }; +} + +/// The operating systems the Dart VM runs on. +class OS { + /// This OS as used in [Platform.version] + final String dartPlatform; + + const OS._(this.dartPlatform); + + factory OS.fromAbi(Abi abi) => _abiToOS[abi]!; + + static const OS android = OS._('android'); + static const OS fuchsia = OS._('fuchsia'); + static const OS iOS = OS._('ios'); + static const OS linux = OS._('linux'); + static const OS macOS = OS._('macos'); + static const OS windows = OS._('windows'); + + /// Known values for [OS]. + static const List values = [ + android, + fuchsia, + iOS, + linux, + macOS, + windows, + ]; + + static const _abiToOS = { + Abi.androidArm: OS.android, + Abi.androidArm64: OS.android, + Abi.androidIA32: OS.android, + Abi.androidX64: OS.android, + Abi.fuchsiaArm64: OS.fuchsia, + Abi.fuchsiaX64: OS.fuchsia, + Abi.iosArm: OS.iOS, + Abi.iosArm64: OS.iOS, + Abi.iosX64: OS.iOS, + Abi.linuxArm: OS.linux, + Abi.linuxArm64: OS.linux, + Abi.linuxIA32: OS.linux, + Abi.linuxRiscv32: OS.linux, + Abi.linuxRiscv64: OS.linux, + Abi.linuxX64: OS.linux, + Abi.macosArm64: OS.macOS, + Abi.macosX64: OS.macOS, + Abi.windowsArm64: OS.windows, + Abi.windowsIA32: OS.windows, + Abi.windowsX64: OS.windows, + }; + + /// Whether the [OS] is a OS for mobile devices. + bool get isMobile => this == OS.android || this == OS.iOS; + + /// Whether the [OS] is a OS for desktop devices. + bool get isDesktop => + this == OS.linux || this == OS.macOS || this == OS.windows; + + /// Typical cross compilation between OSes. + static const _osCrossCompilationDefault = { + OS.macOS: [OS.macOS, OS.iOS, OS.android], + OS.linux: [OS.linux, OS.android], + OS.windows: [OS.windows, OS.android], + }; + + /// The default dynamic library file name on this [OS]. + String dylibFileName(String name) { + final prefix = _dylibPrefix[this]!; + final extension = _dylibExtension[this]!; + return '$prefix$name.$extension'; + } + + /// The default static library file name on this [OS]. + String staticlibFileName(String name) { + final prefix = _staticlibPrefix[this]!; + final extension = _staticlibExtension[this]!; + return '$prefix$name.$extension'; + } + + String libraryFileName(String name, Packaging packaging) { + if (packaging == Packaging.dynamic) { + return dylibFileName(name); + } + assert(packaging == Packaging.static); + return staticlibFileName(name); + } + + /// The default executable file name on this [OS]. + String executableFileName(String name) { + final extension = _executableExtension[this]!; + final dot = extension.isNotEmpty ? '.' : ''; + return '$name$dot$extension'; + } + + /// The default name prefix for dynamic libraries per [OS]. + static const _dylibPrefix = { + OS.android: 'lib', + OS.fuchsia: 'lib', + OS.iOS: 'lib', + OS.linux: 'lib', + OS.macOS: 'lib', + OS.windows: '', + }; + + /// The default extension for dynamic libraries per [OS]. + static const _dylibExtension = { + OS.android: 'so', + OS.fuchsia: 'so', + OS.iOS: 'dylib', + OS.linux: 'so', + OS.macOS: 'dylib', + OS.windows: 'dll', + }; + + /// The default name prefix for static libraries per [OS]. + static const _staticlibPrefix = _dylibPrefix; + + /// The default extension for static libraries per [OS]. + static const _staticlibExtension = { + OS.android: 'a', + OS.fuchsia: 'a', + OS.iOS: 'a', + OS.linux: 'a', + OS.macOS: 'a', + OS.windows: 'lib', + }; + + /// The default extension for executables per [OS]. + static const _executableExtension = { + OS.android: '', + OS.fuchsia: '', + OS.iOS: '', + OS.linux: '', + OS.macOS: '', + OS.windows: 'exe', + }; +} + +/// Application binary interface. +/// +/// The Dart VM can run on a variety of [Target]s, see [Target.values]. +class Target implements Comparable { + final Abi abi; + + const Target._(this.abi); + + factory Target.fromString(String target) => _stringToTarget[target]!; + + /// The [Target] corresponding the substring of [Platform.version] + /// describing the [Target]. + /// + /// The [Platform.version] strings are formatted as follows: + /// ` () on ""`. + factory Target.fromDartPlatform(String versionStringFull) { + final split = versionStringFull.split('"'); + if (split.length < 2) { + throw FormatException( + "Unknown version from Platform.version '$versionStringFull'."); + } + final versionString = split[1]; + final target = _dartVMstringToTarget[versionString]; + if (target == null) { + throw FormatException("Unknown ABI '$versionString' from Platform.version" + " '$versionStringFull'."); + } + return target; + } + + static const androidArm = Target._(Abi.androidArm); + static const androidArm64 = Target._(Abi.androidArm64); + static const androidIA32 = Target._(Abi.androidIA32); + static const androidX64 = Target._(Abi.androidX64); + static const fuchsiaArm64 = Target._(Abi.fuchsiaArm64); + static const fuchsiaX64 = Target._(Abi.fuchsiaX64); + static const iOSArm = Target._(Abi.iosArm); + static const iOSArm64 = Target._(Abi.iosArm64); + static const linuxArm = Target._(Abi.linuxArm); + static const linuxArm64 = Target._(Abi.linuxArm64); + static const linuxIA32 = Target._(Abi.linuxIA32); + static const linuxRiscv32 = Target._(Abi.linuxRiscv32); + static const linuxRiscv64 = Target._(Abi.linuxRiscv64); + static const linuxX64 = Target._(Abi.linuxX64); + static const macOSArm64 = Target._(Abi.macosArm64); + static const macOSX64 = Target._(Abi.macosX64); + static const windowsIA32 = Target._(Abi.windowsIA32); + static const windowsX64 = Target._(Abi.windowsX64); + + /// All Targets that we can build for. + /// + /// Note that for some of these a Dart SDK is not available and they are only + /// used as target architectures for Flutter apps. + static const values = { + androidArm, + androidArm64, + androidIA32, + androidX64, + fuchsiaArm64, + fuchsiaX64, + iOSArm, + iOSArm64, + linuxArm, + linuxArm64, + linuxIA32, + linuxRiscv32, + linuxRiscv64, + linuxX64, + macOSArm64, + macOSX64, + windowsIA32, + windowsX64, + // TODO(dacoharkes): Add support for `wasm`. + }; + + /// Mapping from strings as used in [Target.toString] to [Target]s. + static final Map _stringToTarget = Map.fromEntries( + Target.values.map((target) => MapEntry(target.toString(), target))); + + /// Mapping from lowercased strings as used in [Platform.version] to + /// [Target]s. + static final Map _dartVMstringToTarget = Map.fromEntries( + Target.values.map((target) => MapEntry(target.dartVMToString(), target))); + + /// The current [Target]. + /// + /// Read from the [Platform.version] string. + static final Target current = Target.fromDartPlatform(Platform.version); + + Architecture get architecture => Architecture.fromAbi(abi); + + OS get os => OS.fromAbi(abi); + + String get _architectureString => architecture.dartPlatform; + + String get _osString => os.dartPlatform; + + /// A string representation of this object. + @override + String toString() => dartVMToString(); + + /// As used in [Platform.version]. + String dartVMToString() => '${_osString}_$_architectureString'; + + /// Compares `this` to [other]. + /// + /// If [other] is also an [Target], consistent with sorting on [toString]. + @override + int compareTo(Target other) => toString().compareTo(other.toString()); + + /// A list of supported target [Target]s from this host [os]. + List supportedTargetTargets( + {Map> osCrossCompilation = + OS._osCrossCompilationDefault}) => + Target.values + .where((target) => + // Only valid cross compilation. + osCrossCompilation[os]!.contains(target.os) && + // And no deprecated architectures. + target != Target.iOSArm) + .sorted; + + /// The `package:config` key preferably used. + static const String configKey = 'target'; +} + +/// Common methods for manipulating iterables of [Target]s. +extension TargetList on Iterable { + /// The [Target]s in `this` sorted by name alphabetically. + List get sorted => [for (final target in this) target]..sort(); +} diff --git a/pkgs/native_assets_cli/lib/src/utils/datetime.dart b/pkgs/native_assets_cli/lib/src/utils/datetime.dart new file mode 100644 index 0000000000..24842c131a --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/utils/datetime.dart @@ -0,0 +1,9 @@ +// 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. + +extension DateTimeExtension on DateTime { + DateTime roundDownToSeconds() => + DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch - + millisecondsSinceEpoch % Duration(seconds: 1).inMilliseconds); +} diff --git a/pkgs/native_assets_cli/lib/src/utils/file.dart b/pkgs/native_assets_cli/lib/src/utils/file.dart new file mode 100644 index 0000000000..703f601e50 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/utils/file.dart @@ -0,0 +1,65 @@ +// 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 'dart:convert'; +import 'dart:io'; + +extension FileExtension on File { + Future writeAsStringCreateDirectory(String contents, + {FileMode mode = FileMode.write, + Encoding encoding = utf8, + bool flush = false}) async { + if (!await parent.exists()) { + await parent.create(recursive: true); + } + return await writeAsString(contents, + mode: mode, encoding: encoding, flush: flush); + } +} + +extension FileSystemEntityExtension on FileSystemEntity { + Future lastModified() async { + final this_ = this; + if (this_ is Link || await FileSystemEntity.isLink(this_.path)) { + // Don't follow links. + return DateTime.fromMicrosecondsSinceEpoch(0); + } + if (this_ is File) { + if (!await this_.exists()) { + // If the file was deleted, regard it is modified recently. + return DateTime.now(); + } + return await this_.lastModified(); + } + assert(this_ is Directory); + this_ as Directory; + return await this_.lastModified(); + } +} + +extension FileSystemEntityIterable on Iterable { + Future lastModified() async { + var last = DateTime.fromMillisecondsSinceEpoch(0); + for (final entity in this) { + final entityTimestamp = await entity.lastModified(); + if (entityTimestamp.isAfter(last)) { + last = entityTimestamp; + } + } + return last; + } +} + +extension DirectoryExtension on Directory { + Future lastModified() async { + var last = DateTime.fromMillisecondsSinceEpoch(0); + await for (final entity in list()) { + final entityTimestamp = await entity.lastModified(); + if (entityTimestamp.isAfter(last)) { + last = entityTimestamp; + } + } + return last; + } +} diff --git a/pkgs/native_assets_cli/lib/src/utils/map.dart b/pkgs/native_assets_cli/lib/src/utils/map.dart new file mode 100644 index 0000000000..bb767ed991 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/utils/map.dart @@ -0,0 +1,15 @@ +// 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. + +extension MapSorting, V extends Object> on Map { + Map sortOnKey() { + final result = {}; + final keysSorted = keys.toList()..sort(); + for (final key in keysSorted) { + final value = this[key]!; + result[key] = value; + } + return result; + } +} diff --git a/pkgs/native_assets_cli/lib/src/utils/uri.dart b/pkgs/native_assets_cli/lib/src/utils/uri.dart new file mode 100644 index 0000000000..ee4fd032a7 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/utils/uri.dart @@ -0,0 +1,21 @@ +// 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 'dart:io'; + +extension UriExtension on Uri { + FileSystemEntity get fileSystemEntity { + if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) { + return Directory.fromUri(this); + } + return File.fromUri(this); + } +} + +Uri fileSystemPathToUri(String path) { + if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) { + return Uri.directory(path); + } + return Uri.file(path); +} diff --git a/pkgs/native_assets_cli/lib/src/utils/yaml.dart b/pkgs/native_assets_cli/lib/src/utils/yaml.dart new file mode 100644 index 0000000000..b62746bb12 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/utils/yaml.dart @@ -0,0 +1,18 @@ +// 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:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +String yamlEncode(Object yamlEncoding) { + final editor = YamlEditor(''); + editor.update( + [], + wrapAsYamlNode( + yamlEncoding, + collectionStyle: CollectionStyle.BLOCK, + ), + ); + return editor.toString(); +} diff --git a/pkgs/native_assets_cli/pubspec.yaml b/pkgs/native_assets_cli/pubspec.yaml index 5039b5984f..53966c5cc5 100644 --- a/pkgs/native_assets_cli/pubspec.yaml +++ b/pkgs/native_assets_cli/pubspec.yaml @@ -1,11 +1,19 @@ name: native_assets_cli description: A library that contains the argument and file formats for implementing a native assets CLI. version: 0.1.0-dev -repository: https://github.com/dart-lang/native/native_assets_cli +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli environment: sdk: ">=2.19.3 <4.0.0" +publish_to: none + +dependencies: + cli_config: ^0.1.1 + collection: ^1.17.1 + yaml: ^3.1.1 + yaml_edit: ^2.1.0 + dev_dependencies: dart_flutter_team_lints: ^1.0.0 test: ^1.21.0 diff --git a/pkgs/native_assets_cli/test/example/native_add_test.dart b/pkgs/native_assets_cli/test/example/native_add_test.dart new file mode 100644 index 0000000000..d2be5a27fd --- /dev/null +++ b/pkgs/native_assets_cli/test/example/native_add_test.dart @@ -0,0 +1,61 @@ +// 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 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() async { + late Uri tempUri; + final packageUri = Directory.current.uri; + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + test('native_add build', () async { + final testTempUri = tempUri.resolve('test1/'); + await Directory.fromUri(testTempUri).create(); + final testPackageUri = packageUri.resolve('example/native_add/'); + final dartUri = Uri.file(Platform.resolvedExecutable); + + final processResult = await Process.run( + dartUri.path, + [ + 'build.dart', + '-Dout_dir=${tempUri.path}', + '-Dpackage_root=${testPackageUri.path}', + '-Dtarget=linux_x64', + '-Dpackaging=dynamic', + ], + workingDirectory: testPackageUri.path, + ); + if (processResult.exitCode != 0) { + print(processResult.stdout); + print(processResult.stderr); + print(processResult.exitCode); + } + expect(processResult.exitCode, 0); + + final buildOutputUri = tempUri.resolve('build_output.yaml'); + final buildOutput = BuildOutput.fromYamlString( + await File.fromUri(buildOutputUri).readAsString()); + final assets = buildOutput.assets; + expect(assets.length, 1); + final dependencies = buildOutput.dependencies; + expect(await assets.allExist(), true); + expect( + dependencies.dependencies, + [ + testPackageUri.resolve('src/native_add.c'), + testPackageUri.resolve('build.dart'), + ], + ); + }); +} diff --git a/pkgs/native_assets_cli/test/model/asset_test.dart b/pkgs/native_assets_cli/test/model/asset_test.dart new file mode 100644 index 0000000000..3a12620185 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/asset_test.dart @@ -0,0 +1,186 @@ +// 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:collection/collection.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() { + final assets = [ + Asset( + name: 'foo', + path: AssetAbsolutePath(Uri(path: 'path/to/libfoo.so')), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + Asset( + name: 'foo2', + path: AssetRelativePath(Uri(path: 'path/to/libfoo2.so')), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + Asset( + name: 'foo3', + path: AssetSystemPath(Uri(path: 'libfoo3.so')), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + Asset( + name: 'foo4', + path: AssetInExecutable(), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + Asset( + name: 'foo5', + path: AssetInProcess(), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + Asset( + name: 'bar', + path: AssetAbsolutePath(Uri(path: 'path/to/libbar.a')), + target: Target.linuxArm64, + packaging: Packaging.static, + ), + Asset( + name: 'bla', + path: AssetAbsolutePath(Uri(path: 'path/with spaces/bla.dll')), + target: Target.windowsX64, + packaging: Packaging.dynamic, + ), + ]; + + const assetsYamlEncoding = '''- name: foo + packaging: dynamic + path: + path_type: absolute + uri: path/to/libfoo.so + target: android_x64 +- name: foo2 + packaging: dynamic + path: + path_type: relative + uri: path/to/libfoo2.so + target: android_x64 +- name: foo3 + packaging: dynamic + path: + path_type: system + uri: libfoo3.so + target: android_x64 +- name: foo4 + packaging: dynamic + path: + path_type: executable + target: android_x64 +- name: foo5 + packaging: dynamic + path: + path_type: process + target: android_x64 +- name: bar + packaging: static + path: + path_type: absolute + uri: path/to/libbar.a + target: linux_arm64 +- name: bla + packaging: dynamic + path: + path_type: absolute + uri: path/with spaces/bla.dll + target: windows_x64'''; + + const assetsDartEncoding = '''format-version: + - 1 + - 0 + - 0 +native-assets: + android_x64: + foo: + - absolute + - path/to/libfoo.so + foo2: + - relative + - path/to/libfoo2.so + foo3: + - system + - libfoo3.so + foo4: + - executable + foo5: + - process + linux_arm64: + bar: + - absolute + - path/to/libbar.a + windows_x64: + bla: + - absolute + - path/with spaces/bla.dll'''; + + test('asset yaml', () { + final yaml = assets.toYamlString().replaceAll('\\', '/'); + expect(yaml, assetsYamlEncoding); + final assets2 = Asset.listFromYamlString(yaml); + expect(assets, assets2); + }); + + test('asset yaml', () async { + final fileContents = assets.toNativeAssetsFile(); + expect(fileContents.replaceAll('\\', '/'), assetsDartEncoding); + }); + + test('AssetPath factory', () async { + expect(() => AssetPath('wrong', null), throwsFormatException); + }); + + test('Asset hashCode copyWith', () async { + final asset = assets.first; + final asset2 = asset.copyWith(name: 'foo321'); + expect(asset.hashCode != asset2.hashCode, true); + + final asset3 = asset.copyWith(); + expect(asset.hashCode, asset3.hashCode); + }); + + test('List hashCode', () async { + final assets2 = assets.take(3).toList(); + final equality = ListEquality(); + expect(equality.hash(assets) != equality.hash(assets2), true); + }); + + test('List wherePackaging', () async { + final assets2 = assets.wherePackaging(Packaging.dynamic); + expect(assets2.length, 6); + }); + + test('Asset toString', () async { + assets.toString(); + }); + + test('Asset toString', () async { + expect(await assets.allExist(), false); + }); + + test('Asset toYaml', () async { + expect( + assets.first.toYamlString(), + ''' +name: foo +packaging: dynamic +path: + path_type: absolute + uri: path/to/libfoo.so +target: android_x64 +''' + .trim()); + }); + + test('Asset listFromYamlString', () async { + final assets = Asset.listFromYamlString(''); + expect(assets, []); + }); +} diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart new file mode 100644 index 0000000000..9c3c619fb9 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/build_config_test.dart @@ -0,0 +1,232 @@ +// 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 'dart:io'; + +import 'package:cli_config/cli_config.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() async { + late Uri tempUri; + late Uri fakeClang; + late Uri fakeLd; + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + fakeClang = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClang).create(); + fakeLd = tempUri.resolve('fake_ld'); + await File.fromUri(fakeLd).create(); + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + test('BuildConfig ==', () { + final config1 = BuildConfig( + outDir: tempUri.resolve('out1/'), + packageRoot: tempUri, + target: Target.iOSArm64, + targetIOSSdk: IOSSdk.iPhoneOs, + cc: fakeClang, + ld: fakeLd, + packaging: PackagingPreference.preferStatic, + ); + + final config2 = BuildConfig( + outDir: tempUri.resolve('out2/'), + packageRoot: tempUri, + target: Target.androidArm64, + packaging: PackagingPreference.preferStatic, + ); + + expect(config1, equals(config1)); + expect(config1 == config2, false); + expect(config1.outDir != config2.outDir, true); + expect(config1.packageRoot, config2.packageRoot); + expect(config1.target != config2.target, true); + expect(config1.targetIOSSdk != config2.targetIOSSdk, true); + expect(config1.cc != config2.cc, true); + expect(config1.ld != config2.ld, true); + expect(config1.packaging, config2.packaging); + expect(config1.dependencyMetadata, config2.dependencyMetadata); + }); + + test('BuildConfig fromConfig', () { + final nativeAssetsCliConfig2 = BuildConfig( + outDir: tempUri.resolve('out2/'), + packageRoot: tempUri.resolve('packageRoot/'), + target: Target.androidArm64, + packaging: PackagingPreference.preferStatic, + ); + + final config = Config(fileParsed: { + 'out_dir': tempUri.resolve('out2/').path, + 'package_root': tempUri.resolve('packageRoot/').path, + 'target': 'android_arm64', + 'packaging': 'prefer-static', + }); + + final fromConfig = BuildConfig.fromConfig(config); + expect(fromConfig, equals(nativeAssetsCliConfig2)); + }); + + test('BuildConfig toYaml fromConfig', () { + final nativeAssetsCliConfig1 = BuildConfig( + outDir: tempUri.resolve('out1/'), + packageRoot: tempUri.resolve('packageRoot/'), + target: Target.iOSArm64, + targetIOSSdk: IOSSdk.iPhoneOs, + cc: fakeClang, + ld: fakeLd, + packaging: PackagingPreference.preferStatic, + ); + + final configFile = nativeAssetsCliConfig1.toYaml(); + final config = Config(fileParsed: configFile); + final fromConfig = BuildConfig.fromConfig(config); + expect(fromConfig, equals(nativeAssetsCliConfig1)); + }); + + test('BuildConfig == dependency metadata', () { + final nativeAssetsCliConfig1 = BuildConfig( + outDir: tempUri.resolve('out1/'), + packageRoot: tempUri, + target: Target.androidArm64, + packaging: PackagingPreference.preferStatic, + dependencyMetadata: { + 'bar': Metadata({ + 'key': 'value', + 'foo': ['asdf', 'fdsa'], + }), + 'foo': Metadata({ + 'key': 321, + }), + }, + ); + + final nativeAssetsCliConfig2 = BuildConfig( + outDir: tempUri.resolve('out1/'), + packageRoot: tempUri, + target: Target.androidArm64, + packaging: PackagingPreference.preferStatic, + dependencyMetadata: { + 'bar': Metadata({ + 'key': 'value', + }), + 'foo': Metadata({ + 'key': 123, + }), + }, + ); + + expect(nativeAssetsCliConfig1, equals(nativeAssetsCliConfig1)); + expect(nativeAssetsCliConfig1 == nativeAssetsCliConfig2, false); + expect(nativeAssetsCliConfig1.hashCode == nativeAssetsCliConfig2.hashCode, + false); + }); + + test('BuildConfig toYaml fromYaml', () { + final outDir = tempUri.resolve('out1/'); + final nativeAssetsCliConfig1 = BuildConfig( + outDir: outDir, + packageRoot: tempUri, + target: Target.iOSArm64, + targetIOSSdk: IOSSdk.iPhoneOs, + cc: fakeClang, + ld: fakeLd, + packaging: PackagingPreference.preferStatic, + // This map should be sorted on key for two layers. + dependencyMetadata: { + 'foo': Metadata({ + 'z': ['z', 'a'], + 'a': 321, + }), + 'bar': Metadata({ + 'key': 'value', + }), + }, + ); + final yamlString = nativeAssetsCliConfig1.toYamlString(); + final expectedYamlString = '''cc: ${fakeClang.path} +dependency_metadata: + bar: + key: value + foo: + a: 321 + z: + - z + - a +ld: ${fakeLd.path} +out_dir: ${outDir.path} +package_root: ${tempUri.path} +packaging: prefer-static +target: ios_arm64 +target_ios_sdk: iphoneos'''; + expect(yamlString, equals(expectedYamlString)); + + final buildConfig2 = BuildConfig.fromConfig( + Config.fromConfigFileContents( + fileContents: yamlString, + ), + ); + expect(buildConfig2, nativeAssetsCliConfig1); + }); + + test('BuildConfig FormatExceptions', () { + expect( + () => BuildConfig.fromConfig(Config(fileParsed: {})), + throwsFormatException, + ); + expect( + () => BuildConfig.fromConfig(Config(fileParsed: { + 'package_root': tempUri.resolve('packageRoot/').path, + 'target': 'android_arm64', + 'packaging': 'prefer-static', + })), + throwsFormatException, + ); + expect( + () => BuildConfig.fromConfig(Config(fileParsed: { + 'out_dir': tempUri.resolve('out2/').path, + 'package_root': tempUri.resolve('packageRoot/').path, + 'target': 'android_arm64', + 'packaging': 'prefer-static', + 'dependency_metadata': { + 'bar': {'key': 'value'}, + 'foo': [], + }, + })), + throwsFormatException, + ); + }); + + test('FormatExceptions contain full stack trace of wrapped exception', () { + try { + BuildConfig.fromConfig(Config(fileParsed: { + 'out_dir': tempUri.resolve('out2/').path, + 'package_root': tempUri.resolve('packageRoot/').path, + 'target': [1, 2, 3, 4, 5], + 'packaging': 'prefer-static', + })); + } on FormatException catch (e) { + expect(e.toString(), stringContainsInOrder(['Config.string'])); + } + }); + + test('BuildConfig toString', () { + final config = BuildConfig( + outDir: tempUri.resolve('out1/'), + packageRoot: tempUri, + target: Target.iOSArm64, + targetIOSSdk: IOSSdk.iPhoneOs, + cc: fakeClang, + ld: fakeLd, + packaging: PackagingPreference.preferStatic, + ); + config.toString(); + }); +} diff --git a/pkgs/native_assets_cli/test/model/build_output_test.dart b/pkgs/native_assets_cli/test/model/build_output_test.dart new file mode 100644 index 0000000000..6751416b6f --- /dev/null +++ b/pkgs/native_assets_cli/test/model/build_output_test.dart @@ -0,0 +1,97 @@ +// 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 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() { + late Uri tempUri; + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + final buildOutput = BuildOutput( + timestamp: DateTime.parse('2022-11-10 13:25:01.000'), + assets: [ + Asset( + name: 'foo', + path: AssetAbsolutePath(Uri(path: 'path/to/libfoo.so')), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + Asset( + name: 'foo2', + path: AssetRelativePath(Uri(path: 'path/to/libfoo2.so')), + target: Target.androidX64, + packaging: Packaging.dynamic, + ), + ], + dependencies: Dependencies([ + Uri.file('path/to/file.ext'), + ]), + metadata: Metadata({ + 'key': 'value', + }), + ); + + const yamlEncoding = '''timestamp: 2022-11-10 13:25:01.000 +assets: + - name: foo + packaging: dynamic + path: + path_type: absolute + uri: path/to/libfoo.so + target: android_x64 + - name: foo2 + packaging: dynamic + path: + path_type: relative + uri: path/to/libfoo2.so + target: android_x64 +dependencies: + - path/to/file.ext +metadata: + key: value'''; + + test('built info yaml', () { + final yaml = buildOutput.toYamlString().replaceAll('\\', '/'); + expect(yaml, yamlEncoding); + final buildOutput2 = BuildOutput.fromYamlString(yaml); + expect(buildOutput.hashCode, buildOutput2.hashCode); + expect(buildOutput, buildOutput2); + }); + + test('BuildOutput.toString', buildOutput.toString); + + test('BuildOutput.hashCode', () { + final buildOutput2 = BuildOutput.fromYamlString(yamlEncoding); + expect(buildOutput.hashCode, buildOutput2.hashCode); + + final buildOutput3 = BuildOutput( + timestamp: DateTime.parse('2022-11-10 13:25:01.000'), + ); + expect(buildOutput.hashCode != buildOutput3.hashCode, true); + }); + + test('BuildOutput.readFromFile BuildOutput.writeToFile', () async { + final outDir = tempUri.resolve('out_dir/'); + await buildOutput.writeToFile(outDir: outDir); + final buildOutput2 = await BuildOutput.readFromFile(outDir: outDir); + expect(buildOutput2, buildOutput); + }); + + test('Round timestamp', () { + final buildOutput3 = BuildOutput( + timestamp: DateTime.parse('2022-11-10 13:25:01.372257'), + ); + expect(buildOutput3.timestamp, DateTime.parse('2022-11-10 13:25:01.000')); + }); +} diff --git a/pkgs/native_assets_cli/test/model/dependencies_test.dart b/pkgs/native_assets_cli/test/model/dependencies_test.dart new file mode 100644 index 0000000000..e707e949f8 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/dependencies_test.dart @@ -0,0 +1,84 @@ +// 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 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() { + late Uri tempUri; + + setUp(() async => tempUri = (await Directory.systemTemp.createTemp()).uri); + + tearDown( + () async => await Directory.fromUri(tempUri).delete(recursive: true)); + + final dependencies = Dependencies([ + Uri.file('src/bar.c'), + Uri.file('src/baz.c'), + Uri.directory('src/bla/'), + Uri.file('build.dart'), + ]); + + const yamlEncoding = '''- src/bar.c +- src/baz.c +- src/bla/ +- build.dart'''; + + test('dependencies yaml', () { + final yaml = dependencies.toYamlString().replaceAll('\\', '/'); + expect(yaml, yamlEncoding); + final dependencies2 = Dependencies.fromYamlString(yaml); + expect(dependencies.hashCode, dependencies2.hashCode); + expect(dependencies, dependencies2); + }); + + test('dependencies toString', dependencies.toString); + + test('dependencies fromYamlString', () { + final dependencies = Dependencies.fromYamlString(''); + expect(dependencies, Dependencies([])); + }); + + test('dependencies lastModified', () async { + final dirUri = tempUri.resolve('foo/'); + final dir = Directory.fromUri(dirUri); + await dir.create(); + final fileUri = tempUri.resolve('bla.c'); + final file = File.fromUri(fileUri); + await file.writeAsString('dummy contents'); + final dependencies = Dependencies([dirUri, fileUri]); + expect(await dependencies.lastModified(), await file.lastModified()); + }); + + test('dependencies lastModified symlinks', () async { + final symlink = Link.fromUri(tempUri.resolve('my_link')); + await symlink.create(tempUri.toFilePath()); + + final someFileUri = tempUri.resolve('foo.txt'); + final someFile = File.fromUri(someFileUri); + await someFile.writeAsString('yay!'); + + final dependencies = Dependencies([tempUri]); + expect(await dependencies.lastModified(), await someFile.lastModified()); + }); + + test('dependencies lastModified does not exist', () async { + final someFileUri = tempUri.resolve('foo.txt'); + final someFile = File.fromUri(someFileUri); + await someFile.writeAsString('yay!'); + + final deletedFileUri = tempUri.resolve('bar.txt'); + + final now = DateTime.now(); + + final dependencies = Dependencies([ + someFileUri, + deletedFileUri, + ]); + final depsLastModified = await dependencies.lastModified(); + expect(depsLastModified == now || depsLastModified.isAfter(now), true); + }); +} diff --git a/pkgs/native_assets_cli/test/model/metadata_test.dart b/pkgs/native_assets_cli/test/model/metadata_test.dart new file mode 100644 index 0000000000..3e1f933262 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/metadata_test.dart @@ -0,0 +1,26 @@ +// 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:collection/collection.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() { + final metadata = Metadata({ + 'key': 'value', + 'my_list': [1, 2, 3], + 'my_map': { + 3: 4, + 'foo': 'bar', + }, + }); + + test('Metadata toString', metadata.toString); + + test('Metadata toYamlString fromYamlString', () { + final yamlString = metadata.toYamlString(); + final metadata2 = Metadata.fromYamlString(yamlString); + expect(metadata2, metadata); + }); +} diff --git a/pkgs/native_assets_cli/test/native_assets_cli_test.dart b/pkgs/native_assets_cli/test/model/packaging_test.dart similarity index 62% rename from pkgs/native_assets_cli/test/native_assets_cli_test.dart rename to pkgs/native_assets_cli/test/model/packaging_test.dart index a0de8d3ea3..398f760ef1 100644 --- a/pkgs/native_assets_cli/test/native_assets_cli_test.dart +++ b/pkgs/native_assets_cli/test/model/packaging_test.dart @@ -6,15 +6,7 @@ import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; void main() { - group('A group of tests', () { - final awesome = Awesome(); - - setUp(() { - // Additional setup goes here. - }); - - test('First Test', () { - expect(awesome.isAwesome, isTrue); - }); + test('Packaging toString', () async { + Packaging.static.toString(); }); } diff --git a/pkgs/native_assets_cli/test/model/target_test.dart b/pkgs/native_assets_cli/test/model/target_test.dart new file mode 100644 index 0000000000..7a6e3ee94f --- /dev/null +++ b/pkgs/native_assets_cli/test/model/target_test.dart @@ -0,0 +1,49 @@ +// 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 'dart:ffi'; +import 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +void main() { + test('OS accessors', () async { + expect(OS.android.isDesktop, false); + expect(OS.android.isMobile, true); + }); + + test('OS naming conventions', () async { + expect(OS.android.dylibFileName('foo'), 'libfoo.so'); + expect(OS.android.staticlibFileName('foo'), 'libfoo.a'); + expect(OS.windows.dylibFileName('foo'), 'foo.dll'); + expect(OS.windows.libraryFileName('foo', Packaging.dynamic), 'foo.dll'); + expect(OS.windows.staticlibFileName('foo'), 'foo.lib'); + expect(OS.windows.libraryFileName('foo', Packaging.static), 'foo.lib'); + expect(OS.windows.executableFileName('foo'), 'foo.exe'); + }); + + test('Target current', () async { + final current = Target.current; + expect(current.toString(), Abi.current().toString()); + }); + + test('Target fromDartPlatform', () async { + final current = Target.fromDartPlatform(Platform.version); + expect(current.toString(), Abi.current().toString()); + expect(() => Target.fromDartPlatform('bogus'), throwsFormatException); + expect( + () => Target.fromDartPlatform( + '3.0.0 (be) (Wed Apr 5 14:19:42 2023 +0000) on "myfancyos_ia32"'), + throwsFormatException); + }); + + test('Target cross compilation', () async { + // All hosts can cross compile to Android. + expect( + Target.current.supportedTargetTargets(), contains(Target.androidArm64)); + expect( + Target.macOSArm64.supportedTargetTargets(), contains(Target.iOSArm64)); + }); +} From b10ce94bf00b22e2c14b18285df0f47f168d6880 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 11 Apr 2023 15:49:56 +0000 Subject: [PATCH 02/27] Rename CBuilder to RunCBuilder --- pkgs/c_compiler/lib/c_compiler.dart | 2 +- .../lib/src/cbuilder/{cbuilder.dart => run_cbuilder.dart} | 4 ++-- ...android_test.dart => run_cbuilder_cross_android_test.dart} | 2 +- ...host_test.dart => run_cbuilder_cross_linux_host_test.dart} | 2 +- .../cbuilder/{cbuilder_test.dart => run_cbuilder_test.dart} | 4 ++-- pkgs/native_assets_cli/example/native_add/build.dart | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename pkgs/c_compiler/lib/src/cbuilder/{cbuilder.dart => run_cbuilder.dart} (99%) rename pkgs/c_compiler/test/cbuilder/{cbuilder_cross_android_test.dart => run_cbuilder_cross_android_test.dart} (98%) rename pkgs/c_compiler/test/cbuilder/{cbuilder_cross_linux_host_test.dart => run_cbuilder_cross_linux_host_test.dart} (98%) rename pkgs/c_compiler/test/cbuilder/{cbuilder_test.dart => run_cbuilder_test.dart} (97%) diff --git a/pkgs/c_compiler/lib/c_compiler.dart b/pkgs/c_compiler/lib/c_compiler.dart index cb36b4a676..95426c2e10 100644 --- a/pkgs/c_compiler/lib/c_compiler.dart +++ b/pkgs/c_compiler/lib/c_compiler.dart @@ -5,7 +5,7 @@ /// A library to invoke the native C compiler installed on the host machine. library; -export 'src/cbuilder/cbuilder.dart'; +export 'src/cbuilder/run_cbuilder.dart'; export 'src/native_toolchain/android_ndk.dart' show androidNdk, androidNdkClang; export 'src/native_toolchain/clang.dart' show clang; export 'src/tool/tool.dart'; diff --git a/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart similarity index 99% rename from pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart rename to pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index be68a87400..ab24f3849b 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -9,7 +9,7 @@ import '../utils/run_process.dart'; import 'compiler_resolver.dart'; import 'target.dart'; -class CBuilder { +class RunCBuilder { final Config config; final Logger logger; final List sources; @@ -20,7 +20,7 @@ class CBuilder { final Uri outDir; final String target; - CBuilder({ + RunCBuilder({ required this.config, required this.logger, this.sources = const [], diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart similarity index 98% rename from pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart rename to pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart index 0d5050aced..9172494e2d 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart @@ -46,7 +46,7 @@ void main() { 'target': target, }); - final cbuilder = CBuilder( + final cbuilder = RunCBuilder( config: config, logger: logger, sources: [addCUri], diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart similarity index 98% rename from pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart rename to pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart index 68f23c3a42..51ba71c065 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart @@ -46,7 +46,7 @@ void main() { 'target': target, }); - final cbuilder = CBuilder( + final cbuilder = RunCBuilder( config: config, logger: logger, sources: [addCUri], diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart similarity index 97% rename from pkgs/c_compiler/test/cbuilder/cbuilder_test.dart rename to pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart index c260e41f09..4c6b5e19b2 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart @@ -26,7 +26,7 @@ void main() { final config = Config(fileParsed: { 'out_dir': tempUri.path, }); - final cbuilder = CBuilder( + final cbuilder = RunCBuilder( config: config, logger: logger, sources: [helloWorldCUri], @@ -53,7 +53,7 @@ void main() { 'out_dir': tempUri.path, }); - final cbuilder = CBuilder( + final cbuilder = RunCBuilder( config: config, logger: logger, sources: [addCUri], diff --git a/pkgs/native_assets_cli/example/native_add/build.dart b/pkgs/native_assets_cli/example/native_add/build.dart index 07c4aff12d..ddbfa703a4 100644 --- a/pkgs/native_assets_cli/example/native_add/build.dart +++ b/pkgs/native_assets_cli/example/native_add/build.dart @@ -28,7 +28,7 @@ void main(List args) async { nativeAssetsConfig.target.os.libraryFileName(packageName, packaging)); final sources = [for (final path in sourcePaths) packageRoot.resolve(path)]; - final task = CBuilder( + final task = RunCBuilder( config: config, logger: logger, sources: sources, From 62551be02ff48cb373d968c24c348ac8f4396d04 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 11 Apr 2023 15:50:33 +0000 Subject: [PATCH 03/27] remove unused import --- pkgs/native_assets_cli/test/model/metadata_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/native_assets_cli/test/model/metadata_test.dart b/pkgs/native_assets_cli/test/model/metadata_test.dart index 3e1f933262..30623191ef 100644 --- a/pkgs/native_assets_cli/test/model/metadata_test.dart +++ b/pkgs/native_assets_cli/test/model/metadata_test.dart @@ -2,7 +2,6 @@ // 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:collection/collection.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; From 92f04c2c3bc408b9cf5b17bdae6e7a6800e76416 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 11 Apr 2023 19:35:45 +0000 Subject: [PATCH 04/27] new CBuilder API --- pkgs/c_compiler/lib/c_compiler.dart | 1 + .../c_compiler/lib/src/cbuilder/cbuilder.dart | 133 ++++++++++++++++++ .../lib/src/cbuilder/compiler_resolver.dart | 11 +- .../lib/src/cbuilder/run_cbuilder.dart | 21 ++- pkgs/c_compiler/pubspec.yaml | 5 + .../run_cbuilder_cross_android_test.dart | 27 ++-- .../run_cbuilder_cross_linux_host_test.dart | 27 ++-- .../test/cbuilder/run_cbuilder_test.dart | 24 ++-- .../example/native_add/build.dart | 53 ++----- .../lib/src/model/build_config.dart | 5 + .../lib/src/model/build_output.dart | 13 +- .../test/model/build_config_test.dart | 16 +++ 12 files changed, 240 insertions(+), 96 deletions(-) create mode 100644 pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart diff --git a/pkgs/c_compiler/lib/c_compiler.dart b/pkgs/c_compiler/lib/c_compiler.dart index 95426c2e10..cd9239705c 100644 --- a/pkgs/c_compiler/lib/c_compiler.dart +++ b/pkgs/c_compiler/lib/c_compiler.dart @@ -5,6 +5,7 @@ /// A library to invoke the native C compiler installed on the host machine. library; +export 'src/cbuilder/cbuilder.dart'; export 'src/cbuilder/run_cbuilder.dart'; export 'src/native_toolchain/android_ndk.dart' show androidNdk, androidNdkClang; export 'src/native_toolchain/clang.dart' show clang; diff --git a/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart new file mode 100644 index 0000000000..c8b29cd267 --- /dev/null +++ b/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart @@ -0,0 +1,133 @@ +// 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 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +import 'run_cbuilder.dart'; + +abstract class Builder { + Future run({ + required BuildConfig buildConfig, + required BuildOutput buildOutput, + Logger? logger, + }); +} + +/// Specification for building an artifact with a C compiler. +class CBuilder implements Builder { + /// What kind of artifact to build. + final _CBuilderType _type; + + /// Name of the library or executable to build. + /// + /// The filename will be decided by [BuildConfig.target] and + /// [OS.libraryFileName] or [OS.executableFileName]. + /// + /// File will be placed in [BuildConfig.outDir]. + final String name; + + /// Asset identifier. + /// + /// Used to output the [BuildOutput.assets]. + /// + /// If omitted, no asset will be added to the build output. + final String? assetName; + + /// Sources to build the library or executable. + /// + /// Resolved against [BuildConfig.packageRoot]. + /// + /// Used to output the [BuildOutput.dependencies]. + final List sources; + + /// Sources to build the library or executable. + /// + /// Resolved against [BuildConfig.packageRoot]. + /// + /// Used to output the [BuildOutput.dependencies]. + final List includePaths; + + /// The dart files involved in building this artifact. + /// + /// Resolved against [BuildConfig.packageRoot]. + /// + /// Used to output the [BuildOutput.dependencies]. + final List dartBuildFiles; + + CBuilder.library({ + required this.name, + required this.assetName, + this.sources = const [], + this.includePaths = const [], + this.dartBuildFiles = const ['build.dart'], + }) : _type = _CBuilderType.library; + + CBuilder.executable({ + required this.name, + this.sources = const [], + this.includePaths = const [], + this.dartBuildFiles = const ['build.dart'], + }) : _type = _CBuilderType.executable, + assetName = null; + + @override + Future run({ + required BuildConfig buildConfig, + required BuildOutput buildOutput, + Logger? logger, + }) async { + logger ??= Logger(''); + final outDir = buildConfig.outDir; + final packageRoot = buildConfig.packageRoot; + await Directory.fromUri(outDir).create(recursive: true); + final packaging = buildConfig.packaging.preferredPackaging.first; + final libUri = + outDir.resolve(buildConfig.target.os.libraryFileName(name, packaging)); + final exeUri = + outDir.resolve(buildConfig.target.os.executableFileName(name)); + final sources = [ + for (final source in this.sources) packageRoot.resolve(source), + ]; + final dartBuildFiles = [ + for (final source in this.dartBuildFiles) packageRoot.resolve(source), + ]; + + final task = RunCBuilder( + buildConfig: buildConfig, + logger: logger, + sources: sources, + dynamicLibrary: + _type == _CBuilderType.library && packaging == Packaging.dynamic + ? libUri + : null, + staticLibrary: + _type == _CBuilderType.library && packaging == Packaging.static + ? libUri + : null, + executable: _type == _CBuilderType.executable ? exeUri : null, + ); + await task.run(); + + if (assetName != null) { + buildOutput.assets.add(Asset( + name: assetName!, + packaging: packaging, + target: buildConfig.target, + path: AssetAbsolutePath(libUri), + )); + } + buildOutput.dependencies.dependencies.addAll([ + ...sources, + ...dartBuildFiles, + ]); + } +} + +enum _CBuilderType { + executable, + library, +} diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index dc277a2a8f..6211212ffe 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -4,22 +4,21 @@ import 'dart:io'; -import 'package:cli_config/cli_config.dart'; import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import '../native_toolchain/android_ndk.dart'; import '../native_toolchain/clang.dart'; import '../tool/tool.dart'; import '../tool/tool_instance.dart'; import '../tool/tool_resolver.dart'; -import 'target.dart'; class CompilerResolver implements ToolResolver { - final Config config; + final BuildConfig buildConfig; final Logger? logger; CompilerResolver({ - required this.config, + required this.buildConfig, required this.logger, }); @@ -46,7 +45,7 @@ class CompilerResolver implements ToolResolver { /// Select the right compiler for cross compiling to the specified target. Tool selectCompiler() { - final target = config.optionalString('target') ?? Target.current(); + final target = buildConfig.target; switch (target) { case Target.linuxArm: return armLinuxGnueabihfGcc; @@ -73,7 +72,7 @@ class CompilerResolver implements ToolResolver { Future _tryLoadCompilerFromConfig( Tool tool, String configKey) async { - final configCcUri = config.optionalPath(_configKeyCC); + final configCcUri = buildConfig.cc; if (configCcUri != null) { if (await File.fromUri(configCcUri).exists()) { logger?.finer( diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index ab24f3849b..e1ae1a3ff4 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -2,15 +2,14 @@ // 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:cli_config/cli_config.dart'; import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import '../utils/run_process.dart'; import 'compiler_resolver.dart'; -import 'target.dart'; class RunCBuilder { - final Config config; + final BuildConfig buildConfig; final Logger logger; final List sources; final List includePaths; @@ -18,18 +17,18 @@ class RunCBuilder { final Uri? dynamicLibrary; final Uri? staticLibrary; final Uri outDir; - final String target; + final Target target; RunCBuilder({ - required this.config, + required this.buildConfig, required this.logger, this.sources = const [], this.includePaths = const [], this.executable, this.dynamicLibrary, this.staticLibrary, - }) : outDir = config.path('out_dir'), - target = config.optionalString('target') ?? Target.current() { + }) : outDir = buildConfig.outDir, + target = buildConfig.target { if ([executable, dynamicLibrary, staticLibrary].whereType().length != 1) { throw ArgumentError( @@ -43,7 +42,7 @@ class RunCBuilder { if (_compilerCached != null) { return _compilerCached!; } - final resolver = CompilerResolver(config: config, logger: logger); + final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); _compilerCached = (await resolver.resolve()).first.uri; return _compilerCached!; } @@ -55,7 +54,7 @@ class RunCBuilder { return _archiverCached!; } final compiler_ = await compiler(); - final resolver = CompilerResolver(config: config, logger: logger); + final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); _linkerCached = await resolver.resolveArchiver( compiler_, ); @@ -69,7 +68,7 @@ class RunCBuilder { return _linkerCached!; } final compiler_ = await compiler(); - final resolver = CompilerResolver(config: config, logger: logger); + final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); _linkerCached = await resolver.resolveLinker( compiler_, ); @@ -87,7 +86,7 @@ class RunCBuilder { await RunProcess( executable: compiler_.path, arguments: [ - if (target.startsWith('android')) ...[ + if (target.os == OS.android) ...[ // TODO(dacoharkes): How to solve linking issues? // Workaround: '-nostartfiles', diff --git a/pkgs/c_compiler/pubspec.yaml b/pkgs/c_compiler/pubspec.yaml index c7b89d719f..70d408c92b 100644 --- a/pkgs/c_compiler/pubspec.yaml +++ b/pkgs/c_compiler/pubspec.yaml @@ -12,8 +12,13 @@ dependencies: cli_config: ^0.1.1 glob: ^2.1.1 logging: ^1.1.1 + native_assets_cli: ^0.1.0-dev pub_semver: ^2.1.3 dev_dependencies: dart_flutter_team_lints: ^1.0.0 test: ^1.21.0 + +dependency_overrides: + native_assets_cli: + path: ../native_assets_cli/ diff --git a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart index 9172494e2d..03f5efc516 100644 --- a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart @@ -5,9 +5,8 @@ import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; -import 'package:c_compiler/src/cbuilder/target.dart'; -import 'package:cli_config/cli_config.dart'; import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; void main() { @@ -27,7 +26,7 @@ void main() { Target.androidX64: 'Advanced Micro Devices X86-64', }; - for (final packaging in ['dynamic', 'static']) { + for (final packaging in Packaging.values) { for (final target in targets) { test('Cbuilder $packaging library $target', () async { await inTempDir((tempUri) async { @@ -35,23 +34,29 @@ void main() { final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); final Uri libRelativeUri; - if (packaging == 'dynamic') { + if (packaging == Packaging.dynamic) { libRelativeUri = Uri.file('libadd.so'); } else { libRelativeUri = Uri.file('libadd.a'); } - final config = Config(fileParsed: { - 'out_dir': tempUri.path, - 'target': target, - }); + final buildConfig = BuildConfig( + outDir: tempUri, + packageRoot: tempUri, + target: target, + packaging: packaging == Packaging.dynamic + ? PackagingPreference.dynamic + : PackagingPreference.static, + ); final cbuilder = RunCBuilder( - config: config, + buildConfig: buildConfig, logger: logger, sources: [addCUri], - dynamicLibrary: packaging == 'dynamic' ? libRelativeUri : null, - staticLibrary: packaging == 'static' ? libRelativeUri : null, + dynamicLibrary: + packaging == Packaging.dynamic ? libRelativeUri : null, + staticLibrary: + packaging == Packaging.static ? libRelativeUri : null, ); await cbuilder.run(); diff --git a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart index 51ba71c065..b197c2d3d4 100644 --- a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart @@ -5,9 +5,8 @@ import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; -import 'package:c_compiler/src/cbuilder/target.dart'; -import 'package:cli_config/cli_config.dart'; import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; void main() { @@ -27,7 +26,7 @@ void main() { Target.linuxX64: 'Advanced Micro Devices X86-64', }; - for (final packaging in ['dynamic', 'static']) { + for (final packaging in Packaging.values) { for (final target in targets) { test('Cbuilder $packaging library linux $target', () async { await inTempDir((tempUri) async { @@ -35,23 +34,29 @@ void main() { final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); final Uri libRelativeUri; - if (packaging == 'dynamic') { + if (packaging == Packaging.dynamic) { libRelativeUri = Uri.file('libadd.so'); } else { libRelativeUri = Uri.file('libadd.a'); } - final config = Config(fileParsed: { - 'out_dir': tempUri.path, - 'target': target, - }); + final buildConfig = BuildConfig( + outDir: tempUri, + packageRoot: tempUri, + target: target, + packaging: packaging == Packaging.dynamic + ? PackagingPreference.dynamic + : PackagingPreference.static, + ); final cbuilder = RunCBuilder( - config: config, + buildConfig: buildConfig, logger: logger, sources: [addCUri], - dynamicLibrary: packaging == 'dynamic' ? libRelativeUri : null, - staticLibrary: packaging == 'static' ? libRelativeUri : null, + dynamicLibrary: + packaging == Packaging.dynamic ? libRelativeUri : null, + staticLibrary: + packaging == Packaging.static ? libRelativeUri : null, ); await cbuilder.run(); diff --git a/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart index 4c6b5e19b2..1e840c9485 100644 --- a/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart @@ -6,8 +6,8 @@ import 'dart:ffi'; import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; -import 'package:cli_config/cli_config.dart'; import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; void main() { @@ -23,11 +23,14 @@ void main() { } final executableRelativeUri = Uri.file('hello_world'); - final config = Config(fileParsed: { - 'out_dir': tempUri.path, - }); + final buildConfig = BuildConfig( + outDir: tempUri, + packageRoot: tempUri, + target: Target.current, + packaging: PackagingPreference.dynamic, // Ignored by executables. + ); final cbuilder = RunCBuilder( - config: config, + buildConfig: buildConfig, logger: logger, sources: [helloWorldCUri], executable: executableRelativeUri, @@ -49,12 +52,15 @@ void main() { packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); final dylibRelativeUri = Uri.file('libadd.so'); - final config = Config(fileParsed: { - 'out_dir': tempUri.path, - }); + final buildConfig = BuildConfig( + outDir: tempUri, + packageRoot: tempUri, + target: Target.current, + packaging: PackagingPreference.dynamic, + ); final cbuilder = RunCBuilder( - config: config, + buildConfig: buildConfig, logger: logger, sources: [addCUri], dynamicLibrary: dylibRelativeUri, diff --git a/pkgs/native_assets_cli/example/native_add/build.dart b/pkgs/native_assets_cli/example/native_add/build.dart index ddbfa703a4..0d09253e19 100644 --- a/pkgs/native_assets_cli/example/native_add/build.dart +++ b/pkgs/native_assets_cli/example/native_add/build.dart @@ -2,55 +2,22 @@ // 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 'dart:io'; - import 'package:c_compiler/c_compiler.dart'; -import 'package:cli_config/cli_config.dart'; -import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; const packageName = 'native_add'; -const assetName = - 'package:$packageName/src/${packageName}_bindings_generated.dart'; -const sourcePaths = [ - 'src/$packageName.c', -]; void main(List args) async { - final logger = Logger(''); - final config = await Config.fromArgs(args: args); - final nativeAssetsConfig = BuildConfig.fromConfig(config); - final outDir = nativeAssetsConfig.outDir; - final packageRoot = nativeAssetsConfig.packageRoot; - await Directory.fromUri(outDir).create(recursive: true); - final packaging = nativeAssetsConfig.packaging.preferredPackaging.first; - final libUri = outDir.resolve( - nativeAssetsConfig.target.os.libraryFileName(packageName, packaging)); - final sources = [for (final path in sourcePaths) packageRoot.resolve(path)]; - - final task = RunCBuilder( - config: config, - logger: logger, - sources: sources, - dynamicLibrary: packaging == Packaging.dynamic ? libUri : null, - staticLibrary: packaging == Packaging.static ? libUri : null, - ); - await task.run(); - - final buildOutput = BuildOutput( - timestamp: DateTime.now(), - assets: [ - Asset( - name: assetName, - packaging: packaging, - target: nativeAssetsConfig.target, - path: AssetAbsolutePath(libUri), - ) + final buildConfig = await BuildConfig.fromArgs(args); + final buildOutput = BuildOutput(); + final cbuilder = CBuilder.library( + name: packageName, + assetName: + 'package:$packageName/src/${packageName}_bindings_generated.dart', + sources: [ + 'src/$packageName.c', ], - dependencies: Dependencies([ - ...sources, - packageRoot.resolve('build.dart'), - ]), ); - await buildOutput.writeToFile(outDir: outDir); + await cbuilder.run(buildConfig: buildConfig, buildOutput: buildOutput); + await buildOutput.writeToFile(outDir: buildConfig.outDir); } diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart index f6584e3f28..61da4095d5 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -101,6 +101,11 @@ class BuildConfig { return result; } + static Future fromArgs(List args) async { + final config = await Config.fromArgs(args: args); + return BuildConfig.fromConfig(config); + } + static const outDirConfigKey = 'out_dir'; static const packageRootConfigKey = 'package_root'; static const ccConfigKey = 'cc'; diff --git a/pkgs/native_assets_cli/lib/src/model/build_output.dart b/pkgs/native_assets_cli/lib/src/model/build_output.dart index 35933b4fc3..31558b2a08 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_output.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_output.dart @@ -26,11 +26,14 @@ class BuildOutput { final Metadata metadata; BuildOutput({ - required DateTime timestamp, - this.assets = const [], - this.dependencies = const Dependencies([]), - this.metadata = const Metadata({}), - }) : timestamp = timestamp.roundDownToSeconds(); + DateTime? timestamp, + List? assets, + Dependencies? dependencies, + Metadata? metadata, + }) : timestamp = (timestamp ?? DateTime.now()).roundDownToSeconds(), + assets = assets ?? [], + dependencies = dependencies ?? Dependencies([]), + metadata = metadata ?? Metadata({}); static const _assetsKey = 'assets'; static const _dependenciesKey = 'dependencies'; diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart index 9c3c619fb9..e0fda3609e 100644 --- a/pkgs/native_assets_cli/test/model/build_config_test.dart +++ b/pkgs/native_assets_cli/test/model/build_config_test.dart @@ -229,4 +229,20 @@ target_ios_sdk: iphoneos'''; ); config.toString(); }); + + test('BuildConfig fromArgs', () async { + final buildConfig = BuildConfig( + outDir: tempUri.resolve('out2/'), + packageRoot: tempUri, + target: Target.androidArm64, + packaging: PackagingPreference.preferStatic, + ); + final configFileContents = buildConfig.toYamlString(); + final configUri = tempUri.resolve('config.yaml'); + final configFile = File.fromUri(configUri); + await configFile.writeAsString(configFileContents); + final buildConfig2 = + await BuildConfig.fromArgs(['--config', configUri.toFilePath()]); + expect(buildConfig2, buildConfig); + }); } From 6f79a12454a5aa1d68a34f530d608c9bb2d364e3 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 11 Apr 2023 19:55:16 +0000 Subject: [PATCH 05/27] test new CBuilder API --- pkgs/c_compiler/lib/c_compiler.dart | 1 - ....dart => cbuilder_cross_android_test.dart} | 44 +++++----------- ...rt => cbuilder_cross_linux_host_test.dart} | 44 +++++----------- ..._cbuilder_test.dart => cbuilder_test.dart} | 51 ++++++++----------- pkgs/c_compiler/test/helpers.dart | 22 ++++++++ 5 files changed, 70 insertions(+), 92 deletions(-) rename pkgs/c_compiler/test/cbuilder/{run_cbuilder_cross_android_test.dart => cbuilder_cross_android_test.dart} (65%) rename pkgs/c_compiler/test/cbuilder/{run_cbuilder_cross_linux_host_test.dart => cbuilder_cross_linux_host_test.dart} (64%) rename pkgs/c_compiler/test/cbuilder/{run_cbuilder_test.dart => cbuilder_test.dart} (68%) create mode 100644 pkgs/c_compiler/test/helpers.dart diff --git a/pkgs/c_compiler/lib/c_compiler.dart b/pkgs/c_compiler/lib/c_compiler.dart index cd9239705c..cb36b4a676 100644 --- a/pkgs/c_compiler/lib/c_compiler.dart +++ b/pkgs/c_compiler/lib/c_compiler.dart @@ -6,7 +6,6 @@ library; export 'src/cbuilder/cbuilder.dart'; -export 'src/cbuilder/run_cbuilder.dart'; export 'src/native_toolchain/android_ndk.dart' show androidNdk, androidNdkClang; export 'src/native_toolchain/clang.dart' show clang; export 'src/tool/tool.dart'; diff --git a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart similarity index 65% rename from pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart rename to pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart index 03f5efc516..f461850901 100644 --- a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -9,6 +9,8 @@ import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; +import '../helpers.dart'; + void main() { final logger = Logger('')..level = Level.ALL; @@ -33,12 +35,7 @@ void main() { final packageUri = Directory.current.uri; final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); - final Uri libRelativeUri; - if (packaging == Packaging.dynamic) { - libRelativeUri = Uri.file('libadd.so'); - } else { - libRelativeUri = Uri.file('libadd.a'); - } + const name = 'add'; final buildConfig = BuildConfig( outDir: tempUri, @@ -48,19 +45,21 @@ void main() { ? PackagingPreference.dynamic : PackagingPreference.static, ); + final buildOutput = BuildOutput(); - final cbuilder = RunCBuilder( + final cbuilder = CBuilder.library( + name: 'add', + assetName: 'add', + sources: [addCUri.toFilePath()], + ); + await cbuilder.run( buildConfig: buildConfig, + buildOutput: buildOutput, logger: logger, - sources: [addCUri], - dynamicLibrary: - packaging == Packaging.dynamic ? libRelativeUri : null, - staticLibrary: - packaging == Packaging.static ? libRelativeUri : null, ); - await cbuilder.run(); - final libUri = tempUri.resolveUri(libRelativeUri); + final libUri = + tempUri.resolve(target.os.libraryFileName(name, packaging)); final result = await Process.run('readelf', ['-h', libUri.path]); expect(result.exitCode, 0); final machine = (result.stdout as String) @@ -73,20 +72,3 @@ void main() { } } } - -const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; - -Future inTempDir( - Future Function(Uri tempUri) fun, { - String? prefix, -}) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); - try { - await fun(tempDir.uri); - } finally { - if (!Platform.environment.containsKey(keepTempKey) || - Platform.environment[keepTempKey]!.isEmpty) { - await tempDir.delete(recursive: true); - } - } -} diff --git a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart similarity index 64% rename from pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart rename to pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart index b197c2d3d4..99b86a0b17 100644 --- a/pkgs/c_compiler/test/cbuilder/run_cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -9,6 +9,8 @@ import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; +import '../helpers.dart'; + void main() { final logger = Logger('')..level = Level.ALL; @@ -33,12 +35,7 @@ void main() { final packageUri = Directory.current.uri; final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); - final Uri libRelativeUri; - if (packaging == Packaging.dynamic) { - libRelativeUri = Uri.file('libadd.so'); - } else { - libRelativeUri = Uri.file('libadd.a'); - } + const name = 'add'; final buildConfig = BuildConfig( outDir: tempUri, @@ -48,19 +45,21 @@ void main() { ? PackagingPreference.dynamic : PackagingPreference.static, ); + final buildOutput = BuildOutput(); - final cbuilder = RunCBuilder( + final cbuilder = CBuilder.library( + name: 'add', + assetName: 'add', + sources: [addCUri.toFilePath()], + ); + await cbuilder.run( buildConfig: buildConfig, + buildOutput: buildOutput, logger: logger, - sources: [addCUri], - dynamicLibrary: - packaging == Packaging.dynamic ? libRelativeUri : null, - staticLibrary: - packaging == Packaging.static ? libRelativeUri : null, ); - await cbuilder.run(); - final libUri = tempUri.resolveUri(libRelativeUri); + final libUri = + tempUri.resolve(target.os.libraryFileName(name, packaging)); final result = await Process.run('readelf', ['-h', libUri.path]); expect(result.exitCode, 0); final machine = (result.stdout as String) @@ -73,20 +72,3 @@ void main() { } } } - -const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; - -Future inTempDir( - Future Function(Uri tempUri) fun, { - String? prefix, -}) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); - try { - await fun(tempDir.uri); - } finally { - if (!Platform.environment.containsKey(keepTempKey) || - Platform.environment[keepTempKey]!.isEmpty) { - await tempDir.delete(recursive: true); - } - } -} diff --git a/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart similarity index 68% rename from pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart rename to pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index 1e840c9485..951d280a45 100644 --- a/pkgs/c_compiler/test/cbuilder/run_cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -10,6 +10,8 @@ import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; +import '../helpers.dart'; + void main() { final logger = Logger('')..level = Level.ALL; @@ -21,7 +23,7 @@ void main() { if (!await File.fromUri(helloWorldCUri).exists()) { throw Exception('Run the test from the root directory.'); } - final executableRelativeUri = Uri.file('hello_world'); + const name = 'hello_world'; final buildConfig = BuildConfig( outDir: tempUri, @@ -29,15 +31,19 @@ void main() { target: Target.current, packaging: PackagingPreference.dynamic, // Ignored by executables. ); - final cbuilder = RunCBuilder( + final buildOutput = BuildOutput(); + final cbuilder = CBuilder.executable( + name: name, + sources: [helloWorldCUri.toFilePath()], + ); + await cbuilder.run( buildConfig: buildConfig, + buildOutput: buildOutput, logger: logger, - sources: [helloWorldCUri], - executable: executableRelativeUri, ); - await cbuilder.run(); - final executableUri = tempUri.resolveUri(executableRelativeUri); + final executableUri = + tempUri.resolve(Target.current.os.executableFileName(name)); expect(await File.fromUri(executableUri).exists(), true); final result = await Process.run(executableUri.path, []); expect(result.exitCode, 0); @@ -50,7 +56,7 @@ void main() { final packageUri = Directory.current.uri; final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); - final dylibRelativeUri = Uri.file('libadd.so'); + const name = 'add'; final buildConfig = BuildConfig( outDir: tempUri, @@ -58,16 +64,20 @@ void main() { target: Target.current, packaging: PackagingPreference.dynamic, ); + final buildOutput = BuildOutput(); - final cbuilder = RunCBuilder( + final cbuilder = CBuilder.library( + sources: [addCUri.toFilePath()], + name: name, + assetName: name, + ); + await cbuilder.run( buildConfig: buildConfig, + buildOutput: buildOutput, logger: logger, - sources: [addCUri], - dynamicLibrary: dylibRelativeUri, ); - await cbuilder.run(); - final dylibUri = tempUri.resolveUri(dylibRelativeUri); + final dylibUri = tempUri.resolve(Target.current.os.dylibFileName(name)); final dylib = DynamicLibrary.open(dylibUri.path); final add = dylib.lookupFunction('add'); @@ -75,20 +85,3 @@ void main() { }); }); } - -const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; - -Future inTempDir( - Future Function(Uri tempUri) fun, { - String? prefix, -}) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); - try { - await fun(tempDir.uri); - } finally { - if (!Platform.environment.containsKey(keepTempKey) || - Platform.environment[keepTempKey]!.isEmpty) { - await tempDir.delete(recursive: true); - } - } -} diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart new file mode 100644 index 0000000000..c4922b1cde --- /dev/null +++ b/pkgs/c_compiler/test/helpers.dart @@ -0,0 +1,22 @@ +// 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 'dart:io'; + +const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; + +Future inTempDir( + Future Function(Uri tempUri) fun, { + String? prefix, +}) async { + final tempDir = await Directory.systemTemp.createTemp(prefix); + try { + await fun(tempDir.uri); + } finally { + if (!Platform.environment.containsKey(keepTempKey) || + Platform.environment[keepTempKey]!.isEmpty) { + await tempDir.delete(recursive: true); + } + } +} From ce7ee206e26588260256b62b85cec4cf183f6238 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 12 Apr 2023 07:24:59 +0000 Subject: [PATCH 06/27] Remove unused file --- pkgs/c_compiler/lib/src/cbuilder/target.dart | 97 -------------------- 1 file changed, 97 deletions(-) delete mode 100644 pkgs/c_compiler/lib/src/cbuilder/target.dart diff --git a/pkgs/c_compiler/lib/src/cbuilder/target.dart b/pkgs/c_compiler/lib/src/cbuilder/target.dart deleted file mode 100644 index 0c9be2171c..0000000000 --- a/pkgs/c_compiler/lib/src/cbuilder/target.dart +++ /dev/null @@ -1,97 +0,0 @@ -// 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 'dart:ffi'; - -abstract class Target { - /// The application binary interface for Android on the Arm architecture. - static const String androidArm = 'android_arm'; - - /// The application binary interface for Android on the Arm64 architecture. - static const String androidArm64 = 'android_arm64'; - - /// The application binary interface for Android on the IA32 architecture. - static const String androidIA32 = 'android_ia32'; - - /// The application binary interface for android on the X64 architecture. - static const String androidX64 = 'android_x64'; - - /// The application binary interface for Fuchsia on the Arm64 architecture. - static const String fuchsiaArm64 = 'fuchsia_arm64'; - - /// The application binary interface for Fuchsia on the X64 architecture. - static const String fuchsiaX64 = 'fuchsia_x64'; - - /// The application binary interface for iOS on the Arm architecture. - static const String iosArm = 'ios_arm'; - - /// The application binary interface for iOS on the Arm64 architecture. - static const String iosArm64 = 'ios_arm64'; - - /// The application binary interface for iOS on the X64 architecture. - static const String iosX64 = 'ios_x64'; - - /// The application binary interface for Linux on the Arm architecture. - /// - /// Does not distinguish between hard and soft fp. Currently, no uses of Abi - /// require this distinction. - static const String linuxArm = 'linux_arm'; - - /// The application binary interface for linux on the Arm64 architecture. - static const String linuxArm64 = 'linux_arm64'; - - /// The application binary interface for linux on the IA32 architecture. - static const String linuxIA32 = 'linux_ia32'; - - /// The application binary interface for linux on the X64 architecture. - static const String linuxX64 = 'linux_x64'; - - /// The application binary interface for linux on 32-bit RISC-V. - static const String linuxRiscv32 = 'linux_riscv32'; - - /// The application binary interface for linux on 64-bit RISC-V. - static const String linuxRiscv64 = 'linux_riscv64'; - - /// The application binary interface for MacOS on the Arm64 architecture. - static const String macosArm64 = 'macos_arm64'; - - /// The application binary interface for MacOS on the X64 architecture. - static const String macosX64 = 'macos_x64'; - - /// The application binary interface for Windows on the Arm64 architecture. - static const String windowsArm64 = 'windows_arm64'; - - /// The application binary interface for Windows on the IA32 architecture. - static const String windowsIA32 = 'windows_ia32'; - - /// The application binary interface for Windows on the X64 architecture. - static const String windowsX64 = 'windows_x64'; - - static const List values = [ - androidArm, - androidArm64, - androidIA32, - androidX64, - fuchsiaArm64, - fuchsiaX64, - iosArm, - iosArm64, - iosX64, - linuxArm, - linuxArm64, - linuxIA32, - linuxX64, - linuxRiscv32, - linuxRiscv64, - macosArm64, - macosX64, - windowsArm64, - windowsIA32, - windowsX64, - ]; - - /// Returns the target of the host machine. - static String current() => - values.firstWhere((e) => e == Abi.current().toString()); -} From e4638c33d3e526768b3586353cb8ccfd66b838c4 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 12 Apr 2023 09:17:39 +0000 Subject: [PATCH 07/27] Refactor c_compiler implementation --- pkgs/c_compiler/lib/c_compiler.dart | 5 +- .../lib/src/cbuilder/compiler_resolver.dart | 67 +++++++--------- .../lib/src/cbuilder/run_cbuilder.dart | 4 +- .../lib/src/native_toolchain/android_ndk.dart | 6 +- .../lib/src/native_toolchain/gcc.dart | 21 +++++ pkgs/c_compiler/lib/src/tool/tool.dart | 2 +- pkgs/c_compiler/lib/src/tool/tool_error.dart | 2 + .../lib/src/tool/tool_instance.dart | 34 ++++----- .../lib/src/tool/tool_requirement.dart | 2 +- .../lib/src/tool/tool_resolver.dart | 23 ++---- .../c_compiler/lib/src/utils/run_process.dart | 20 +++-- pkgs/c_compiler/pubspec.yaml | 1 + .../cbuilder/cbuilder_cross_android_test.dart | 3 +- .../cbuilder_cross_linux_host_test.dart | 3 +- .../test/cbuilder/cbuilder_test.dart | 3 +- pkgs/c_compiler/test/helpers.dart | 15 ++++ .../test/native_toolchain/clang_test.dart | 2 +- .../test/native_toolchain/ndk_test.dart | 2 +- .../test/tool/tool_instance_test.dart | 76 +++++++++++++++++++ .../test/tool/tool_requirement_test.dart | 48 ++++++++++++ .../test/tool/tool_resolver_test.dart | 61 +++++++++++++++ pkgs/c_compiler/test/tool/tool_test.dart | 27 +++++++ .../lib/src/model/asset.dart | 9 +-- .../lib/src/model/build_config.dart | 27 ++++--- 24 files changed, 352 insertions(+), 111 deletions(-) create mode 100644 pkgs/c_compiler/lib/src/native_toolchain/gcc.dart create mode 100644 pkgs/c_compiler/test/tool/tool_instance_test.dart create mode 100644 pkgs/c_compiler/test/tool/tool_requirement_test.dart create mode 100644 pkgs/c_compiler/test/tool/tool_resolver_test.dart create mode 100644 pkgs/c_compiler/test/tool/tool_test.dart diff --git a/pkgs/c_compiler/lib/c_compiler.dart b/pkgs/c_compiler/lib/c_compiler.dart index cb36b4a676..8ef513d5d4 100644 --- a/pkgs/c_compiler/lib/c_compiler.dart +++ b/pkgs/c_compiler/lib/c_compiler.dart @@ -6,8 +6,9 @@ library; export 'src/cbuilder/cbuilder.dart'; -export 'src/native_toolchain/android_ndk.dart' show androidNdk, androidNdkClang; -export 'src/native_toolchain/clang.dart' show clang; +export 'src/native_toolchain/android_ndk.dart'; +export 'src/native_toolchain/clang.dart'; +export 'src/native_toolchain/gcc.dart'; export 'src/tool/tool.dart'; export 'src/tool/tool_instance.dart'; export 'src/tool/tool_requirement.dart'; diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index 6211212ffe..22aba873c5 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -9,7 +9,9 @@ import 'package:native_assets_cli/native_assets_cli.dart'; import '../native_toolchain/android_ndk.dart'; import '../native_toolchain/clang.dart'; +import '../native_toolchain/gcc.dart'; import '../tool/tool.dart'; +import '../tool/tool_error.dart'; import '../tool/tool_instance.dart'; import '../tool/tool_resolver.dart'; @@ -25,12 +27,25 @@ class CompilerResolver implements ToolResolver { @override Future> resolve() async { final tool = selectCompiler(); - ToolInstance? result; - result ??= await _tryLoadCompilerFromConfig(tool, _configKeyCC); + + // First, check if the launcher provided a direct path to the compiler. + var result = await _tryLoadCompilerFromConfig( + tool, BuildConfig.ccConfigKey, (buildConfig) => buildConfig.cc); + + // Then, check if this package itself provided metadata. + final depsToolKey = [ + BuildConfig.dependencyMetadataConfigKey, + ' c_compiler', + tool.name + ].join('.'); result ??= await _tryLoadCompilerFromConfig( tool, - _configKeyNativeToolchainClang, + depsToolKey, + (buildConfig) => + buildConfig.config.optionalPath(depsToolKey, mustExist: true), ); + + // Lastly, try to detect on the host machine. result ??= await _tryLoadCompilerFromNativeToolchain( tool, ); @@ -38,6 +53,7 @@ class CompilerResolver implements ToolResolver { if (result != null) { return [result]; } + const errorMessage = 'No C compiler found.'; logger?.severe(errorMessage); throw Exception(errorMessage); @@ -45,6 +61,7 @@ class CompilerResolver implements ToolResolver { /// Select the right compiler for cross compiling to the specified target. Tool selectCompiler() { + final host = Target.current; final target = buildConfig.target; switch (target) { case Target.linuxArm: @@ -61,26 +78,20 @@ class CompilerResolver implements ToolResolver { case Target.androidX64: return androidNdkClang; } - throw Exception('No tool available for target: $target.'); + throw ToolError("No tool available on host '$host' for target: '$target'."); } - /// Provided by launchers. - static const _configKeyCC = 'cc'; - - /// Provided by package:native_toolchain. - static const _configKeyNativeToolchainClang = 'deps.native_toolchain.clang'; - Future _tryLoadCompilerFromConfig( - Tool tool, String configKey) async { - final configCcUri = buildConfig.cc; + Tool tool, String configKey, Uri? Function(BuildConfig) getter) async { + final configCcUri = getter(buildConfig); if (configCcUri != null) { if (await File.fromUri(configCcUri).exists()) { - logger?.finer( - 'Using compiler ${configCcUri.path} from config[$_configKeyCC].'); + logger?.finer('Using compiler ${configCcUri.path} ' + 'from config[${BuildConfig.ccConfigKey}].'); return ToolInstance(tool: tool, uri: configCcUri); } else { - logger?.warning( - 'Compiler ${configCcUri.path} from config[$_configKeyCC] does not ' + logger?.warning('Compiler ${configCcUri.path} from ' + 'config[${BuildConfig.ccConfigKey}] does not ' 'exist.'); } } @@ -97,7 +108,7 @@ class CompilerResolver implements ToolResolver { logger?.warning('Clang could not be found by package:native_toolchain.'); return null; } - return resolved.last; + return resolved.first; } Future resolveLinker( @@ -138,25 +149,3 @@ class CompilerResolver implements ToolResolver { throw Exception(errorMessage); } } - -final i686LinuxGnuGcc = Tool( - name: 'i686-linux-gnu-gcc', - defaultResolver: PathToolResolver(toolName: 'i686-linux-gnu-gcc'), -); - -final armLinuxGnueabihfGcc = Tool( - name: 'arm-linux-gnueabihf-gcc', - defaultResolver: PathToolResolver(toolName: 'arm-linux-gnueabihf-gcc'), -); - -final aarch64LinuxGnuGcc = Tool( - name: 'aarch64-linux-gnu-gcc', - defaultResolver: PathToolResolver(toolName: 'aarch64-linux-gnu-gcc'), -); - -final clangLike = [ - clang, - i686LinuxGnuGcc, - armLinuxGnueabihfGcc, - aarch64LinuxGnuGcc, -]; diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index e1ae1a3ff4..ff4b8b9cf7 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -88,10 +88,10 @@ class RunCBuilder { arguments: [ if (target.os == OS.android) ...[ // TODO(dacoharkes): How to solve linking issues? - // Workaround: - '-nostartfiles', // Non-working fix: --sysroot=$NDKPATH/toolchains/llvm/prebuilt/linux-x86_64/sysroot. // The sysroot should be discovered automatically after NDK 22. + // Workaround: + if (dynamicLibrary != null) '-nostartfiles', '--target=${androidNdkClangTargetFlags[target]!}', ], ...sources.map((e) => e.path), diff --git a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart index 2951435790..3de9fa8bd8 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart @@ -10,16 +10,16 @@ import '../tool/tool_resolver.dart'; final androidNdk = Tool( name: 'Android NDK', - defaultResolver: AndroidNdkResolver(), + defaultResolver: _AndroidNdkResolver(), ); /// A clang that knows how to target Android. final androidNdkClang = Tool( name: 'Android NDK Clang', - defaultResolver: AndroidNdkResolver(), + defaultResolver: _AndroidNdkResolver(), ); -class AndroidNdkResolver implements ToolResolver { +class _AndroidNdkResolver implements ToolResolver { final installLocationResolver = PathVersionResolver( wrappedResolver: ToolResolvers([ RelativeToolResolver( diff --git a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart new file mode 100644 index 0000000000..935e6baa78 --- /dev/null +++ b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart @@ -0,0 +1,21 @@ +// 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 '../tool/tool.dart'; +import '../tool/tool_resolver.dart'; + +final i686LinuxGnuGcc = Tool( + name: 'i686-linux-gnu-gcc', + defaultResolver: PathToolResolver(toolName: 'i686-linux-gnu-gcc'), +); + +final armLinuxGnueabihfGcc = Tool( + name: 'arm-linux-gnueabihf-gcc', + defaultResolver: PathToolResolver(toolName: 'arm-linux-gnueabihf-gcc'), +); + +final aarch64LinuxGnuGcc = Tool( + name: 'aarch64-linux-gnu-gcc', + defaultResolver: PathToolResolver(toolName: 'aarch64-linux-gnu-gcc'), +); diff --git a/pkgs/c_compiler/lib/src/tool/tool.dart b/pkgs/c_compiler/lib/src/tool/tool.dart index 13360a5ed8..7083cb5f7e 100644 --- a/pkgs/c_compiler/lib/src/tool/tool.dart +++ b/pkgs/c_compiler/lib/src/tool/tool.dart @@ -18,5 +18,5 @@ class Tool { bool operator ==(Object other) => other is Tool && name == other.name; @override - int get hashCode => name.hashCode * 41; + int get hashCode => Object.hash(name, 133709); } diff --git a/pkgs/c_compiler/lib/src/tool/tool_error.dart b/pkgs/c_compiler/lib/src/tool/tool_error.dart index 93a351b014..def9ce4467 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_error.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_error.dart @@ -6,7 +6,9 @@ /// host system. class ToolError extends Error { final String message; + ToolError(this.message); + @override String toString() => 'System not configured correctly: $message'; } diff --git a/pkgs/c_compiler/lib/src/tool/tool_instance.dart b/pkgs/c_compiler/lib/src/tool/tool_instance.dart index 765b9ef5e6..87c42d537d 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_instance.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_instance.dart @@ -33,14 +33,12 @@ class ToolInstance implements Comparable { @override String toString() => 'ToolInstance(${tool.name}, $version, $uri)'; - /// The path of this native tool. + /// Compares this tool instance to [other]. /// - /// We use forward slashes on also on Windows because that's easier with - /// escaping when passing as command line arguments. - /// Using this because `windows: false` puts a `/` in front of `C:/`. - String get path => - uri.toFilePath().replaceAll(r'\', r'/').replaceAll(' ', '\\ '); - + /// When used in sorting, orders [ToolInstance]s according to: + /// 1. [tool] name, alphabetically; then + /// 2. [version], newest first and preferring having a version; then + /// 3. [uri], alphabetically. @override int compareTo(ToolInstance other) { final nameCompare = tool.name.compareTo(other.tool.name); @@ -48,16 +46,18 @@ class ToolInstance implements Comparable { return nameCompare; } final version = this.version; - if (version == null) { - return -1; - } final otherVersion = other.version; - if (otherVersion == null) { - return 1; - } - final versionCompare = version.compareTo(otherVersion); - if (versionCompare != 0) { - return versionCompare; + if (version != null || otherVersion != null) { + if (version == null) { + return 1; + } + if (otherVersion == null) { + return -1; + } + final versionCompare = version.compareTo(otherVersion); + if (versionCompare != 0) { + return -versionCompare; + } } return uri.toFilePath().compareTo(other.uri.toFilePath()); } @@ -70,5 +70,5 @@ class ToolInstance implements Comparable { version == other.version; @override - int get hashCode => tool.hashCode ^ uri.hashCode ^ version.hashCode; + int get hashCode => Object.hash(tool, uri, version); } diff --git a/pkgs/c_compiler/lib/src/tool/tool_requirement.dart b/pkgs/c_compiler/lib/src/tool/tool_requirement.dart index 9e4881742b..33dba8ba49 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_requirement.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_requirement.dart @@ -51,7 +51,7 @@ class ToolRequirement implements Requirement { return null; } candidates.sort(); - return [candidates.last]; + return [candidates.first]; } } diff --git a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart index 983944180a..335db92057 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:pub_semver/pub_semver.dart'; import '../utils/run_process.dart'; @@ -38,13 +39,8 @@ class PathToolResolver extends ToolResolver { ]; } - String get executableName { - final executableName = toolName.toLowerCase(); - if (Platform.isWindows) { - return '$executableName.exe'; - } - return executableName; - } + String get executableName => + Target.current.os.executableFileName(toolName.toLowerCase()); static String get which => Platform.isWindows ? 'where' : 'which'; @@ -59,12 +55,9 @@ class PathToolResolver extends ToolResolver { final uri = File(await file.resolveSymbolicLinks()).uri; return uri; } - if (process.exitCode == 1) { - // The exit code for executable not being on the `PATH`. - return null; - } - throw ToolError('`$which $executableName` returned unexpected exit code: ' - '${process.exitCode}.'); + // The exit code for executable not being on the `PATH`. + assert(process.exitCode == 1); + return null; } } @@ -172,9 +165,7 @@ class InstallLocationResolver implements ToolResolver { Future> tryResolvePath(String path) async { if (path.startsWith(home)) { final homeDir_ = homeDir; - if (homeDir_ == null) { - return []; - } + assert(homeDir_ != null); path = path.replaceAll('$home/', homeDir!.path); } diff --git a/pkgs/c_compiler/lib/src/utils/run_process.dart b/pkgs/c_compiler/lib/src/utils/run_process.dart index 3dcc1cbf21..d30016b002 100644 --- a/pkgs/c_compiler/lib/src/utils/run_process.dart +++ b/pkgs/c_compiler/lib/src/utils/run_process.dart @@ -124,6 +124,9 @@ class RunProcess { Future run({Logger? logger}) async { final workingDirectoryString = workingDirectory?.toFilePath(); + final stdoutBuffer = []; + final stderrBuffer = []; + logger?.info('Running `$commandString`.'); final process = await Process.start( executable, @@ -133,18 +136,21 @@ class RunProcess { environment: environment, includeParentEnvironment: includeParentEnvironment, ).then((process) { - process.stdout - .transform(utf8.decoder) - .forEach((s) => logger?.fine(' $s')); - process.stderr - .transform(utf8.decoder) - .forEach((s) => logger?.severe(' $s')); + process.stdout.transform(utf8.decoder).forEach((s) { + logger?.fine(' $s'); + stdoutBuffer.add(s); + }); + process.stderr.transform(utf8.decoder).forEach((s) { + logger?.severe(' $s'); + stderrBuffer.add(s); + }); return process; }); final exitCode = await process.exitCode; if (exitCode != 0) { final message = - 'Command `$commandString` failed with exit code $exitCode.'; + 'Command `$commandString` failed with exit code $exitCode. ' + 'stderr: ${stderrBuffer.join('\n')}'; logger?.severe(message); if (throwOnFailure) { throw Exception(message); diff --git a/pkgs/c_compiler/pubspec.yaml b/pkgs/c_compiler/pubspec.yaml index 70d408c92b..6e4a826185 100644 --- a/pkgs/c_compiler/pubspec.yaml +++ b/pkgs/c_compiler/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: pub_semver: ^2.1.3 dev_dependencies: + collection: ^1.17.1 dart_flutter_team_lints: ^1.0.0 test: ^1.21.0 diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart index f461850901..a78be33186 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -5,14 +5,13 @@ import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; -import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { - final logger = Logger('')..level = Level.ALL; + final logger = createLogger(); const targets = [ Target.androidArm, diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart index 99b86a0b17..7b4494531a 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -5,14 +5,13 @@ import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; -import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { - final logger = Logger('')..level = Level.ALL; + final logger = createLogger(); const targets = [ Target.linuxArm, diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index 951d280a45..5245c713d1 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -6,14 +6,13 @@ import 'dart:ffi'; import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; -import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { - final logger = Logger('')..level = Level.ALL; + final logger = createLogger(); test('Cbuilder executable', () async { await inTempDir((tempUri) async { diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart index c4922b1cde..b5999051a7 100644 --- a/pkgs/c_compiler/test/helpers.dart +++ b/pkgs/c_compiler/test/helpers.dart @@ -4,6 +4,8 @@ import 'dart:io'; +import 'package:logging/logging.dart'; + const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; Future inTempDir( @@ -20,3 +22,16 @@ Future inTempDir( } } } + +Logger createLogger({bool verbose = false}) { + final logger = Logger(''); + if (verbose) { + logger.level = Level.ALL; + } else { + logger.level = Level.WARNING; + } + logger.onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + return logger; +} diff --git a/pkgs/c_compiler/test/native_toolchain/clang_test.dart b/pkgs/c_compiler/test/native_toolchain/clang_test.dart index 490963e38c..a64093b89e 100644 --- a/pkgs/c_compiler/test/native_toolchain/clang_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/clang_test.dart @@ -13,6 +13,6 @@ void main() { ToolRequirement(clang, minimumVersion: Version(14, 0, 0)); final resolved = await clang.defaultResolver!.resolve(); final satisfied = requirement.satisfy(resolved); - print(satisfied); + expect(satisfied?.length, 1); }); } diff --git a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart index 146cdbb16a..71e0d627cc 100644 --- a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart @@ -14,6 +14,6 @@ void main() { ]); final resolved = await androidNdk.defaultResolver!.resolve(); final satisfied = requirement.satisfy(resolved); - print(satisfied); + expect(satisfied?.length, 2); }); } diff --git a/pkgs/c_compiler/test/tool/tool_instance_test.dart b/pkgs/c_compiler/test/tool/tool_instance_test.dart new file mode 100644 index 0000000000..8c38f8c479 --- /dev/null +++ b/pkgs/c_compiler/test/tool/tool_instance_test.dart @@ -0,0 +1,76 @@ +// 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:c_compiler/c_compiler.dart'; +import 'package:collection/collection.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + test('equals and hashCode', () { + final barToolInstance = + ToolInstance(tool: Tool(name: 'bar'), uri: Uri.file('path/to/bar')); + final fooToolInstance = + ToolInstance(tool: Tool(name: 'foo'), uri: Uri.file('path/to/foo')); + + expect(barToolInstance, barToolInstance); + expect(barToolInstance != fooToolInstance, true); + + expect(barToolInstance.hashCode, barToolInstance.hashCode); + expect(barToolInstance.hashCode != fooToolInstance.hashCode, true); + + expect( + ToolInstance( + tool: Tool(name: 'bar'), + version: Version(1, 0, 0), + uri: Uri.file('path/to/bar')) != + ToolInstance( + tool: Tool(name: 'bar'), + version: Version(1, 0, 1), + uri: Uri.file('path/to/bar')), + true); + }); + + test('compareTo', () { + final toolInstances = [ + ToolInstance( + tool: Tool(name: 'bar'), + version: Version(2, 0, 0), + uri: Uri.file('path/to/bar'), + ), + ToolInstance( + tool: Tool(name: 'bar'), + version: Version(1, 0, 0), + uri: Uri.file('path/to/bar')), + ToolInstance( + tool: Tool(name: 'bar'), + uri: Uri.file('path/to/bar'), + ), + ToolInstance( + tool: Tool(name: 'bar'), + uri: Uri.file('path/to/some/other/bar'), + ), + ToolInstance( + tool: Tool(name: 'baz'), + uri: Uri.file('path/to/baz'), + ), + ]; + + final toolInstancesSorted = [...toolInstances]..sort(); + expect(DeepCollectionEquality().equals(toolInstancesSorted, toolInstances), + true); + }); + + test('toString', () { + final instance = ToolInstance( + tool: Tool(name: 'bar'), + version: Version(1, 0, 0), + uri: Uri.file('path/to/bar'), + ); + + expect(instance.toString(), contains('bar')); + expect(instance.toString(), contains('1.0.0')); + expect(instance.toString(), contains('path/to/bar')); + }); +} diff --git a/pkgs/c_compiler/test/tool/tool_requirement_test.dart b/pkgs/c_compiler/test/tool/tool_requirement_test.dart new file mode 100644 index 0000000000..87d0a6162e --- /dev/null +++ b/pkgs/c_compiler/test/tool/tool_requirement_test.dart @@ -0,0 +1,48 @@ +// 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:c_compiler/src/tool/tool.dart'; +import 'package:c_compiler/src/tool/tool_instance.dart'; +import 'package:c_compiler/src/tool/tool_requirement.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + test('toString', () { + final requirement = + ToolRequirement(Tool(name: 'clang'), minimumVersion: Version(10, 0, 0)); + expect(requirement.toString(), contains('clang')); + expect(requirement.toString(), contains('10.0.0')); + }); + + test('RequireOne', () { + final requirement = RequireOne([ + ToolRequirement(Tool(name: 'bar'), minimumVersion: Version(10, 0, 0)), + ToolRequirement(Tool(name: 'foo'), minimumVersion: Version(10, 0, 0)), + ]); + final toolInstances = [ + ToolInstance( + tool: Tool(name: 'bar'), + version: Version(10, 0, 0), + uri: Uri.file('path/to/bar'), + ), + ToolInstance( + tool: Tool(name: 'foo'), + version: Version(9, 0, 0), + uri: Uri.file('path/to/foo'), + ), + ]; + final result = requirement.satisfy(toolInstances); + expect( + result, + [ + ToolInstance( + tool: Tool(name: 'bar'), + version: Version(10, 0, 0), + uri: Uri.file('path/to/bar'), + ) + ], + ); + }); +} diff --git a/pkgs/c_compiler/test/tool/tool_resolver_test.dart b/pkgs/c_compiler/test/tool/tool_resolver_test.dart new file mode 100644 index 0000000000..f0d4a5479e --- /dev/null +++ b/pkgs/c_compiler/test/tool/tool_resolver_test.dart @@ -0,0 +1,61 @@ +// 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 'dart:io'; + +import 'package:c_compiler/src/native_toolchain/clang.dart'; +import 'package:c_compiler/src/tool/tool.dart'; +import 'package:c_compiler/src/tool/tool_error.dart'; +import 'package:c_compiler/src/tool/tool_instance.dart'; +import 'package:c_compiler/src/tool/tool_resolver.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + test('CliVersionResolver.executableVersion', () async { + final clangInstances = await clang.defaultResolver!.resolve(); + expect(clangInstances.isNotEmpty, true); + final version = + await CliVersionResolver.executableVersion(clangInstances.first.uri); + expect(version.major > 5, true); + expect( + () => CliVersionResolver.executableVersion(clangInstances.first.uri, + expectedExitCode: 9999), + throwsA(isA()), + ); + + try { + await CliVersionResolver.executableVersion(clangInstances.first.uri, + expectedExitCode: 9999); + // ignore: avoid_catching_errors + } on ToolError catch (e) { + expect(e.toString(), contains('returned unexpected exit code')); + } + }); + + test('RelativeToolResolver', () async { + await inTempDir((tempUri) async { + final barExeUri = + tempUri.resolve(Target.current.os.executableFileName('bar')); + final bazExeName = Target.current.os.executableFileName('baz'); + final bazExeUri = tempUri.resolve(bazExeName); + await File.fromUri(barExeUri).writeAsString('dummy'); + await File.fromUri(bazExeUri).writeAsString('dummy'); + final barResolver = InstallLocationResolver( + toolName: 'bar', paths: [barExeUri.toFilePath()]); + final bazResolver = RelativeToolResolver( + toolName: 'baz', + wrappedResolver: barResolver, + relativePath: Uri(path: bazExeName), + ); + final resolvedBazInstances = await bazResolver.resolve(); + expect( + resolvedBazInstances, + [ToolInstance(tool: Tool(name: 'baz'), uri: bazExeUri)], + ); + }); + }); +} diff --git a/pkgs/c_compiler/test/tool/tool_test.dart b/pkgs/c_compiler/test/tool/tool_test.dart new file mode 100644 index 0000000000..3fdcf6d261 --- /dev/null +++ b/pkgs/c_compiler/test/tool/tool_test.dart @@ -0,0 +1,27 @@ +// 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:c_compiler/src/native_toolchain/android_ndk.dart'; +import 'package:c_compiler/src/native_toolchain/clang.dart'; +import 'package:c_compiler/src/tool/tool.dart'; +import 'package:c_compiler/src/tool/tool_resolver.dart'; +import 'package:test/test.dart'; + +void main() { + test('equals and hashCode', () async { + expect(clang, clang); + expect(clang != androidNdk, true); + expect( + Tool(name: 'foo'), + Tool(name: 'foo', defaultResolver: PathToolResolver(toolName: 'foo')), + ); + expect(Tool(name: 'foo') != Tool(name: 'bar'), true); + expect( + Tool(name: 'foo').hashCode, + Tool(name: 'foo', defaultResolver: PathToolResolver(toolName: 'foo')) + .hashCode, + ); + expect(Tool(name: 'foo').hashCode != Tool(name: 'bar').hashCode, true); + }); +} diff --git a/pkgs/native_assets_cli/lib/src/model/asset.dart b/pkgs/native_assets_cli/lib/src/model/asset.dart index c65084e6ff..265e3a785b 100644 --- a/pkgs/native_assets_cli/lib/src/model/asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/asset.dart @@ -60,7 +60,7 @@ class AssetAbsolutePath implements AssetPath { List toDartConst() => [_pathTypeValue, uri.toFilePath()]; @override - int get hashCode => uri.hashCode ^ 5; + int get hashCode => Object.hash(uri, 133711); @override bool operator ==(Object other) { @@ -95,7 +95,7 @@ class AssetRelativePath implements AssetPath { List toDartConst() => [_pathTypeValue, uri.toFilePath()]; @override - int get hashCode => uri.hashCode ^ 39; + int get hashCode => Object.hash(uri, 133717); @override bool operator ==(Object other) { @@ -129,7 +129,7 @@ class AssetSystemPath implements AssetPath { List toDartConst() => [_pathTypeValue, uri.toFilePath()]; @override - int get hashCode => uri.hashCode ^ 13; + int get hashCode => Object.hash(uri, 133723); @override bool operator ==(Object other) { @@ -250,8 +250,7 @@ class Asset { } @override - int get hashCode => - name.hashCode ^ packaging.hashCode ^ target.hashCode ^ path.hashCode; + int get hashCode => Object.hash(name, packaging, target, path); Map toYaml() => { _nameKey: name, diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart index 61da4095d5..cf1427b2c0 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -55,6 +55,12 @@ class BuildConfig { Map? get dependencyMetadata => _dependencyMetadata; late final Map? _dependencyMetadata; + /// The underlying config. + /// + /// Can be used for easier access to values on [dependencyMetadata]. + Config get config => _config; + late final Config _config; + factory BuildConfig({ required Uri outDir, required Uri packageRoot, @@ -115,6 +121,7 @@ class BuildConfig { List _readFieldsFromConfig() { var targetSet = false; return [ + (config) => _config = config, (config) => _outDir = config.path(outDirConfigKey), (config) => _packageRoot = config.path(packageRootConfigKey), (config) { @@ -209,17 +216,17 @@ class BuildConfig { return true; } - // Ordering of fields doesn't matter. @override - int get hashCode => - _outDir.hashCode ^ - _packageRoot.hashCode ^ - _target.hashCode ^ - _targetIOSSdk.hashCode ^ - _cc.hashCode ^ - _ld.hashCode ^ - _packaging.hashCode ^ - DeepCollectionEquality().hash(_dependencyMetadata); + int get hashCode => Object.hash( + _outDir, + _packageRoot, + _target, + _targetIOSSdk, + _cc, + _ld, + _packaging, + DeepCollectionEquality().hash(_dependencyMetadata), + ); @override String toString() => 'BuildConfig(${toYaml()})'; From 652369cc74834944ee6cbbb24cfb0c4bffe8df39 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 12 Apr 2023 10:58:58 +0000 Subject: [PATCH 08/27] massage dependencies --- pkgs/c_compiler/pubspec.yaml | 8 +++----- pkgs/native_assets_cli/pubspec.yaml | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pkgs/c_compiler/pubspec.yaml b/pkgs/c_compiler/pubspec.yaml index 6e4a826185..948eef51a5 100644 --- a/pkgs/c_compiler/pubspec.yaml +++ b/pkgs/c_compiler/pubspec.yaml @@ -12,14 +12,12 @@ dependencies: cli_config: ^0.1.1 glob: ^2.1.1 logging: ^1.1.1 - native_assets_cli: ^0.1.0-dev + # TODO(dacoharkes): Publish native_assets_cli first. + native_assets_cli: + path: ../native_assets_cli/ pub_semver: ^2.1.3 dev_dependencies: collection: ^1.17.1 dart_flutter_team_lints: ^1.0.0 test: ^1.21.0 - -dependency_overrides: - native_assets_cli: - path: ../native_assets_cli/ diff --git a/pkgs/native_assets_cli/pubspec.yaml b/pkgs/native_assets_cli/pubspec.yaml index 53966c5cc5..a1b6902c48 100644 --- a/pkgs/native_assets_cli/pubspec.yaml +++ b/pkgs/native_assets_cli/pubspec.yaml @@ -6,8 +6,6 @@ repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli environment: sdk: ">=2.19.3 <4.0.0" -publish_to: none - dependencies: cli_config: ^0.1.1 collection: ^1.17.1 From 1d0262b6669b2722313cbe2bfbda0e133e4abede Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 12 Apr 2023 11:03:58 +0000 Subject: [PATCH 09/27] which clang version on GitHub CI? --- pkgs/c_compiler/test/native_toolchain/clang_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/c_compiler/test/native_toolchain/clang_test.dart b/pkgs/c_compiler/test/native_toolchain/clang_test.dart index a64093b89e..346c617c89 100644 --- a/pkgs/c_compiler/test/native_toolchain/clang_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/clang_test.dart @@ -12,6 +12,8 @@ void main() { final requirement = ToolRequirement(clang, minimumVersion: Version(14, 0, 0)); final resolved = await clang.defaultResolver!.resolve(); + expect(resolved.isNotEmpty, true); + print(resolved); final satisfied = requirement.satisfy(resolved); expect(satisfied?.length, 1); }); From 983f3321ec478ce45429863e4183d604fe346f4f Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 12 Apr 2023 11:14:47 +0000 Subject: [PATCH 10/27] Allow tool pre-release in smoke test --- .../test/native_toolchain/clang_test.dart | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkgs/c_compiler/test/native_toolchain/clang_test.dart b/pkgs/c_compiler/test/native_toolchain/clang_test.dart index 346c617c89..5e4ea9b409 100644 --- a/pkgs/c_compiler/test/native_toolchain/clang_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/clang_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:c_compiler/src/native_toolchain/clang.dart'; +import 'package:c_compiler/src/tool/tool_instance.dart'; import 'package:c_compiler/src/tool/tool_requirement.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; @@ -10,11 +11,22 @@ import 'package:test/test.dart'; void main() { test('clang smoke test', () async { final requirement = - ToolRequirement(clang, minimumVersion: Version(14, 0, 0)); + ToolRequirement(clang, minimumVersion: Version(14, 0, 0, pre: '0')); final resolved = await clang.defaultResolver!.resolve(); expect(resolved.isNotEmpty, true); - print(resolved); final satisfied = requirement.satisfy(resolved); expect(satisfied?.length, 1); }); + + test('clang versions', () { + final clangInstance = ToolInstance( + tool: clang, + uri: Uri.file('some/path'), + version: Version.parse('14.0.0-1'), + ); + final requirement = + ToolRequirement(clang, minimumVersion: Version(14, 0, 0, pre: '0')); + final satisfied = requirement.satisfy([clangInstance]); + expect(satisfied?.length, 1); + }); } From 40582b9b16118e324c206c4fc0d026fe95886e61 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 12 Apr 2023 14:43:18 +0000 Subject: [PATCH 11/27] Use `printOnFailure` --- .../cbuilder/cbuilder_cross_android_test.dart | 2 -- .../cbuilder/cbuilder_cross_linux_host_test.dart | 2 -- pkgs/c_compiler/test/cbuilder/cbuilder_test.dart | 2 -- pkgs/c_compiler/test/helpers.dart | 16 +++++----------- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart index a78be33186..ca1fdf1b06 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -11,8 +11,6 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() { - final logger = createLogger(); - const targets = [ Target.androidArm, Target.androidArm64, diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart index 7b4494531a..cd5390aaf3 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -11,8 +11,6 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() { - final logger = createLogger(); - const targets = [ Target.linuxArm, Target.linuxArm64, diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index 5245c713d1..b4e61a1e11 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -12,8 +12,6 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() { - final logger = createLogger(); - test('Cbuilder executable', () async { await inTempDir((tempUri) async { final packageUri = Directory.current.uri; diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart index b5999051a7..e300b65ded 100644 --- a/pkgs/c_compiler/test/helpers.dart +++ b/pkgs/c_compiler/test/helpers.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; +import 'package:test/test.dart'; const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; @@ -23,15 +24,8 @@ Future inTempDir( } } -Logger createLogger({bool verbose = false}) { - final logger = Logger(''); - if (verbose) { - logger.level = Level.ALL; - } else { - logger.level = Level.WARNING; - } - logger.onRecord.listen((record) { - print('${record.level.name}: ${record.time}: ${record.message}'); +final logger = Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + printOnFailure('${record.level.name}: ${record.time}: ${record.message}'); }); - return logger; -} From a59abceb73f952f422eae5b9131c86e0927613a9 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 10:08:27 +0000 Subject: [PATCH 12/27] Add archiver tools and logger to tool resolvers --- .../lib/src/cbuilder/compiler_resolver.dart | 13 ++- .../lib/src/cbuilder/run_cbuilder.dart | 2 +- .../lib/src/native_toolchain/android_ndk.dart | 19 +++- .../lib/src/native_toolchain/clang.dart | 26 ++++-- .../lib/src/native_toolchain/gcc.dart | 33 ++++++- .../lib/src/tool/tool_resolver.dart | 87 +++++++++++++------ pkgs/c_compiler/test/helpers.dart | 9 ++ .../test/native_toolchain/clang_test.dart | 11 ++- .../test/native_toolchain/gcc_test.dart | 37 ++++++++ .../test/native_toolchain/ndk_test.dart | 7 +- .../test/tool/tool_resolver_test.dart | 24 ++++- 11 files changed, 216 insertions(+), 52 deletions(-) create mode 100644 pkgs/c_compiler/test/native_toolchain/gcc_test.dart diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index 22aba873c5..7fe840cc41 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -25,7 +25,7 @@ class CompilerResolver implements ToolResolver { }); @override - Future> resolve() async { + Future> resolve({Logger? logger}) async { final tool = selectCompiler(); // First, check if the launcher provided a direct path to the compiler. @@ -78,7 +78,8 @@ class CompilerResolver implements ToolResolver { case Target.androidX64: return androidNdkClang; } - throw ToolError("No tool available on host '$host' for target: '$target'."); + throw ToolError( + "No tool configured on host '$host' for target: '$target'."); } Future _tryLoadCompilerFromConfig( @@ -100,15 +101,11 @@ class CompilerResolver implements ToolResolver { /// If a build is invoked Future _tryLoadCompilerFromNativeToolchain(Tool tool) async { - final resolved = (await tool.defaultResolver!.resolve()) + final resolved = (await tool.defaultResolver!.resolve(logger: logger)) .where((i) => i.tool == tool) .toList() ..sort(); - if (resolved.isEmpty) { - logger?.warning('Clang could not be found by package:native_toolchain.'); - return null; - } - return resolved.first; + return resolved.isEmpty ? null : resolved.first; } Future resolveLinker( diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index ff4b8b9cf7..7ecc647634 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -43,7 +43,7 @@ class RunCBuilder { return _compilerCached!; } final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); - _compilerCached = (await resolver.resolve()).first.uri; + _compilerCached = (await resolver.resolve(logger: logger)).first.uri; return _compilerCached!; } diff --git a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart index 3de9fa8bd8..7d9e306abf 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart @@ -4,6 +4,8 @@ import 'dart:io'; +import 'package:logging/logging.dart'; + import '../tool/tool.dart'; import '../tool/tool_instance.dart'; import '../tool/tool_resolver.dart'; @@ -19,6 +21,12 @@ final androidNdkClang = Tool( defaultResolver: _AndroidNdkResolver(), ); +/// A clang that knows how to target Android. +final androidNdkClangAr = Tool( + name: 'Android NDK Clang Archiver', + defaultResolver: _AndroidNdkResolver(), +); + class _AndroidNdkResolver implements ToolResolver { final installLocationResolver = PathVersionResolver( wrappedResolver: ToolResolvers([ @@ -40,8 +48,8 @@ class _AndroidNdkResolver implements ToolResolver { ); @override - Future> resolve() async { - final ndkInstances = await installLocationResolver.resolve(); + Future> resolve({Logger? logger}) async { + final ndkInstances = await installLocationResolver.resolve(logger: logger); return [ for (final ndkInstance in ndkInstances) ...[ @@ -67,6 +75,13 @@ class _AndroidNdkResolver implements ToolResolver { uri: clangUri, ))); } + final arUri = hostArchDir.uri.resolve('bin/llvm-ar'); + if (await File.fromUri(arUri).exists()) { + result.add(await CliVersionResolver.lookupVersion(ToolInstance( + tool: androidNdkClangAr, + uri: arUri, + ))); + } } return result; } diff --git a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart index f62a4fe0de..bcaad40984 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart @@ -6,9 +6,23 @@ import '../tool/tool.dart'; import '../tool/tool_resolver.dart'; final Tool clang = Tool( - name: 'Clang', - defaultResolver: CliVersionResolver( - wrappedResolver: ToolResolvers([ - PathToolResolver(toolName: 'Clang'), - ]), - )); + name: 'Clang', + defaultResolver: CliVersionResolver( + wrappedResolver: ToolResolvers([ + PathToolResolver(toolName: 'Clang'), + ]), + ), +); + +final Tool llvmAr = Tool( + name: 'llvm-ar', + defaultResolver: CliVersionResolver( + wrappedResolver: ToolResolvers([ + RelativeToolResolver( + toolName: 'llvm-ar', + wrappedResolver: clang.defaultResolver!, + relativePath: Uri.file('llvm-ar'), + ), + ]), + ), +); diff --git a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart index 935e6baa78..5aaebe2c8a 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart @@ -7,15 +7,42 @@ import '../tool/tool_resolver.dart'; final i686LinuxGnuGcc = Tool( name: 'i686-linux-gnu-gcc', - defaultResolver: PathToolResolver(toolName: 'i686-linux-gnu-gcc'), + defaultResolver: CliVersionResolver( + wrappedResolver: PathToolResolver(toolName: 'i686-linux-gnu-gcc'), + ), ); +final i686LinuxGnuGccAr = _gccArchiver(i686LinuxGnuGcc); + final armLinuxGnueabihfGcc = Tool( name: 'arm-linux-gnueabihf-gcc', - defaultResolver: PathToolResolver(toolName: 'arm-linux-gnueabihf-gcc'), + defaultResolver: CliVersionResolver( + wrappedResolver: PathToolResolver(toolName: 'arm-linux-gnueabihf-gcc'), + ), ); +final armLinuxGnueabihfGccAr = _gccArchiver(armLinuxGnueabihfGcc); + final aarch64LinuxGnuGcc = Tool( name: 'aarch64-linux-gnu-gcc', - defaultResolver: PathToolResolver(toolName: 'aarch64-linux-gnu-gcc'), + defaultResolver: CliVersionResolver( + wrappedResolver: PathToolResolver(toolName: 'aarch64-linux-gnu-gcc'), + ), ); + +final aarch64LinuxGnuGccAr = _gccArchiver(aarch64LinuxGnuGcc); + +/// Finds the `ar` belonging to that GCC. +Tool _gccArchiver(Tool gcc) { + final arName = gcc.name.replaceAll('-gcc', '-gcc-ar'); + return Tool( + name: arName, + defaultResolver: ToolResolvers([ + RelativeToolResolver( + toolName: arName, + wrappedResolver: gcc.defaultResolver!, + relativePath: Uri.file(arName), + ), + ]), + ); +} diff --git a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart index 335db92057..aaf003c53d 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; +import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -18,7 +19,7 @@ import 'tool_instance.dart'; abstract class ToolResolver { /// Resolves tools on the host system. - Future> resolve(); + Future> resolve({Logger? logger}); } /// Tries to resolve a tool on the `PATH`. @@ -31,12 +32,18 @@ class PathToolResolver extends ToolResolver { PathToolResolver({required this.toolName}); @override - Future> resolve() async { + Future> resolve({Logger? logger}) async { + logger?.finer('Looking for $toolName on PATH.'); final uri = await runWhich(); - - return [ - if (uri != null) ToolInstance(tool: Tool(name: toolName), uri: uri), + if (uri == null) { + logger?.fine('Did not find $toolName on PATH.'); + return []; + } + final toolInstances = [ + ToolInstance(tool: Tool(name: toolName), uri: uri), ]; + logger?.fine('Found ${toolInstances.single}.'); + return toolInstances; } String get executableName => @@ -67,22 +74,24 @@ class CliVersionResolver implements ToolResolver { CliVersionResolver({required this.wrappedResolver}); @override - Future> resolve() async { - final toolInstances = await wrappedResolver.resolve(); - + Future> resolve({Logger? logger}) async { + final toolInstances = await wrappedResolver.resolve(logger: logger); return [ for (final toolInstance in toolInstances) - await lookupVersion(toolInstance) + await lookupVersion(toolInstance, logger: logger) ]; } - static Future lookupVersion(ToolInstance toolInstance) async { - if (toolInstance.version != null) { - return toolInstance; - } - return toolInstance.copyWith( - version: await executableVersion(toolInstance.uri), - ); + static Future lookupVersion( + ToolInstance toolInstance, { + Logger? logger, + }) async { + if (toolInstance.version != null) return toolInstance; + logger?.finer('Looking up version with --version for $toolInstance.'); + final version = await executableVersion(toolInstance.uri); + final result = toolInstance.copyWith(version: version); + logger?.fine('Found version for $result.'); + return result; } static Future executableVersion( @@ -111,8 +120,8 @@ class PathVersionResolver implements ToolResolver { PathVersionResolver({required this.wrappedResolver}); @override - Future> resolve() async { - final toolInstances = await wrappedResolver.resolve(); + Future> resolve({Logger? logger}) async { + final toolInstances = await wrappedResolver.resolve(logger: logger); return [ for (final toolInstance in toolInstances) lookupVersion(toolInstance) @@ -135,14 +144,17 @@ class PathVersionResolver implements ToolResolver { } } +/// A resolver which invokes all [resolvers] tools. class ToolResolvers implements ToolResolver { final List resolvers; ToolResolvers(this.resolvers); @override - Future> resolve() async => - [for (final resolver in resolvers) ...await resolver.resolve()]; + Future> resolve({Logger? logger}) async => [ + for (final resolver in resolvers) + ...await resolver.resolve(logger: logger) + ]; } class InstallLocationResolver implements ToolResolver { @@ -157,10 +169,22 @@ class InstallLocationResolver implements ToolResolver { static const home = '\$HOME'; @override - Future> resolve() async => - [for (final path in paths) ...await tryResolvePath(path)] - .map((uri) => ToolInstance(tool: Tool(name: toolName), uri: uri)) - .toList(); + Future> resolve({Logger? logger}) async { + logger?.finer('Looking for $toolName 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), + ]; + if (toolInstances.isNotEmpty) { + logger?.fine('Found $toolInstances.'); + } else { + logger?.finer('Found no $toolName in $paths.'); + } + return toolInstances; + } Future> tryResolvePath(String path) async { if (path.startsWith(home)) { @@ -199,14 +223,23 @@ class RelativeToolResolver implements ToolResolver { }); @override - Future> resolve() async { - final otherToolInstances = await wrappedResolver.resolve(); - return [ + Future> resolve({Logger? logger}) async { + final otherToolInstances = await wrappedResolver.resolve(logger: logger); + + logger?.finer('Looking for $toolName relative to $otherToolInstances ' + 'with $relativePath.'); + final result = [ for (final toolInstance in otherToolInstances) ToolInstance( tool: Tool(name: toolName), uri: toolInstance.uri.resolveUri(relativePath), ), ]; + if (result.isNotEmpty) { + logger?.fine('Found $result.'); + } else { + logger?.finer('Found no $toolName relative to $otherToolInstances.'); + } + return result; } } diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart index e300b65ded..717a3db547 100644 --- a/pkgs/c_compiler/test/helpers.dart +++ b/pkgs/c_compiler/test/helpers.dart @@ -2,6 +2,7 @@ // 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 'dart:async'; import 'dart:io'; import 'package:logging/logging.dart'; @@ -24,8 +25,16 @@ Future inTempDir( } } +/// Logger that outputs the full trace when a test fails. final logger = Logger('') ..level = Level.ALL ..onRecord.listen((record) { printOnFailure('${record.level.name}: ${record.time}: ${record.message}'); }); + +Logger createCapturingLogger(List capturedMessages) => Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + printOnFailure('${record.level.name}: ${record.time}: ${record.message}'); + capturedMessages.add(record.message); + }); diff --git a/pkgs/c_compiler/test/native_toolchain/clang_test.dart b/pkgs/c_compiler/test/native_toolchain/clang_test.dart index 5e4ea9b409..b0bb4bb423 100644 --- a/pkgs/c_compiler/test/native_toolchain/clang_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/clang_test.dart @@ -8,11 +8,13 @@ import 'package:c_compiler/src/tool/tool_requirement.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; +import '../helpers.dart'; + void main() { test('clang smoke test', () async { final requirement = ToolRequirement(clang, minimumVersion: Version(14, 0, 0, pre: '0')); - final resolved = await clang.defaultResolver!.resolve(); + final resolved = await clang.defaultResolver!.resolve(logger: logger); expect(resolved.isNotEmpty, true); final satisfied = requirement.satisfy(resolved); expect(satisfied?.length, 1); @@ -29,4 +31,11 @@ void main() { final satisfied = requirement.satisfy([clangInstance]); expect(satisfied?.length, 1); }); + test('llvm-ar smoke test', () async { + final requirement = ToolRequirement(llvmAr); + final resolved = await llvmAr.defaultResolver!.resolve(logger: logger); + expect(resolved.isNotEmpty, true); + final satisfied = requirement.satisfy(resolved); + expect(satisfied?.length, 1); + }); } diff --git a/pkgs/c_compiler/test/native_toolchain/gcc_test.dart b/pkgs/c_compiler/test/native_toolchain/gcc_test.dart new file mode 100644 index 0000000000..23a232fb02 --- /dev/null +++ b/pkgs/c_compiler/test/native_toolchain/gcc_test.dart @@ -0,0 +1,37 @@ +// 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:c_compiler/src/native_toolchain/gcc.dart'; +import 'package:c_compiler/src/tool/tool_requirement.dart'; +import 'package:c_compiler/src/tool/tool_resolver.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + test('gcc cross compilation smoke test', () async { + final tools = [ + i686LinuxGnuGcc, + i686LinuxGnuGccAr, + armLinuxGnueabihfGcc, + armLinuxGnueabihfGccAr, + aarch64LinuxGnuGcc, + aarch64LinuxGnuGccAr, + ]; + + final resolver = ToolResolvers([ + for (final tool in tools) tool.defaultResolver!, + ]); + + final resolved = await resolver.resolve(logger: logger); + expect(resolved.isNotEmpty, true); + + final requirement = RequireAll([ + for (final tool in tools) ToolRequirement(tool), + ]); + + final satisfied = requirement.satisfy(resolved); + expect(satisfied?.length, tools.length); + }); +} diff --git a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart index 71e0d627cc..1072b55dfb 100644 --- a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart @@ -6,14 +6,17 @@ import 'package:c_compiler/src/native_toolchain/android_ndk.dart'; import 'package:c_compiler/src/tool/tool_requirement.dart'; import 'package:test/test.dart'; +import '../helpers.dart'; + void main() { test('NDK smoke test', () async { final requirement = RequireAll([ ToolRequirement(androidNdk), ToolRequirement(androidNdkClang), + ToolRequirement(androidNdkClangAr), ]); - final resolved = await androidNdk.defaultResolver!.resolve(); + final resolved = await androidNdk.defaultResolver!.resolve(logger: logger); final satisfied = requirement.satisfy(resolved); - expect(satisfied?.length, 2); + expect(satisfied?.length, 3); }); } diff --git a/pkgs/c_compiler/test/tool/tool_resolver_test.dart b/pkgs/c_compiler/test/tool/tool_resolver_test.dart index f0d4a5479e..c02493c150 100644 --- a/pkgs/c_compiler/test/tool/tool_resolver_test.dart +++ b/pkgs/c_compiler/test/tool/tool_resolver_test.dart @@ -16,7 +16,7 @@ import '../helpers.dart'; void main() { test('CliVersionResolver.executableVersion', () async { - final clangInstances = await clang.defaultResolver!.resolve(); + final clangInstances = await clang.defaultResolver!.resolve(logger: logger); expect(clangInstances.isNotEmpty, true); final version = await CliVersionResolver.executableVersion(clangInstances.first.uri); @@ -51,11 +51,31 @@ void main() { wrappedResolver: barResolver, relativePath: Uri(path: bazExeName), ); - final resolvedBazInstances = await bazResolver.resolve(); + final resolvedBazInstances = await bazResolver.resolve(logger: logger); expect( resolvedBazInstances, [ToolInstance(tool: Tool(name: 'baz'), uri: bazExeUri)], ); }); }); + + test('logger', () async { + await inTempDir((tempUri) async { + final barExeUri = + tempUri.resolve(Target.current.os.executableFileName('bar')); + final bazExeName = Target.current.os.executableFileName('baz'); + final bazExeUri = tempUri.resolve(bazExeName); + await File.fromUri(barExeUri).writeAsString('dummy'); + final barResolver = InstallLocationResolver( + toolName: 'bar', paths: [barExeUri.toFilePath()]); + final bazResolver = InstallLocationResolver( + toolName: 'baz', paths: [bazExeUri.toFilePath()]); + final barLogs = []; + final bazLogs = []; + await barResolver.resolve(logger: createCapturingLogger(barLogs)); + await bazResolver.resolve(logger: createCapturingLogger(bazLogs)); + expect(barLogs.join('\n'), contains('Found [ToolInstance(bar')); + expect(bazLogs.join('\n'), contains('Found no baz')); + }); + }); } From dac31bdcacd9f06cd74d21f0b49502c5f7eae21d Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 10:37:09 +0000 Subject: [PATCH 13/27] Find linkers --- .../lib/src/native_toolchain/android_ndk.dart | 13 +++++++- .../lib/src/native_toolchain/clang.dart | 13 ++++++++ .../lib/src/native_toolchain/gcc.dart | 31 ++++++++++++++----- .../test/native_toolchain/clang_test.dart | 9 ++++++ .../test/native_toolchain/gcc_test.dart | 11 ++++--- .../test/native_toolchain/ndk_test.dart | 3 +- 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart index 7d9e306abf..0462f53ac8 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart @@ -21,12 +21,16 @@ final androidNdkClang = Tool( defaultResolver: _AndroidNdkResolver(), ); -/// A clang that knows how to target Android. final androidNdkClangAr = Tool( name: 'Android NDK Clang Archiver', defaultResolver: _AndroidNdkResolver(), ); +final androidNdkClangLd = Tool( + name: 'Android NDK Clang Linker', + defaultResolver: _AndroidNdkResolver(), +); + class _AndroidNdkResolver implements ToolResolver { final installLocationResolver = PathVersionResolver( wrappedResolver: ToolResolvers([ @@ -82,6 +86,13 @@ class _AndroidNdkResolver implements ToolResolver { uri: arUri, ))); } + final ldUri = hostArchDir.uri.resolve('bin/ld.lld'); + if (await File.fromUri(arUri).exists()) { + result.add(await CliVersionResolver.lookupVersion(ToolInstance( + tool: androidNdkClangLd, + uri: ldUri, + ))); + } } return result; } diff --git a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart index bcaad40984..c0701bfb89 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart @@ -26,3 +26,16 @@ final Tool llvmAr = Tool( ]), ), ); + +final Tool lld = Tool( + name: 'ld.lld', + defaultResolver: CliVersionResolver( + wrappedResolver: ToolResolvers([ + RelativeToolResolver( + toolName: 'ld.lld', + wrappedResolver: clang.defaultResolver!, + relativePath: Uri.file('ld.lld'), + ), + ]), + ), +); diff --git a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart index 5aaebe2c8a..0ddfde7c03 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart @@ -14,6 +14,8 @@ final i686LinuxGnuGcc = Tool( final i686LinuxGnuGccAr = _gccArchiver(i686LinuxGnuGcc); +final i686LinuxGnuGccLd = _gccLinker(i686LinuxGnuGcc); + final armLinuxGnueabihfGcc = Tool( name: 'arm-linux-gnueabihf-gcc', defaultResolver: CliVersionResolver( @@ -23,6 +25,8 @@ final armLinuxGnueabihfGcc = Tool( final armLinuxGnueabihfGccAr = _gccArchiver(armLinuxGnueabihfGcc); +final armLinuxGnueabihfGccLd = _gccLinker(armLinuxGnueabihfGcc); + final aarch64LinuxGnuGcc = Tool( name: 'aarch64-linux-gnu-gcc', defaultResolver: CliVersionResolver( @@ -32,17 +36,30 @@ final aarch64LinuxGnuGcc = Tool( final aarch64LinuxGnuGccAr = _gccArchiver(aarch64LinuxGnuGcc); +final aarch64LinuxGnuGccLd = _gccLinker(aarch64LinuxGnuGcc); + /// Finds the `ar` belonging to that GCC. Tool _gccArchiver(Tool gcc) { final arName = gcc.name.replaceAll('-gcc', '-gcc-ar'); return Tool( name: arName, - defaultResolver: ToolResolvers([ - RelativeToolResolver( - toolName: arName, - wrappedResolver: gcc.defaultResolver!, - relativePath: Uri.file(arName), - ), - ]), + defaultResolver: RelativeToolResolver( + toolName: arName, + wrappedResolver: gcc.defaultResolver!, + relativePath: Uri.file(arName), + ), + ); +} + +/// Finds the `ld` belonging to that GCC. +Tool _gccLinker(Tool gcc) { + final arName = gcc.name.replaceAll('-gcc', '-gcc-ld'); + return Tool( + name: arName, + defaultResolver: RelativeToolResolver( + toolName: arName, + wrappedResolver: gcc.defaultResolver!, + relativePath: Uri.file(arName), + ), ); } diff --git a/pkgs/c_compiler/test/native_toolchain/clang_test.dart b/pkgs/c_compiler/test/native_toolchain/clang_test.dart index b0bb4bb423..b8373d7918 100644 --- a/pkgs/c_compiler/test/native_toolchain/clang_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/clang_test.dart @@ -31,6 +31,7 @@ void main() { final satisfied = requirement.satisfy([clangInstance]); expect(satisfied?.length, 1); }); + test('llvm-ar smoke test', () async { final requirement = ToolRequirement(llvmAr); final resolved = await llvmAr.defaultResolver!.resolve(logger: logger); @@ -38,4 +39,12 @@ void main() { final satisfied = requirement.satisfy(resolved); expect(satisfied?.length, 1); }); + + test('ld test', () async { + final requirement = ToolRequirement(lld); + final resolved = await lld.defaultResolver!.resolve(logger: logger); + expect(resolved.isNotEmpty, true); + final satisfied = requirement.satisfy(resolved); + expect(satisfied?.length, 1); + }); } diff --git a/pkgs/c_compiler/test/native_toolchain/gcc_test.dart b/pkgs/c_compiler/test/native_toolchain/gcc_test.dart index 23a232fb02..a70ee356c6 100644 --- a/pkgs/c_compiler/test/native_toolchain/gcc_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/gcc_test.dart @@ -12,12 +12,15 @@ import '../helpers.dart'; void main() { test('gcc cross compilation smoke test', () async { final tools = [ - i686LinuxGnuGcc, - i686LinuxGnuGccAr, - armLinuxGnueabihfGcc, - armLinuxGnueabihfGccAr, aarch64LinuxGnuGcc, aarch64LinuxGnuGccAr, + aarch64LinuxGnuGccLd, + armLinuxGnueabihfGcc, + armLinuxGnueabihfGccAr, + armLinuxGnueabihfGccLd, + i686LinuxGnuGcc, + i686LinuxGnuGccAr, + i686LinuxGnuGccLd, ]; final resolver = ToolResolvers([ diff --git a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart index 1072b55dfb..12b592790a 100644 --- a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart @@ -14,9 +14,10 @@ void main() { ToolRequirement(androidNdk), ToolRequirement(androidNdkClang), ToolRequirement(androidNdkClangAr), + ToolRequirement(androidNdkClangLd), ]); final resolved = await androidNdk.defaultResolver!.resolve(logger: logger); final satisfied = requirement.satisfy(resolved); - expect(satisfied?.length, 3); + expect(satisfied?.length, 4); }); } From eb1156277ef4d2cd4fa6cae88f6643f6b914870a Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 13:53:25 +0000 Subject: [PATCH 14/27] Cleanup tool naming --- .../lib/src/native_toolchain/android_ndk.dart | 20 +++-- .../lib/src/native_toolchain/clang.dart | 17 +++- .../lib/src/native_toolchain/gcc.dart | 89 +++++++++++-------- pkgs/c_compiler/lib/src/tool/tool.dart | 3 + .../lib/src/tool/tool_resolver.dart | 11 ++- .../test/native_toolchain/gcc_test.dart | 66 ++++++++------ .../test/native_toolchain/ndk_test.dart | 4 +- 7 files changed, 129 insertions(+), 81 deletions(-) diff --git a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart index 0462f53ac8..40ad71a0db 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/android_ndk.dart @@ -5,29 +5,33 @@ import 'dart:io'; import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; import '../tool/tool.dart'; import '../tool/tool_instance.dart'; import '../tool/tool_resolver.dart'; +import 'clang.dart'; final androidNdk = Tool( name: 'Android NDK', defaultResolver: _AndroidNdkResolver(), ); -/// A clang that knows how to target Android. +/// [clang] with [Tool.defaultResolver] for the [OS.android] NDK. final androidNdkClang = Tool( - name: 'Android NDK Clang', + name: clang.name, defaultResolver: _AndroidNdkResolver(), ); -final androidNdkClangAr = Tool( - name: 'Android NDK Clang Archiver', +/// [llvmAr] with [Tool.defaultResolver] for the [OS.android] NDK. +final androidNdkLlvmAr = Tool( + name: llvmAr.name, defaultResolver: _AndroidNdkResolver(), ); -final androidNdkClangLd = Tool( - name: 'Android NDK Clang Linker', +/// [lld] with [Tool.defaultResolver] for the [OS.android] NDK. +final androidNdkLld = Tool( + name: lld.name, defaultResolver: _AndroidNdkResolver(), ); @@ -82,14 +86,14 @@ class _AndroidNdkResolver implements ToolResolver { final arUri = hostArchDir.uri.resolve('bin/llvm-ar'); if (await File.fromUri(arUri).exists()) { result.add(await CliVersionResolver.lookupVersion(ToolInstance( - tool: androidNdkClangAr, + tool: androidNdkLlvmAr, uri: arUri, ))); } final ldUri = hostArchDir.uri.resolve('bin/ld.lld'); if (await File.fromUri(arUri).exists()) { result.add(await CliVersionResolver.lookupVersion(ToolInstance( - tool: androidNdkClangLd, + tool: androidNdkLld, uri: ldUri, ))); } diff --git a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart index c0701bfb89..d1e8845763 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/clang.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/clang.dart @@ -5,6 +5,9 @@ import '../tool/tool.dart'; import '../tool/tool_resolver.dart'; +/// The Clang compiler. +/// +/// https://clang.llvm.org/ final Tool clang = Tool( name: 'Clang', defaultResolver: CliVersionResolver( @@ -14,12 +17,15 @@ final Tool clang = Tool( ), ); +/// The LLVM archiver. +/// +/// https://llvm.org/docs/CommandGuide/llvm-ar.html final Tool llvmAr = Tool( - name: 'llvm-ar', + name: 'LLVM archiver', defaultResolver: CliVersionResolver( wrappedResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'llvm-ar', + toolName: 'LLVM archiver', wrappedResolver: clang.defaultResolver!, relativePath: Uri.file('llvm-ar'), ), @@ -27,12 +33,15 @@ final Tool llvmAr = Tool( ), ); +/// The LLVM Linker. +/// +/// https://lld.llvm.org/ final Tool lld = Tool( - name: 'ld.lld', + name: 'LLD', defaultResolver: CliVersionResolver( wrappedResolver: ToolResolvers([ RelativeToolResolver( - toolName: 'ld.lld', + toolName: 'LLD', wrappedResolver: clang.defaultResolver!, relativePath: Uri.file('ld.lld'), ), diff --git a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart index 0ddfde7c03..0f2477b155 100644 --- a/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart +++ b/pkgs/c_compiler/lib/src/native_toolchain/gcc.dart @@ -2,64 +2,81 @@ // 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:native_assets_cli/native_assets_cli.dart'; + import '../tool/tool.dart'; import '../tool/tool_resolver.dart'; -final i686LinuxGnuGcc = Tool( - name: 'i686-linux-gnu-gcc', - defaultResolver: CliVersionResolver( - wrappedResolver: PathToolResolver(toolName: 'i686-linux-gnu-gcc'), - ), -); +/// The GNU Compiler Collection. +/// +/// https://gcc.gnu.org/ +final gcc = Tool(name: 'GCC'); + +/// The GNU GCC archiver. +final gnuArchiver = Tool(name: 'GNU archiver'); + +/// The GNU linker. +/// +/// https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/ld.html +final gnuLinker = Tool(name: 'GNU linker'); + +/// [gcc] with [Tool.defaultResolver] for [Architecture.ia32]. +final i686LinuxGnuGcc = _gcc('i686-linux-gnu'); + +/// [gnuArchiver] with [Tool.defaultResolver] for [Architecture.ia32]. +final i686LinuxGnuGccAr = _gnuArchiver('i686-linux-gnu'); -final i686LinuxGnuGccAr = _gccArchiver(i686LinuxGnuGcc); +/// [gnuLinker] with [Tool.defaultResolver] for [Architecture.ia32]. +final i686LinuxGnuLd = _gnuLinker('i686-linux-gnu'); -final i686LinuxGnuGccLd = _gccLinker(i686LinuxGnuGcc); +/// [gcc] with [Tool.defaultResolver] for [Architecture.arm]. +final armLinuxGnueabihfGcc = _gcc('arm-linux-gnueabihf'); -final armLinuxGnueabihfGcc = Tool( - name: 'arm-linux-gnueabihf-gcc', - defaultResolver: CliVersionResolver( - wrappedResolver: PathToolResolver(toolName: 'arm-linux-gnueabihf-gcc'), - ), -); +/// [gnuArchiver] with [Tool.defaultResolver] for [Architecture.arm]. +final armLinuxGnueabihfGccAr = _gnuArchiver('arm-linux-gnueabihf'); -final armLinuxGnueabihfGccAr = _gccArchiver(armLinuxGnueabihfGcc); +/// [gnuLinker] with [Tool.defaultResolver] for [Architecture.arm]. +final armLinuxGnueabihfLd = _gnuLinker('arm-linux-gnueabihf'); -final armLinuxGnueabihfGccLd = _gccLinker(armLinuxGnueabihfGcc); +/// [gcc] with [Tool.defaultResolver] for [Architecture.arm64]. +final aarch64LinuxGnuGcc = _gcc('aarch64-linux-gnu'); -final aarch64LinuxGnuGcc = Tool( - name: 'aarch64-linux-gnu-gcc', - defaultResolver: CliVersionResolver( - wrappedResolver: PathToolResolver(toolName: 'aarch64-linux-gnu-gcc'), - ), -); +/// [gnuArchiver] with [Tool.defaultResolver] for [Architecture.arm64]. +final aarch64LinuxGnuGccAr = _gnuArchiver('aarch64-linux-gnu'); -final aarch64LinuxGnuGccAr = _gccArchiver(aarch64LinuxGnuGcc); +/// [gnuLinker] with [Tool.defaultResolver] for [Architecture.arm64]. +final aarch64LinuxGnuLd = _gnuLinker('aarch64-linux-gnu'); -final aarch64LinuxGnuGccLd = _gccLinker(aarch64LinuxGnuGcc); +Tool _gcc(String prefix) => Tool( + name: gcc.name, + defaultResolver: CliVersionResolver( + wrappedResolver: PathToolResolver( + toolName: gcc.name, + executableName: '$prefix-gcc', + ), + ), + ); -/// Finds the `ar` belonging to that GCC. -Tool _gccArchiver(Tool gcc) { - final arName = gcc.name.replaceAll('-gcc', '-gcc-ar'); +Tool _gnuArchiver(String prefix) { + final gcc = _gcc(prefix); return Tool( - name: arName, + name: gnuArchiver.name, defaultResolver: RelativeToolResolver( - toolName: arName, + toolName: gnuArchiver.name, wrappedResolver: gcc.defaultResolver!, - relativePath: Uri.file(arName), + relativePath: Uri.file('$prefix-gcc-ar'), ), ); } -/// Finds the `ld` belonging to that GCC. -Tool _gccLinker(Tool gcc) { - final arName = gcc.name.replaceAll('-gcc', '-gcc-ld'); +Tool _gnuLinker(String prefix) { + final gcc = _gcc(prefix); return Tool( - name: arName, + name: gnuLinker.name, defaultResolver: RelativeToolResolver( - toolName: arName, + toolName: gnuLinker.name, wrappedResolver: gcc.defaultResolver!, - relativePath: Uri.file(arName), + relativePath: Uri.file('$prefix-ld'), ), ); } diff --git a/pkgs/c_compiler/lib/src/tool/tool.dart b/pkgs/c_compiler/lib/src/tool/tool.dart index 7083cb5f7e..ff42f28559 100644 --- a/pkgs/c_compiler/lib/src/tool/tool.dart +++ b/pkgs/c_compiler/lib/src/tool/tool.dart @@ -19,4 +19,7 @@ class Tool { @override int get hashCode => Object.hash(name, 133709); + + @override + String toString() => 'Tool($name)'; } diff --git a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart index aaf003c53d..cf98168fc7 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart @@ -29,7 +29,13 @@ class PathToolResolver extends ToolResolver { /// The [Tool.name] of the [Tool] to find on the `PATH`. final String toolName; - PathToolResolver({required this.toolName}); + final String executableName; + + PathToolResolver({ + required this.toolName, + String? executableName, + }) : executableName = executableName ?? + Target.current.os.executableFileName(toolName.toLowerCase()); @override Future> resolve({Logger? logger}) async { @@ -46,9 +52,6 @@ class PathToolResolver extends ToolResolver { return toolInstances; } - String get executableName => - Target.current.os.executableFileName(toolName.toLowerCase()); - static String get which => Platform.isWindows ? 'where' : 'which'; Future runWhich() async { diff --git a/pkgs/c_compiler/test/native_toolchain/gcc_test.dart b/pkgs/c_compiler/test/native_toolchain/gcc_test.dart index a70ee356c6..3604b17505 100644 --- a/pkgs/c_compiler/test/native_toolchain/gcc_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/gcc_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:c_compiler/src/native_toolchain/gcc.dart'; +import 'package:c_compiler/src/tool/tool.dart'; import 'package:c_compiler/src/tool/tool_requirement.dart'; import 'package:c_compiler/src/tool/tool_resolver.dart'; import 'package:test/test.dart'; @@ -10,31 +11,42 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() { - test('gcc cross compilation smoke test', () async { - final tools = [ - aarch64LinuxGnuGcc, - aarch64LinuxGnuGccAr, - aarch64LinuxGnuGccLd, - armLinuxGnueabihfGcc, - armLinuxGnueabihfGccAr, - armLinuxGnueabihfGccLd, - i686LinuxGnuGcc, - i686LinuxGnuGccAr, - i686LinuxGnuGccLd, - ]; - - final resolver = ToolResolvers([ - for (final tool in tools) tool.defaultResolver!, - ]); - - final resolved = await resolver.resolve(logger: logger); - expect(resolved.isNotEmpty, true); - - final requirement = RequireAll([ - for (final tool in tools) ToolRequirement(tool), - ]); - - final satisfied = requirement.satisfy(resolved); - expect(satisfied?.length, tools.length); - }); + void testToolSet(String name, List tools) { + test('gcc cross compilation $name smoke test', () async { + final resolver = ToolResolvers([ + for (final tool in tools) tool.defaultResolver!, + ]); + + final resolved = await resolver.resolve(logger: logger); + printOnFailure(resolved.toString()); + expect(resolved.isNotEmpty, true); + + final requirement = RequireAll([ + for (final tool in tools) ToolRequirement(tool), + ]); + + final satisfied = requirement.satisfy(resolved); + printOnFailure(tools.toString()); + printOnFailure(satisfied.toString()); + expect(satisfied?.length, tools.length); + }); + } + + testToolSet('aarch64LinuxGnuGcc', [ + aarch64LinuxGnuGcc, + aarch64LinuxGnuGccAr, + aarch64LinuxGnuLd, + ]); + + testToolSet('armLinuxGnueabihfGcc', [ + armLinuxGnueabihfGcc, + armLinuxGnueabihfGccAr, + armLinuxGnueabihfLd, + ]); + + testToolSet('i686LinuxGnuGcc', [ + i686LinuxGnuGcc, + i686LinuxGnuGccAr, + i686LinuxGnuLd, + ]); } diff --git a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart index 12b592790a..26b993b382 100644 --- a/pkgs/c_compiler/test/native_toolchain/ndk_test.dart +++ b/pkgs/c_compiler/test/native_toolchain/ndk_test.dart @@ -13,8 +13,8 @@ void main() { final requirement = RequireAll([ ToolRequirement(androidNdk), ToolRequirement(androidNdkClang), - ToolRequirement(androidNdkClangAr), - ToolRequirement(androidNdkClangLd), + ToolRequirement(androidNdkLlvmAr), + ToolRequirement(androidNdkLld), ]); final resolved = await androidNdk.defaultResolver!.resolve(logger: logger); final satisfied = requirement.satisfy(resolved); From 157236ddeee80474a66843987d9630d8d4535cbf Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 14:39:37 +0000 Subject: [PATCH 15/27] Recognize tools from just Uri --- .../lib/src/native_toolchain/recognizer.dart | 113 ++++++++++++++++++ .../native_toolchain/recognizer_test.dart | 93 ++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 pkgs/c_compiler/lib/src/native_toolchain/recognizer.dart create mode 100644 pkgs/c_compiler/test/native_toolchain/recognizer_test.dart diff --git a/pkgs/c_compiler/lib/src/native_toolchain/recognizer.dart b/pkgs/c_compiler/lib/src/native_toolchain/recognizer.dart new file mode 100644 index 0000000000..1c8dda974e --- /dev/null +++ b/pkgs/c_compiler/lib/src/native_toolchain/recognizer.dart @@ -0,0 +1,113 @@ +// 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 '../tool/tool.dart'; +import '../tool/tool_instance.dart'; +import '../tool/tool_resolver.dart'; +import 'clang.dart'; +import 'gcc.dart'; + +class CompilerRecognizer implements ToolResolver { + final Uri uri; + + CompilerRecognizer(this.uri); + + @override + Future> resolve({Logger? logger}) async { + logger?.finer('Trying to recognize $uri.'); + final filePath = uri.toFilePath(); + Tool? tool; + if (filePath.contains('-gcc')) { + tool = gcc; + } else if (filePath.contains('clang')) { + tool = clang; + } + + if (tool != null) { + logger?.fine('Tool instance $uri is likely $tool.'); + final toolInstance = ToolInstance(tool: tool, uri: uri); + return [ + await CliVersionResolver.lookupVersion( + toolInstance, + logger: logger, + ), + ]; + } + + logger?.severe('Tool instance $uri not recognized.'); + return []; + } +} + +class LinkerRecognizer implements ToolResolver { + final Uri uri; + + LinkerRecognizer(this.uri); + + @override + Future> resolve({Logger? logger}) async { + logger?.finer('Trying to recognize $uri.'); + final filePath = uri.toFilePath(); + Tool? tool; + if (filePath.contains('-ld')) { + tool = gnuLinker; + } else if (filePath.contains('ld.lld')) { + tool = lld; + } + + if (tool != null) { + logger?.fine('Tool instance $uri is likely $tool.'); + final toolInstance = ToolInstance(tool: tool, uri: uri); + if (tool == lld) { + return [ + await CliVersionResolver.lookupVersion( + toolInstance, + logger: logger, + ), + ]; + } + return [toolInstance]; + } + + logger?.severe('Tool instance $uri not recognized.'); + return []; + } +} + +class ArchiverRecognizer implements ToolResolver { + final Uri uri; + + ArchiverRecognizer(this.uri); + + @override + Future> resolve({Logger? logger}) async { + logger?.finer('Trying to recognize $uri.'); + final filePath = uri.toFilePath(); + Tool? tool; + if (filePath.contains('-gcc-ar')) { + tool = gnuArchiver; + } else if (filePath.contains('llvm-ar')) { + tool = llvmAr; + } + + if (tool != null) { + logger?.fine('Tool instance $uri is likely $tool.'); + final toolInstance = ToolInstance(tool: tool, uri: uri); + if (tool == llvmAr) { + return [ + await CliVersionResolver.lookupVersion( + toolInstance, + logger: logger, + ), + ]; + } + return [toolInstance]; + } + + logger?.severe('Tool instance $uri not recognized.'); + return []; + } +} diff --git a/pkgs/c_compiler/test/native_toolchain/recognizer_test.dart b/pkgs/c_compiler/test/native_toolchain/recognizer_test.dart new file mode 100644 index 0000000000..4531880704 --- /dev/null +++ b/pkgs/c_compiler/test/native_toolchain/recognizer_test.dart @@ -0,0 +1,93 @@ +// 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:c_compiler/src/native_toolchain/android_ndk.dart'; +import 'package:c_compiler/src/native_toolchain/clang.dart'; +import 'package:c_compiler/src/native_toolchain/gcc.dart'; +import 'package:c_compiler/src/native_toolchain/recognizer.dart'; +import 'package:c_compiler/src/tool/tool.dart'; +import 'package:c_compiler/src/tool/tool_instance.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + void recognizeCompilerTest(String name, Tool tool) { + test('recognize compiler $name', () async { + final toolInstance = (await tool.defaultResolver!.resolve(logger: logger)) + .where((element) => element.tool == tool) + .first; + final recognizer = CompilerRecognizer(toolInstance.uri); + final toolInstanceAgain = + (await recognizer.resolve(logger: logger)).first; + expect(toolInstanceAgain, toolInstance); + }); + } + + recognizeCompilerTest('aarch64LinuxGnuGcc', aarch64LinuxGnuGcc); + recognizeCompilerTest('androidNdkClang', androidNdkClang); + recognizeCompilerTest('armLinuxGnueabihfGcc', armLinuxGnueabihfGcc); + recognizeCompilerTest('clang', clang); + recognizeCompilerTest('i686LinuxGnuGcc', i686LinuxGnuGcc); + + test('compiler does not exist', () async { + await inTempDir((tempUri) async { + final recognizer = CompilerRecognizer(tempUri.resolve('asdf')); + final result = await recognizer.resolve(logger: logger); + expect(result, []); + }); + }); + + void recognizeLinkerTest(String name, Tool tool) { + test('recognize compiler $name', () async { + final toolInstance = (await tool.defaultResolver!.resolve(logger: logger)) + .where((element) => element.tool == tool) + .first; + final recognizer = LinkerRecognizer(toolInstance.uri); + final toolInstanceAgain = + (await recognizer.resolve(logger: logger)).first; + expect(toolInstanceAgain, toolInstance); + }); + } + + recognizeLinkerTest('aarch64LinuxGnuLd', aarch64LinuxGnuLd); + recognizeLinkerTest('androidNdkLld', androidNdkLld); + recognizeLinkerTest('armLinuxGnueabihfLd', armLinuxGnueabihfLd); + recognizeLinkerTest('i686LinuxGnuLd', i686LinuxGnuLd); + recognizeLinkerTest('lld', lld); + + test('linker does not exist', () async { + await inTempDir((tempUri) async { + final recognizer = LinkerRecognizer(tempUri.resolve('asdf')); + final result = await recognizer.resolve(logger: logger); + expect(result, []); + }); + }); + + void recognizeArchiverTest(String name, Tool tool) { + test('recognize compiler $name', () async { + final toolInstance = (await tool.defaultResolver!.resolve(logger: logger)) + .where((element) => element.tool == tool) + .first; + final recognizer = ArchiverRecognizer(toolInstance.uri); + final toolInstanceAgain = + (await recognizer.resolve(logger: logger)).first; + expect(toolInstanceAgain, toolInstance); + }); + } + + recognizeArchiverTest('aarch64LinuxGnuGccAr', aarch64LinuxGnuGccAr); + recognizeArchiverTest('androidNdkLlvmAr', androidNdkLlvmAr); + recognizeArchiverTest('armLinuxGnueabihfGccAr', armLinuxGnueabihfGccAr); + recognizeArchiverTest('i686LinuxGnuGccAr', i686LinuxGnuGccAr); + recognizeArchiverTest('llvmAr', llvmAr); + + test('archiver does not exist', () async { + await inTempDir((tempUri) async { + final recognizer = ArchiverRecognizer(tempUri.resolve('asdf')); + final result = await recognizer.resolve(logger: logger); + expect(result, []); + }); + }); +} From 9a9b3e7935c07f5a8653201be7d0a5414d954bd1 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 15:07:23 +0000 Subject: [PATCH 16/27] Use compiler recognizer --- .../lib/src/cbuilder/compiler_resolver.dart | 150 ++++++++++-------- .../lib/src/cbuilder/run_cbuilder.dart | 33 +--- .../lib/src/model/build_config.dart | 9 ++ 3 files changed, 91 insertions(+), 101 deletions(-) diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index 7fe840cc41..9d7b03becb 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -10,12 +10,12 @@ import 'package:native_assets_cli/native_assets_cli.dart'; import '../native_toolchain/android_ndk.dart'; import '../native_toolchain/clang.dart'; import '../native_toolchain/gcc.dart'; +import '../native_toolchain/recognizer.dart'; import '../tool/tool.dart'; import '../tool/tool_error.dart'; import '../tool/tool_instance.dart'; -import '../tool/tool_resolver.dart'; -class CompilerResolver implements ToolResolver { +class CompilerResolver { final BuildConfig buildConfig; final Logger? logger; @@ -24,62 +24,48 @@ class CompilerResolver implements ToolResolver { required this.logger, }); - @override - Future> resolve({Logger? logger}) async { - final tool = selectCompiler(); + Future resolveCompiler() async { + final tool = _selectCompiler(); // First, check if the launcher provided a direct path to the compiler. var result = await _tryLoadCompilerFromConfig( - tool, BuildConfig.ccConfigKey, (buildConfig) => buildConfig.cc); - - // Then, check if this package itself provided metadata. - final depsToolKey = [ - BuildConfig.dependencyMetadataConfigKey, - ' c_compiler', - tool.name - ].join('.'); - result ??= await _tryLoadCompilerFromConfig( tool, - depsToolKey, - (buildConfig) => - buildConfig.config.optionalPath(depsToolKey, mustExist: true), + BuildConfig.ccConfigKey, + (buildConfig) => buildConfig.cc, ); - // Lastly, try to detect on the host machine. - result ??= await _tryLoadCompilerFromNativeToolchain( - tool, - ); + // Then, try to detect on the host machine. + result ??= await _tryLoadToolFromNativeToolchain(tool); if (result != null) { - return [result]; + return result; } const errorMessage = 'No C compiler found.'; logger?.severe(errorMessage); - throw Exception(errorMessage); + throw ToolError(errorMessage); } /// Select the right compiler for cross compiling to the specified target. - Tool selectCompiler() { + Tool _selectCompiler() { final host = Target.current; final target = buildConfig.target; - switch (target) { - case Target.linuxArm: - return armLinuxGnueabihfGcc; - case Target.linuxArm64: - return aarch64LinuxGnuGcc; - case Target.linuxIA32: - return i686LinuxGnuGcc; - case Target.linuxX64: - return clang; - case Target.androidArm: - case Target.androidArm64: - case Target.androidIA32: - case Target.androidX64: - return androidNdkClang; + + if (target == host) return clang; + if (target.os == OS.android) return androidNdkClang; + if (host.os == OS.linux) { + switch (target) { + case Target.linuxArm: + return armLinuxGnueabihfGcc; + case Target.linuxArm64: + return aarch64LinuxGnuGcc; + case Target.linuxIA32: + return i686LinuxGnuGcc; + } } + throw ToolError( - "No tool configured on host '$host' for target: '$target'."); + "No tools configured on host '$host' with target '$target'."); } Future _tryLoadCompilerFromConfig( @@ -89,7 +75,8 @@ class CompilerResolver implements ToolResolver { if (await File.fromUri(configCcUri).exists()) { logger?.finer('Using compiler ${configCcUri.path} ' 'from config[${BuildConfig.ccConfigKey}].'); - return ToolInstance(tool: tool, uri: configCcUri); + return (await CompilerRecognizer(configCcUri).resolve(logger: logger)) + .first; } else { logger?.warning('Compiler ${configCcUri.path} from ' 'config[${BuildConfig.ccConfigKey}] does not ' @@ -99,8 +86,7 @@ class CompilerResolver implements ToolResolver { return null; } - /// If a build is invoked - Future _tryLoadCompilerFromNativeToolchain(Tool tool) async { + Future _tryLoadToolFromNativeToolchain(Tool tool) async { final resolved = (await tool.defaultResolver!.resolve(logger: logger)) .where((i) => i.tool == tool) .toList() @@ -108,41 +94,65 @@ class CompilerResolver implements ToolResolver { return resolved.isEmpty ? null : resolved.first; } - Future resolveLinker( - Uri compiler, - ) async { - if (compiler.pathSegments.last == 'clang') { - final lld = compiler.resolve('lld'); - if (await File.fromUri(lld).exists()) { - return lld; - } + Future resolveArchiver() async { + final tool = _selectArchiver(); + + // First, check if the launcher provided a direct path to the compiler. + var result = await _tryLoadArchiverFromConfig( + tool, + BuildConfig.arConfigKey, + (buildConfig) => buildConfig.ar, + ); + + // Then, try to detect on the host machine. + result ??= await _tryLoadToolFromNativeToolchain(tool); + + if (result != null) { + return result; } - const errorMessage = 'No native linker found.'; + + const errorMessage = 'No C archiver found.'; logger?.severe(errorMessage); - throw Exception(errorMessage); + throw ToolError(errorMessage); } - Future resolveArchiver( - Uri compiler, - ) async { - final compilerExecutable = compiler.pathSegments.last; - if (compilerExecutable == 'clang') { - final ar = compiler.resolve('llvm-ar'); - if (await File.fromUri(ar).exists()) { - return ar; + /// Select the right compiler for cross compiling to the specified target. + Tool _selectArchiver() { + final host = Target.current; + final target = buildConfig.target; + + if (target == host) return llvmAr; + if (target.os == OS.android) return androidNdkLlvmAr; + if (host.os == OS.linux) { + switch (target) { + case Target.linuxArm: + return armLinuxGnueabihfGccAr; + case Target.linuxArm64: + return aarch64LinuxGnuGccAr; + case Target.linuxIA32: + return i686LinuxGnuGccAr; } - } else if (compilerExecutable.contains('-gcc')) { - final ar = - compiler.resolve(compilerExecutable.replaceAll('-gcc', '-gcc-ar')); - if (await File.fromUri(ar).exists()) { - return ar; + } + + throw ToolError( + "No tools configured on host '$host' with target '$target'."); + } + + Future _tryLoadArchiverFromConfig( + Tool tool, String configKey, Uri? Function(BuildConfig) getter) async { + final configCcUri = getter(buildConfig); + if (configCcUri != null) { + if (await File.fromUri(configCcUri).exists()) { + logger?.finer('Using archiver ${configCcUri.path} ' + 'from config[${BuildConfig.ccConfigKey}].'); + return (await ArchiverRecognizer(configCcUri).resolve(logger: logger)) + .first; } else { - print(ar); + logger?.warning('Archiver ${configCcUri.path} from ' + 'config[${BuildConfig.ccConfigKey}] does not ' + 'exist.'); } } - final errorMessage = - 'No native linker found for compiler: $compilerExecutable $compiler.'; - logger?.severe(errorMessage); - throw Exception(errorMessage); + return null; } } diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index 7ecc647634..76aeaa8306 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -36,43 +36,14 @@ class RunCBuilder { } } - Uri? _compilerCached; - Future compiler() async { - if (_compilerCached != null) { - return _compilerCached!; - } final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); - _compilerCached = (await resolver.resolve(logger: logger)).first.uri; - return _compilerCached!; + return (await resolver.resolveCompiler()).uri; } - Uri? _archiverCached; - Future archiver() async { - if (_archiverCached != null) { - return _archiverCached!; - } - final compiler_ = await compiler(); - final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); - _linkerCached = await resolver.resolveArchiver( - compiler_, - ); - return _linkerCached!; - } - - Uri? _linkerCached; - - Future linker() async { - if (_linkerCached != null) { - return _linkerCached!; - } - final compiler_ = await compiler(); final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); - _linkerCached = await resolver.resolveLinker( - compiler_, - ); - return _linkerCached!; + return (await resolver.resolveArchiver()).uri; } Future run() async { diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart index cf1427b2c0..203b380918 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -43,6 +43,10 @@ class BuildConfig { Uri? get ld => _ld; late final Uri? _ld; + /// Path to a native archiver. + Uri? get ar => _ar; + late final Uri? _ar; + /// Preferred packaging method for library. PackagingPreference get packaging => _packaging; late final PackagingPreference _packaging; @@ -66,6 +70,7 @@ class BuildConfig { required Uri packageRoot, required Target target, IOSSdk? targetIOSSdk, + Uri? ar, Uri? cc, Uri? ld, required PackagingPreference packaging, @@ -76,6 +81,7 @@ class BuildConfig { .._packageRoot = packageRoot .._target = target .._targetIOSSdk = targetIOSSdk + .._ar = ar .._cc = cc .._ld = ld .._packaging = packaging @@ -114,6 +120,7 @@ class BuildConfig { static const outDirConfigKey = 'out_dir'; static const packageRootConfigKey = 'package_root'; + static const arConfigKey = 'ar'; static const ccConfigKey = 'cc'; static const ldConfigKey = 'ld'; static const dependencyMetadataConfigKey = 'dependency_metadata'; @@ -141,6 +148,7 @@ class BuildConfig { ), ) : null, + (config) => _ar = config.optionalPath(arConfigKey, mustExist: true), (config) => _cc = config.optionalPath(ccConfigKey, mustExist: true), (config) => _ld = config.optionalPath(ldConfigKey, mustExist: true), (config) => _packaging = PackagingPreference.fromString( @@ -187,6 +195,7 @@ class BuildConfig { packageRootConfigKey: _packageRoot.path, Target.configKey: _target.toString(), if (_targetIOSSdk != null) IOSSdk.configKey: _targetIOSSdk.toString(), + if (_ar != null) arConfigKey: _ar!.path, if (_cc != null) ccConfigKey: _cc!.path, if (_ld != null) ldConfigKey: _ld!.path, PackagingPreference.configKey: _packaging.toString(), From 5d4b2b91c0ee6b9ed4c6da6871cc3bae36d1f104 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 15:43:10 +0000 Subject: [PATCH 17/27] Test compiler resolver --- .../lib/src/cbuilder/compiler_resolver.dart | 76 +++++++++---------- .../test/cbuilder/cbuilder_test.dart | 1 - .../test/cbuilder/compiler_resolver_test.dart | 55 ++++++++++++++ 3 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index 9d7b03becb..5698e619b1 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -15,40 +15,45 @@ import '../tool/tool.dart'; import '../tool/tool_error.dart'; import '../tool/tool_instance.dart'; +// TODO(dacoharkes): This should support alternatives. +// For example use Clang or MSVC on Windows. class CompilerResolver { final BuildConfig buildConfig; final Logger? logger; + final Target host; CompilerResolver({ required this.buildConfig, required this.logger, - }); + Target? host, // Only visible for testing. + }) : host = host ?? Target.current; Future resolveCompiler() async { - final tool = _selectCompiler(); - // First, check if the launcher provided a direct path to the compiler. var result = await _tryLoadCompilerFromConfig( - tool, BuildConfig.ccConfigKey, (buildConfig) => buildConfig.cc, ); // Then, try to detect on the host machine. - result ??= await _tryLoadToolFromNativeToolchain(tool); + final tool = _selectCompiler(); + if (tool != null) { + result ??= await _tryLoadToolFromNativeToolchain(tool); + } if (result != null) { return result; } - const errorMessage = 'No C compiler found.'; + final target = buildConfig.target; + final errorMessage = + "No tools configured on host '$host' with target '$target'."; logger?.severe(errorMessage); throw ToolError(errorMessage); } /// Select the right compiler for cross compiling to the specified target. - Tool _selectCompiler() { - final host = Target.current; + Tool? _selectCompiler() { final target = buildConfig.target; if (target == host) return clang; @@ -64,24 +69,18 @@ class CompilerResolver { } } - throw ToolError( - "No tools configured on host '$host' with target '$target'."); + return null; } Future _tryLoadCompilerFromConfig( - Tool tool, String configKey, Uri? Function(BuildConfig) getter) async { + String configKey, Uri? Function(BuildConfig) getter) async { final configCcUri = getter(buildConfig); if (configCcUri != null) { - if (await File.fromUri(configCcUri).exists()) { - logger?.finer('Using compiler ${configCcUri.path} ' - 'from config[${BuildConfig.ccConfigKey}].'); - return (await CompilerRecognizer(configCcUri).resolve(logger: logger)) - .first; - } else { - logger?.warning('Compiler ${configCcUri.path} from ' - 'config[${BuildConfig.ccConfigKey}] does not ' - 'exist.'); - } + assert(await File.fromUri(configCcUri).exists()); + logger?.finer('Using compiler ${configCcUri.path} ' + 'from config[${BuildConfig.ccConfigKey}].'); + return (await CompilerRecognizer(configCcUri).resolve(logger: logger)) + .first; } return null; } @@ -95,30 +94,31 @@ class CompilerResolver { } Future resolveArchiver() async { - final tool = _selectArchiver(); - // First, check if the launcher provided a direct path to the compiler. var result = await _tryLoadArchiverFromConfig( - tool, BuildConfig.arConfigKey, (buildConfig) => buildConfig.ar, ); // Then, try to detect on the host machine. - result ??= await _tryLoadToolFromNativeToolchain(tool); + final tool = _selectArchiver(); + if (tool != null) { + result ??= await _tryLoadToolFromNativeToolchain(tool); + } if (result != null) { return result; } - const errorMessage = 'No C archiver found.'; + final target = buildConfig.target; + final errorMessage = + "No tools configured on host '$host' with target '$target'."; logger?.severe(errorMessage); throw ToolError(errorMessage); } /// Select the right compiler for cross compiling to the specified target. - Tool _selectArchiver() { - final host = Target.current; + Tool? _selectArchiver() { final target = buildConfig.target; if (target == host) return llvmAr; @@ -134,24 +134,18 @@ class CompilerResolver { } } - throw ToolError( - "No tools configured on host '$host' with target '$target'."); + return null; } Future _tryLoadArchiverFromConfig( - Tool tool, String configKey, Uri? Function(BuildConfig) getter) async { + String configKey, Uri? Function(BuildConfig) getter) async { final configCcUri = getter(buildConfig); if (configCcUri != null) { - if (await File.fromUri(configCcUri).exists()) { - logger?.finer('Using archiver ${configCcUri.path} ' - 'from config[${BuildConfig.ccConfigKey}].'); - return (await ArchiverRecognizer(configCcUri).resolve(logger: logger)) - .first; - } else { - logger?.warning('Archiver ${configCcUri.path} from ' - 'config[${BuildConfig.ccConfigKey}] does not ' - 'exist.'); - } + assert(await File.fromUri(configCcUri).exists()); + logger?.finer('Using archiver ${configCcUri.path} ' + 'from config[${BuildConfig.ccConfigKey}].'); + return (await ArchiverRecognizer(configCcUri).resolve(logger: logger)) + .first; } return null; } diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index b4e61a1e11..f32e7ebdb0 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -71,7 +71,6 @@ void main() { await cbuilder.run( buildConfig: buildConfig, buildOutput: buildOutput, - logger: logger, ); final dylibUri = tempUri.resolve(Target.current.os.dylibFileName(name)); diff --git a/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart b/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart new file mode 100644 index 0000000000..117ac2268a --- /dev/null +++ b/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart @@ -0,0 +1,55 @@ +// 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:c_compiler/c_compiler.dart'; +import 'package:c_compiler/src/cbuilder/compiler_resolver.dart'; +import 'package:c_compiler/src/tool/tool_error.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + test('Config provided compiler', () async { + await inTempDir((tempUri) async { + final ar = + (await llvmAr.defaultResolver!.resolve(logger: logger)).first.uri; + final cc = + (await clang.defaultResolver!.resolve(logger: logger)).first.uri; + final ld = (await lld.defaultResolver!.resolve(logger: logger)).first.uri; + final buildConfig = BuildConfig( + outDir: tempUri, + packageRoot: tempUri, + target: Target.current, + packaging: PackagingPreference.dynamic, + ar: ar, + cc: cc, + ld: ld, + ); + final resolver = + CompilerResolver(buildConfig: buildConfig, logger: logger); + final compiler = await resolver.resolveCompiler(); + final archiver = await resolver.resolveArchiver(); + expect(compiler.uri, buildConfig.cc); + expect(archiver.uri, buildConfig.ar); + }); + }); + test('No compiler found', () async { + await inTempDir((tempUri) async { + final buildConfig = BuildConfig( + outDir: tempUri, + packageRoot: tempUri, + target: Target.windowsX64, + packaging: PackagingPreference.dynamic, + ); + final resolver = CompilerResolver( + buildConfig: buildConfig, + logger: logger, + host: Target.androidArm64, // This is never a host. + ); + expect(resolver.resolveCompiler, throwsA(isA())); + expect(resolver.resolveArchiver, throwsA(isA())); + }); + }); +} From 13b5cbaa1f5b189ee9e10b5f62a2914a0dcc4717 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 15:52:54 +0000 Subject: [PATCH 18/27] Cover archiver in BuildConfig --- .../lib/src/cbuilder/run_cbuilder.dart | 12 ++-- .../lib/src/model/build_config.dart | 2 + .../test/model/build_config_test.dart | 56 ++++++++++++++----- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index 76aeaa8306..ec9318ec66 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -28,13 +28,11 @@ class RunCBuilder { this.dynamicLibrary, this.staticLibrary, }) : outDir = buildConfig.outDir, - target = buildConfig.target { - if ([executable, dynamicLibrary, staticLibrary].whereType().length != - 1) { - throw ArgumentError( - 'Provide one of executable, dynamicLibrary, or staticLibrary.'); - } - } + target = buildConfig.target, + assert([executable, dynamicLibrary, staticLibrary] + .whereType() + .length == + 1); Future compiler() async { final resolver = CompilerResolver(buildConfig: buildConfig, logger: logger); diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart index 203b380918..a4088e5bd8 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -217,6 +217,7 @@ class BuildConfig { if (other._packageRoot != _packageRoot) return false; if (other._target != _target) return false; if (other._targetIOSSdk != _targetIOSSdk) return false; + if (other._ar != _ar) return false; if (other._cc != _cc) return false; if (other._ld != _ld) return false; if (other._packaging != _packaging) return false; @@ -231,6 +232,7 @@ class BuildConfig { _packageRoot, _target, _targetIOSSdk, + _ar, _cc, _ld, _packaging, diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart index e0fda3609e..d9ab529e27 100644 --- a/pkgs/native_assets_cli/test/model/build_config_test.dart +++ b/pkgs/native_assets_cli/test/model/build_config_test.dart @@ -12,6 +12,7 @@ void main() async { late Uri tempUri; late Uri fakeClang; late Uri fakeLd; + late Uri fakeAr; setUp(() async { tempUri = (await Directory.systemTemp.createTemp()).uri; @@ -19,6 +20,8 @@ void main() async { await File.fromUri(fakeClang).create(); fakeLd = tempUri.resolve('fake_ld'); await File.fromUri(fakeLd).create(); + fakeAr = tempUri.resolve('fake_ar'); + await File.fromUri(fakeAr).create(); }); tearDown(() async { @@ -33,6 +36,7 @@ void main() async { targetIOSSdk: IOSSdk.iPhoneOs, cc: fakeClang, ld: fakeLd, + ar: fakeAr, packaging: PackagingPreference.preferStatic, ); @@ -51,12 +55,13 @@ void main() async { expect(config1.targetIOSSdk != config2.targetIOSSdk, true); expect(config1.cc != config2.cc, true); expect(config1.ld != config2.ld, true); + expect(config1.ar != config2.ar, true); expect(config1.packaging, config2.packaging); expect(config1.dependencyMetadata, config2.dependencyMetadata); }); test('BuildConfig fromConfig', () { - final nativeAssetsCliConfig2 = BuildConfig( + final buildConfig2 = BuildConfig( outDir: tempUri.resolve('out2/'), packageRoot: tempUri.resolve('packageRoot/'), target: Target.androidArm64, @@ -71,11 +76,11 @@ void main() async { }); final fromConfig = BuildConfig.fromConfig(config); - expect(fromConfig, equals(nativeAssetsCliConfig2)); + expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig toYaml fromConfig', () { - final nativeAssetsCliConfig1 = BuildConfig( + final buildConfig1 = BuildConfig( outDir: tempUri.resolve('out1/'), packageRoot: tempUri.resolve('packageRoot/'), target: Target.iOSArm64, @@ -85,14 +90,14 @@ void main() async { packaging: PackagingPreference.preferStatic, ); - final configFile = nativeAssetsCliConfig1.toYaml(); + final configFile = buildConfig1.toYaml(); final config = Config(fileParsed: configFile); final fromConfig = BuildConfig.fromConfig(config); - expect(fromConfig, equals(nativeAssetsCliConfig1)); + expect(fromConfig, equals(buildConfig1)); }); test('BuildConfig == dependency metadata', () { - final nativeAssetsCliConfig1 = BuildConfig( + final buildConfig1 = BuildConfig( outDir: tempUri.resolve('out1/'), packageRoot: tempUri, target: Target.androidArm64, @@ -108,7 +113,7 @@ void main() async { }, ); - final nativeAssetsCliConfig2 = BuildConfig( + final buildConfig2 = BuildConfig( outDir: tempUri.resolve('out1/'), packageRoot: tempUri, target: Target.androidArm64, @@ -123,15 +128,14 @@ void main() async { }, ); - expect(nativeAssetsCliConfig1, equals(nativeAssetsCliConfig1)); - expect(nativeAssetsCliConfig1 == nativeAssetsCliConfig2, false); - expect(nativeAssetsCliConfig1.hashCode == nativeAssetsCliConfig2.hashCode, - false); + expect(buildConfig1, equals(buildConfig1)); + expect(buildConfig1 == buildConfig2, false); + expect(buildConfig1.hashCode == buildConfig2.hashCode, false); }); test('BuildConfig toYaml fromYaml', () { final outDir = tempUri.resolve('out1/'); - final nativeAssetsCliConfig1 = BuildConfig( + final buildConfig1 = BuildConfig( outDir: outDir, packageRoot: tempUri, target: Target.iOSArm64, @@ -150,7 +154,7 @@ void main() async { }), }, ); - final yamlString = nativeAssetsCliConfig1.toYamlString(); + final yamlString = buildConfig1.toYamlString(); final expectedYamlString = '''cc: ${fakeClang.path} dependency_metadata: bar: @@ -173,7 +177,7 @@ target_ios_sdk: iphoneos'''; fileContents: yamlString, ), ); - expect(buildConfig2, nativeAssetsCliConfig1); + expect(buildConfig2, buildConfig1); }); test('BuildConfig FormatExceptions', () { @@ -245,4 +249,28 @@ target_ios_sdk: iphoneos'''; await BuildConfig.fromArgs(['--config', configUri.toFilePath()]); expect(buildConfig2, buildConfig); }); + + test('dependency metadata via config accessor', () { + final buildConfig1 = BuildConfig( + outDir: tempUri.resolve('out1/'), + packageRoot: tempUri, + target: Target.androidArm64, + packaging: PackagingPreference.preferStatic, + dependencyMetadata: { + 'bar': Metadata({ + 'key': {'key2': 'value'}, + }), + }, + ); + // Useful for doing `path(..., exists: true)`. + expect( + buildConfig1.config.string([ + BuildConfig.dependencyMetadataConfigKey, + 'bar', + 'key', + 'key2' + ].join('.')), + 'value', + ); + }); } From 93ea2d683da09eeb0b528ef05e28ad11dcab31dd Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 13 Apr 2023 16:34:03 +0000 Subject: [PATCH 19/27] Cleanup Process.run invocations --- .../lib/src/cbuilder/run_cbuilder.dart | 16 ++- .../lib/src/tool/tool_resolver.dart | 17 +-- .../c_compiler/lib/src/utils/run_process.dart | 120 ++++++------------ .../cbuilder/cbuilder_cross_android_test.dart | 9 +- .../cbuilder_cross_linux_host_test.dart | 9 +- .../test/cbuilder/cbuilder_test.dart | 3 +- .../test/utils/run_process_test.dart | 48 +++++++ 7 files changed, 119 insertions(+), 103 deletions(-) create mode 100644 pkgs/c_compiler/test/utils/run_process_test.dart diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index ec9318ec66..68bbb762da 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -52,8 +52,8 @@ class RunCBuilder { archiver_ = await archiver(); } - await RunProcess( - executable: compiler_.path, + await runProcess( + executable: compiler_, arguments: [ if (target.os == OS.android) ...[ // TODO(dacoharkes): How to solve linking issues? @@ -78,16 +78,20 @@ class RunCBuilder { outDir.resolve('out.o').path, ], ], - ).run(logger: logger); + logger: logger, + captureOutput: false, + ); if (staticLibrary != null) { - await RunProcess( - executable: archiver_!.path, + await runProcess( + executable: archiver_!, arguments: [ 'rc', outDir.resolveUri(staticLibrary!).path, outDir.resolve('out.o').path, ], - ).run(logger: logger); + logger: logger, + captureOutput: false, + ); } } diff --git a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart index cf98168fc7..a8dd87aa2d 100644 --- a/pkgs/c_compiler/lib/src/tool/tool_resolver.dart +++ b/pkgs/c_compiler/lib/src/tool/tool_resolver.dart @@ -40,7 +40,7 @@ class PathToolResolver extends ToolResolver { @override Future> resolve({Logger? logger}) async { logger?.finer('Looking for $toolName on PATH.'); - final uri = await runWhich(); + final uri = await runWhich(logger: logger); if (uri == null) { logger?.fine('Did not find $toolName on PATH.'); return []; @@ -52,13 +52,13 @@ class PathToolResolver extends ToolResolver { return toolInstances; } - static String get which => Platform.isWindows ? 'where' : 'which'; + static Uri get which => Uri.file(Platform.isWindows ? 'where' : 'which'); - Future runWhich() async { + Future runWhich({Logger? logger}) async { final process = await runProcess( executable: which, arguments: [executableName], - throwOnFailure: false, + logger: logger, ); if (process.exitCode == 0) { final file = File(LineSplitter.split(process.stdout).first); @@ -91,7 +91,7 @@ class CliVersionResolver implements ToolResolver { }) async { if (toolInstance.version != null) return toolInstance; logger?.finer('Looking up version with --version for $toolInstance.'); - final version = await executableVersion(toolInstance.uri); + final version = await executableVersion(toolInstance.uri, logger: logger); final result = toolInstance.copyWith(version: version); logger?.fine('Found version for $result.'); return result; @@ -101,14 +101,15 @@ class CliVersionResolver implements ToolResolver { Uri executable, { String argument = '--version', int expectedExitCode = 0, + Logger? logger, }) async { - final executablePath = executable.toFilePath(); final process = await runProcess( - executable: executablePath, + executable: executable, arguments: [argument], - throwOnFailure: expectedExitCode == 0, + logger: logger, ); if (process.exitCode != expectedExitCode) { + final executablePath = executable.toFilePath(); throw ToolError( '`$executablePath $argument` returned unexpected exit code: ' '${process.exitCode}.'); diff --git a/pkgs/c_compiler/lib/src/utils/run_process.dart b/pkgs/c_compiler/lib/src/utils/run_process.dart index d30016b002..42a23bdc37 100644 --- a/pkgs/c_compiler/lib/src/utils/run_process.dart +++ b/pkgs/c_compiler/lib/src/utils/run_process.dart @@ -8,33 +8,57 @@ import 'dart:io'; import 'package:logging/logging.dart'; -/// Runs a process async and captures the exit code and standard out. +/// Runs a [Process]. +/// +/// If [logger] is provided, stream stdout and stderr to it. +/// +/// If [captureOutput], captures stdout and stderr. Future runProcess({ - required String executable, - required List arguments, + required Uri executable, + List arguments = const [], Uri? workingDirectory, Map? environment, - bool throwOnFailure = true, + bool includeParentEnvironment = true, + Logger? logger, + bool captureOutput = true, }) async { + final printWorkingDir = + workingDirectory != null && workingDirectory != Directory.current.uri; + final commandString = [ + if (printWorkingDir) '(cd ${workingDirectory.path};', + ...?environment?.entries.map((entry) => '${entry.key}=${entry.value}'), + executable, + ...arguments.map((a) => a.contains(' ') ? "'$a'" : a), + if (printWorkingDir) ')', + ].join(' '); + logger?.info('Running `$commandString`.'); + final stdoutBuffer = []; final stderrBuffer = []; final stdoutCompleter = Completer(); final stderrCompleter = Completer(); final process = await Process.start( - executable, + executable.toFilePath(), arguments, workingDirectory: workingDirectory?.toFilePath(), environment: environment, + includeParentEnvironment: includeParentEnvironment, ); process.stdout.transform(utf8.decoder).listen( - stdoutBuffer.add, - onDone: stdoutCompleter.complete, - ); + (s) { + logger?.fine(' $s'); + if (captureOutput) stdoutBuffer.add(s); + }, + onDone: stdoutCompleter.complete, + ); process.stderr.transform(utf8.decoder).listen( - stderrBuffer.add, - onDone: stderrCompleter.complete, - ); + (s) { + logger?.severe(' $s'); + if (captureOutput) stderrBuffer.add(s); + }, + onDone: stderrCompleter.complete, + ); final exitCode = await process.exitCode; await stdoutCompleter.future; @@ -43,14 +67,11 @@ Future runProcess({ final stderr = stderrBuffer.join(); final result = RunProcessResult( pid: process.pid, - command: '$executable ${arguments.join(' ')}', + command: commandString, exitCode: exitCode, stdout: stdout, stderr: stderr, ); - if (throwOnFailure && result.exitCode != 0) { - throw Exception(result); - } return result; } @@ -90,72 +111,3 @@ exitCode: $exitCode stdout: $stdout stderr: $stderr'''; } - -/// A task that when run executes a process. -class RunProcess { - final String executable; - final List arguments; - final Uri? workingDirectory; - final Map? environment; - final bool includeParentEnvironment; - final bool throwOnFailure; - - RunProcess({ - required this.executable, - this.arguments = const [], - this.workingDirectory, - this.environment, - this.includeParentEnvironment = true, - this.throwOnFailure = true, - }); - - String get commandString { - final printWorkingDir = - workingDirectory != null && workingDirectory != Directory.current.uri; - return [ - if (printWorkingDir) '(cd ${workingDirectory!.path};', - ...?environment?.entries.map((entry) => '${entry.key}=${entry.value}'), - executable, - ...arguments.map((a) => a.contains(' ') ? "'$a'" : a), - if (printWorkingDir) ')', - ].join(' '); - } - - Future run({Logger? logger}) async { - final workingDirectoryString = workingDirectory?.toFilePath(); - - final stdoutBuffer = []; - final stderrBuffer = []; - - logger?.info('Running `$commandString`.'); - final process = await Process.start( - executable, - arguments, - runInShell: true, - workingDirectory: workingDirectoryString, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - ).then((process) { - process.stdout.transform(utf8.decoder).forEach((s) { - logger?.fine(' $s'); - stdoutBuffer.add(s); - }); - process.stderr.transform(utf8.decoder).forEach((s) { - logger?.severe(' $s'); - stderrBuffer.add(s); - }); - return process; - }); - final exitCode = await process.exitCode; - if (exitCode != 0) { - final message = - 'Command `$commandString` failed with exit code $exitCode. ' - 'stderr: ${stderrBuffer.join('\n')}'; - logger?.severe(message); - if (throwOnFailure) { - throw Exception(message); - } - } - logger?.fine('Command `$commandString` done.'); - } -} diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart index ca1fdf1b06..a5ed65aacc 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; +import 'package:c_compiler/src/utils/run_process.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; @@ -57,9 +58,13 @@ void main() { final libUri = tempUri.resolve(target.os.libraryFileName(name, packaging)); - final result = await Process.run('readelf', ['-h', libUri.path]); + final result = await runProcess( + executable: Uri.file('readelf'), + arguments: ['-h', libUri.path], + logger: logger, + ); expect(result.exitCode, 0); - final machine = (result.stdout as String) + final machine = result.stdout .split('\n') .firstWhere((e) => e.contains('Machine:')); expect(machine, contains(readElfMachine[target])); diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart index cd5390aaf3..b12dffd143 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; +import 'package:c_compiler/src/utils/run_process.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; @@ -57,9 +58,13 @@ void main() { final libUri = tempUri.resolve(target.os.libraryFileName(name, packaging)); - final result = await Process.run('readelf', ['-h', libUri.path]); + final result = await runProcess( + executable: Uri.file('readelf'), + arguments: ['-h', libUri.path], + logger: logger, + ); expect(result.exitCode, 0); - final machine = (result.stdout as String) + final machine = result.stdout .split('\n') .firstWhere((e) => e.contains('Machine:')); expect(machine, contains(readElfMachine[target])); diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index f32e7ebdb0..ffabf8e6ae 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -6,6 +6,7 @@ import 'dart:ffi'; import 'dart:io'; import 'package:c_compiler/c_compiler.dart'; +import 'package:c_compiler/src/utils/run_process.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; @@ -42,7 +43,7 @@ void main() { final executableUri = tempUri.resolve(Target.current.os.executableFileName(name)); expect(await File.fromUri(executableUri).exists(), true); - final result = await Process.run(executableUri.path, []); + final result = await runProcess(executable: executableUri); expect(result.exitCode, 0); expect(result.stdout, 'Hello world.\n'); }); diff --git a/pkgs/c_compiler/test/utils/run_process_test.dart b/pkgs/c_compiler/test/utils/run_process_test.dart new file mode 100644 index 0000000000..3968b68195 --- /dev/null +++ b/pkgs/c_compiler/test/utils/run_process_test.dart @@ -0,0 +1,48 @@ +// 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 'dart:io'; + +import 'package:c_compiler/src/utils/run_process.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + final whichUri = Uri.file(Platform.isWindows ? 'where' : 'which'); + + test('log contains working dir', () async { + await inTempDir((tempUri) async { + final messages = []; + await runProcess( + executable: whichUri, + workingDirectory: tempUri, + logger: createCapturingLogger(messages), + ); + expect(messages.join('\n'), contains('cd')); + }); + }); + + test('log contains env', () async { + final messages = []; + await runProcess( + executable: whichUri, + environment: {'FOO': 'BAR'}, + logger: createCapturingLogger(messages), + ); + expect(messages.join('\n'), contains('FOO=BAR')); + }); + + test('stderr', () async { + final messages = []; + const filePath = 'a/dart/file/which/does/not/exist.dart'; + final result = await runProcess( + executable: Uri.file(Platform.resolvedExecutable), + arguments: [filePath], + logger: createCapturingLogger(messages), + ); + expect(result.stderr, contains(filePath)); + expect(result.toString(), contains(filePath)); + }); +} From 1f95e0234ea5b4a6cdc0d97d7079d9859014b102 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 14 Apr 2023 07:22:52 +0000 Subject: [PATCH 20/27] Address comments --- pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart | 9 --------- pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart | 2 +- pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart | 2 -- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart index c8b29cd267..a0c5a6e2b1 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/cbuilder.dart @@ -44,13 +44,6 @@ class CBuilder implements Builder { /// Used to output the [BuildOutput.dependencies]. final List sources; - /// Sources to build the library or executable. - /// - /// Resolved against [BuildConfig.packageRoot]. - /// - /// Used to output the [BuildOutput.dependencies]. - final List includePaths; - /// The dart files involved in building this artifact. /// /// Resolved against [BuildConfig.packageRoot]. @@ -62,14 +55,12 @@ class CBuilder implements Builder { required this.name, required this.assetName, this.sources = const [], - this.includePaths = const [], this.dartBuildFiles = const ['build.dart'], }) : _type = _CBuilderType.library; CBuilder.executable({ required this.name, this.sources = const [], - this.includePaths = const [], this.dartBuildFiles = const ['build.dart'], }) : _type = _CBuilderType.executable, assetName = null; diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index 5698e619b1..17e2e5c398 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -117,7 +117,7 @@ class CompilerResolver { throw ToolError(errorMessage); } - /// Select the right compiler for cross compiling to the specified target. + /// Select the right archiver for cross compiling to the specified target. Tool? _selectArchiver() { final target = buildConfig.target; diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart index 68bbb762da..cdef265e0e 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart @@ -12,7 +12,6 @@ class RunCBuilder { final BuildConfig buildConfig; final Logger logger; final List sources; - final List includePaths; final Uri? executable; final Uri? dynamicLibrary; final Uri? staticLibrary; @@ -23,7 +22,6 @@ class RunCBuilder { required this.buildConfig, required this.logger, this.sources = const [], - this.includePaths = const [], this.executable, this.dynamicLibrary, this.staticLibrary, From 40f9b35e57e5327ba1238bf75515a184f90d0b24 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 14 Apr 2023 08:26:23 +0000 Subject: [PATCH 21/27] Pin workflow action versions --- .github/workflows/c_compiler.yaml | 2 +- .github/workflows/native_assets_cli.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/c_compiler.yaml b/.github/workflows/c_compiler.yaml index f67b0ba9ff..1473cd3961 100644 --- a/.github/workflows/c_compiler.yaml +++ b/.github/workflows/c_compiler.yaml @@ -32,7 +32,7 @@ jobs: - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{matrix.sdk}} - - uses: nttld/setup-ndk@v1 + - uses: nttld/setup-ndk@deccd078bf3db957dbdee9862f51955b35ac81dd with: ndk-version: r25b diff --git a/.github/workflows/native_assets_cli.yaml b/.github/workflows/native_assets_cli.yaml index 4a4b7edc93..30c4d0c8bc 100644 --- a/.github/workflows/native_assets_cli.yaml +++ b/.github/workflows/native_assets_cli.yaml @@ -32,7 +32,7 @@ jobs: - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{matrix.sdk}} - - uses: nttld/setup-ndk@v1 + - uses: nttld/setup-ndk@deccd078bf3db957dbdee9862f51955b35ac81dd with: ndk-version: r25b From c68a59b32264a6a0390ad795e9fa2e71743e2570 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 14 Apr 2023 09:48:11 +0000 Subject: [PATCH 22/27] Enable running tests from different directories --- .../cbuilder/cbuilder_cross_android_test.dart | 1 - .../cbuilder_cross_linux_host_test.dart | 1 - .../test/cbuilder/cbuilder_test.dart | 2 - pkgs/c_compiler/test/helpers.dart | 41 +++++++++++++++++ .../test/example/native_add_test.dart | 3 +- pkgs/native_assets_cli/test/helpers.dart | 46 +++++++++++++++++++ 6 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 pkgs/native_assets_cli/test/helpers.dart diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart index a5ed65aacc..fafa7f2699 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -30,7 +30,6 @@ void main() { for (final target in targets) { test('Cbuilder $packaging library $target', () async { await inTempDir((tempUri) async { - final packageUri = Directory.current.uri; final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); const name = 'add'; diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart index b12dffd143..1df88dda1f 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -30,7 +30,6 @@ void main() { for (final target in targets) { test('Cbuilder $packaging library linux $target', () async { await inTempDir((tempUri) async { - final packageUri = Directory.current.uri; final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); const name = 'add'; diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index ffabf8e6ae..51442e1f06 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -15,7 +15,6 @@ import '../helpers.dart'; void main() { test('Cbuilder executable', () async { await inTempDir((tempUri) async { - final packageUri = Directory.current.uri; final helloWorldCUri = packageUri .resolve('test/cbuilder/testfiles/hello_world/src/hello_world.c'); if (!await File.fromUri(helloWorldCUri).exists()) { @@ -51,7 +50,6 @@ void main() { test('Cbuilder dylib', () async { await inTempDir((tempUri) async { - final packageUri = Directory.current.uri; final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); const name = 'add'; diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart index 717a3db547..19090511d8 100644 --- a/pkgs/c_compiler/test/helpers.dart +++ b/pkgs/c_compiler/test/helpers.dart @@ -38,3 +38,44 @@ Logger createCapturingLogger(List capturedMessages) => Logger('') printOnFailure('${record.level.name}: ${record.time}: ${record.message}'); capturedMessages.add(record.message); }); + +/// Test files are run in a variety of ways, find this package root in all. +/// +/// Test files can be run from source from any working directory. The Dart SDK +/// `tools/test.py` runs them from the root of the SDK for example. +/// +/// Test files can be run from dill from the root of package. `package:test` +/// does this. +Uri findPackageRoot(String packageName) { + final script = Platform.script; + final fileName = script.name; + if (fileName.endsWith('_test.dart')) { + // We're likely running from source. + var directory = script.resolve('.'); + while (true) { + final dirName = directory.name; + if (dirName == packageName) { + return directory; + } + final parent = directory.resolve('..'); + if (parent == directory) break; + directory = parent; + } + } else if (fileName.endsWith('.dill')) { + final cwd = Directory.current.uri; + final dirName = cwd.name; + if (dirName == packageName) { + return cwd; + } + } + throw StateError("Could not find package root for package '$packageName'. " + 'Tried finding the package root via Platform.script ' + "'${Platform.script.toFilePath()}' and Directory.current " + "'${Directory.current.uri.toFilePath()}'."); +} + +Uri packageUri = findPackageRoot('c_compiler'); + +extension on Uri { + String get name => pathSegments.where((e) => e != '').last; +} diff --git a/pkgs/native_assets_cli/test/example/native_add_test.dart b/pkgs/native_assets_cli/test/example/native_add_test.dart index d2be5a27fd..aefbbb2ba8 100644 --- a/pkgs/native_assets_cli/test/example/native_add_test.dart +++ b/pkgs/native_assets_cli/test/example/native_add_test.dart @@ -7,9 +7,10 @@ import 'dart:io'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:test/test.dart'; +import '../helpers.dart'; + void main() async { late Uri tempUri; - final packageUri = Directory.current.uri; setUp(() async { tempUri = (await Directory.systemTemp.createTemp()).uri; diff --git a/pkgs/native_assets_cli/test/helpers.dart b/pkgs/native_assets_cli/test/helpers.dart new file mode 100644 index 0000000000..6a48c103a2 --- /dev/null +++ b/pkgs/native_assets_cli/test/helpers.dart @@ -0,0 +1,46 @@ +// 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 'dart:io'; + +/// Test files are run in a variety of ways, find this package root in all. +/// +/// Test files can be run from source from any working directory. The Dart SDK +/// `tools/test.py` runs them from the root of the SDK for example. +/// +/// Test files can be run from dill from the root of package. `package:test` +/// does this. +Uri findPackageRoot(String packageName) { + final script = Platform.script; + final fileName = script.name; + if (fileName.endsWith('_test.dart')) { + // We're likely running from source. + var directory = script.resolve('.'); + while (true) { + final dirName = directory.name; + if (dirName == packageName) { + return directory; + } + final parent = directory.resolve('..'); + if (parent == directory) break; + directory = parent; + } + } else if (fileName.endsWith('.dill')) { + final cwd = Directory.current.uri; + final dirName = cwd.name; + if (dirName == packageName) { + return cwd; + } + } + throw StateError("Could not find package root for package '$packageName'. " + 'Tried finding the package root via Platform.script ' + "'${Platform.script.toFilePath()}' and Directory.current " + "'${Directory.current.uri.toFilePath()}'."); +} + +Uri packageUri = findPackageRoot('native_assets_cli'); + +extension on Uri { + String get name => pathSegments.where((e) => e != '').last; +} From 3907fc86ddb5a65b456ba346783783634bae7707 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 14 Apr 2023 09:50:59 +0000 Subject: [PATCH 23/27] Fix imports --- pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart | 2 -- .../test/cbuilder/cbuilder_cross_linux_host_test.dart | 2 -- 2 files changed, 4 deletions(-) diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart index fafa7f2699..bffe5e0636 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart @@ -2,8 +2,6 @@ // 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 'dart:io'; - import 'package:c_compiler/c_compiler.dart'; import 'package:c_compiler/src/utils/run_process.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart index 1df88dda1f..e52718006e 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart @@ -2,8 +2,6 @@ // 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 'dart:io'; - import 'package:c_compiler/c_compiler.dart'; import 'package:c_compiler/src/utils/run_process.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; From d3874e916ca25021dcb1ef1807f365f229b838e9 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 14 Apr 2023 10:21:35 +0000 Subject: [PATCH 24/27] Add reference to bug --- pkgs/c_compiler/test/helpers.dart | 2 ++ pkgs/native_assets_cli/test/helpers.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart index 19090511d8..a634852879 100644 --- a/pkgs/c_compiler/test/helpers.dart +++ b/pkgs/c_compiler/test/helpers.dart @@ -46,6 +46,8 @@ Logger createCapturingLogger(List capturedMessages) => Logger('') /// /// Test files can be run from dill from the root of package. `package:test` /// does this. +/// +/// https://github.com/dart-lang/test/issues/110 Uri findPackageRoot(String packageName) { final script = Platform.script; final fileName = script.name; diff --git a/pkgs/native_assets_cli/test/helpers.dart b/pkgs/native_assets_cli/test/helpers.dart index 6a48c103a2..afa5d5c7ee 100644 --- a/pkgs/native_assets_cli/test/helpers.dart +++ b/pkgs/native_assets_cli/test/helpers.dart @@ -11,6 +11,8 @@ import 'dart:io'; /// /// Test files can be run from dill from the root of package. `package:test` /// does this. +/// +/// https://github.com/dart-lang/test/issues/110 Uri findPackageRoot(String packageName) { final script = Platform.script; final fileName = script.name; From a25c01f4e2981c645f761dfe4b92de441008baed Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 14 Apr 2023 13:26:28 +0000 Subject: [PATCH 25/27] Address comments --- pkgs/c_compiler/lib/src/utils/run_process.dart | 14 ++++++-------- .../testfiles/hello_world/src/hello_world.c | 2 +- .../test/example/native_add_test.dart | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pkgs/c_compiler/lib/src/utils/run_process.dart b/pkgs/c_compiler/lib/src/utils/run_process.dart index 42a23bdc37..10b32e1b01 100644 --- a/pkgs/c_compiler/lib/src/utils/run_process.dart +++ b/pkgs/c_compiler/lib/src/utils/run_process.dart @@ -33,8 +33,8 @@ Future runProcess({ ].join(' '); logger?.info('Running `$commandString`.'); - final stdoutBuffer = []; - final stderrBuffer = []; + final stdoutBuffer = StringBuffer(); + final stderrBuffer = StringBuffer(); final stdoutCompleter = Completer(); final stderrCompleter = Completer(); final process = await Process.start( @@ -48,29 +48,27 @@ Future runProcess({ process.stdout.transform(utf8.decoder).listen( (s) { logger?.fine(' $s'); - if (captureOutput) stdoutBuffer.add(s); + if (captureOutput) stdoutBuffer.write(s); }, onDone: stdoutCompleter.complete, ); process.stderr.transform(utf8.decoder).listen( (s) { logger?.severe(' $s'); - if (captureOutput) stderrBuffer.add(s); + if (captureOutput) stderrBuffer.write(s); }, onDone: stderrCompleter.complete, ); final exitCode = await process.exitCode; await stdoutCompleter.future; - final stdout = stdoutBuffer.join(); await stderrCompleter.future; - final stderr = stderrBuffer.join(); final result = RunProcessResult( pid: process.pid, command: commandString, exitCode: exitCode, - stdout: stdout, - stderr: stderr, + stdout: stdoutBuffer.toString(), + stderr: stderrBuffer.toString(), ); return result; } diff --git a/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c b/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c index 6af8a8b19d..56180a119f 100644 --- a/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c +++ b/pkgs/c_compiler/test/cbuilder/testfiles/hello_world/src/hello_world.c @@ -7,4 +7,4 @@ int main() { printf("Hello world.\n"); return 0; -} \ No newline at end of file +} diff --git a/pkgs/native_assets_cli/test/example/native_add_test.dart b/pkgs/native_assets_cli/test/example/native_add_test.dart index aefbbb2ba8..d83f12e227 100644 --- a/pkgs/native_assets_cli/test/example/native_add_test.dart +++ b/pkgs/native_assets_cli/test/example/native_add_test.dart @@ -32,7 +32,7 @@ void main() async { 'build.dart', '-Dout_dir=${tempUri.path}', '-Dpackage_root=${testPackageUri.path}', - '-Dtarget=linux_x64', + '-Dtarget=${Target.current}', '-Dpackaging=dynamic', ], workingDirectory: testPackageUri.path, From 293a9a1632037fcf3c78bf851db498c05919710c Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Mon, 17 Apr 2023 08:37:38 +0000 Subject: [PATCH 26/27] At logger output that no CC / AR env variables were set --- .../lib/src/cbuilder/compiler_resolver.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart index 17e2e5c398..ddbfb1f0f1 100644 --- a/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart +++ b/pkgs/c_compiler/lib/src/cbuilder/compiler_resolver.dart @@ -82,6 +82,7 @@ class CompilerResolver { return (await CompilerRecognizer(configCcUri).resolve(logger: logger)) .first; } + logger?.finer('No compiler set in config[${BuildConfig.ccConfigKey}].'); return null; } @@ -139,14 +140,15 @@ class CompilerResolver { Future _tryLoadArchiverFromConfig( String configKey, Uri? Function(BuildConfig) getter) async { - final configCcUri = getter(buildConfig); - if (configCcUri != null) { - assert(await File.fromUri(configCcUri).exists()); - logger?.finer('Using archiver ${configCcUri.path} ' - 'from config[${BuildConfig.ccConfigKey}].'); - return (await ArchiverRecognizer(configCcUri).resolve(logger: logger)) + final configArUri = getter(buildConfig); + if (configArUri != null) { + assert(await File.fromUri(configArUri).exists()); + logger?.finer('Using archiver ${configArUri.path} ' + 'from config[${BuildConfig.arConfigKey}].'); + return (await ArchiverRecognizer(configArUri).resolve(logger: logger)) .first; } + logger?.finer('No archiver set in config[${BuildConfig.arConfigKey}].'); return null; } } From 5bc4ba034f6e4c7a83a7fb6e36fd330e8d18505b Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Mon, 17 Apr 2023 10:28:46 +0000 Subject: [PATCH 27/27] Pick up environment variables for native compilers in tests --- pkgs/c_compiler/test/cbuilder/cbuilder_test.dart | 3 +++ .../test/cbuilder/compiler_resolver_test.dart | 1 + pkgs/c_compiler/test/helpers.dart | 13 +++++++++++++ .../test/example/native_add_test.dart | 1 + pkgs/native_assets_cli/test/helpers.dart | 13 +++++++++++++ 5 files changed, 31 insertions(+) diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart index 51442e1f06..c18cea0f16 100644 --- a/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart +++ b/pkgs/c_compiler/test/cbuilder/cbuilder_test.dart @@ -27,6 +27,7 @@ void main() { packageRoot: tempUri, target: Target.current, packaging: PackagingPreference.dynamic, // Ignored by executables. + cc: cc, ); final buildOutput = BuildOutput(); final cbuilder = CBuilder.executable( @@ -59,6 +60,7 @@ void main() { packageRoot: tempUri, target: Target.current, packaging: PackagingPreference.dynamic, + cc: cc, ); final buildOutput = BuildOutput(); @@ -70,6 +72,7 @@ void main() { await cbuilder.run( buildConfig: buildConfig, buildOutput: buildOutput, + logger: logger, ); final dylibUri = tempUri.resolve(Target.current.os.dylibFileName(name)); diff --git a/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart b/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart index 117ac2268a..f1d4ef54f6 100644 --- a/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart +++ b/pkgs/c_compiler/test/cbuilder/compiler_resolver_test.dart @@ -35,6 +35,7 @@ void main() { expect(archiver.uri, buildConfig.ar); }); }); + test('No compiler found', () async { await inTempDir((tempUri) async { final buildConfig = BuildConfig( diff --git a/pkgs/c_compiler/test/helpers.dart b/pkgs/c_compiler/test/helpers.dart index a634852879..fc339ddc45 100644 --- a/pkgs/c_compiler/test/helpers.dart +++ b/pkgs/c_compiler/test/helpers.dart @@ -81,3 +81,16 @@ Uri packageUri = findPackageRoot('c_compiler'); extension on Uri { String get name => pathSegments.where((e) => e != '').last; } + +/// Archiver provided by the environment. +final Uri? ar = Platform.environment['AR']?.asFileUri(); + +/// Compiler provided by the environment. +final Uri? cc = Platform.environment['CC']?.asFileUri(); + +/// Linker provided by the environment. +final Uri? ld = Platform.environment['LD']?.asFileUri(); + +extension on String { + Uri asFileUri() => Uri.file(this); +} diff --git a/pkgs/native_assets_cli/test/example/native_add_test.dart b/pkgs/native_assets_cli/test/example/native_add_test.dart index d83f12e227..b6c771ee9d 100644 --- a/pkgs/native_assets_cli/test/example/native_add_test.dart +++ b/pkgs/native_assets_cli/test/example/native_add_test.dart @@ -34,6 +34,7 @@ void main() async { '-Dpackage_root=${testPackageUri.path}', '-Dtarget=${Target.current}', '-Dpackaging=dynamic', + if (cc != null) '-Dcc=${cc!.toFilePath()}', ], workingDirectory: testPackageUri.path, ); diff --git a/pkgs/native_assets_cli/test/helpers.dart b/pkgs/native_assets_cli/test/helpers.dart index afa5d5c7ee..5e2b000627 100644 --- a/pkgs/native_assets_cli/test/helpers.dart +++ b/pkgs/native_assets_cli/test/helpers.dart @@ -46,3 +46,16 @@ Uri packageUri = findPackageRoot('native_assets_cli'); extension on Uri { String get name => pathSegments.where((e) => e != '').last; } + +/// Archiver provided by the environment. +final Uri? ar = Platform.environment['AR']?.asFileUri(); + +/// Compiler provided by the environment. +final Uri? cc = Platform.environment['CC']?.asFileUri(); + +/// Linker provided by the environment. +final Uri? ld = Platform.environment['LD']?.asFileUri(); + +extension on String { + Uri asFileUri() => Uri.file(this); +}