blob: f1241563f25cc34c7edc18d89788a167e320f524 [file] [log] [blame]
// 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 '../native_toolchain/android_ndk.dart';
import '../native_toolchain/apple_clang.dart';
import '../native_toolchain/clang.dart';
import '../native_toolchain/gcc.dart';
import '../native_toolchain/msvc.dart';
import '../native_toolchain/recognizer.dart';
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 HookConfig hookConfig;
final Logger? logger;
final OS hostOS;
final Architecture hostArchitecture;
CompilerResolver({
required this.hookConfig,
required this.logger,
OS? hostOS, // Only visible for testing.
Architecture? hostArchitecture, // Only visible for testing.
}) : hostOS = hostOS ?? OS.current,
hostArchitecture = hostArchitecture ?? Architecture.current;
Future<ToolInstance> resolveCompiler() async {
// First, check if the launcher provided a direct path to the compiler.
var result = await _tryLoadCompilerFromConfig();
// Then, try to detect on the host machine.
final tool = _selectCompiler();
if (tool != null) {
result ??= await _tryLoadToolFromNativeToolchain(tool);
}
if (result != null) {
return result;
}
final targetOS = hookConfig.targetOS;
final targetArchitecture = hookConfig.targetArchitecture;
final errorMessage =
"No tools configured on host '${hostOS}_$hostArchitecture' with target "
"'${targetOS}_$targetArchitecture'.";
logger?.severe(errorMessage);
throw ToolError(errorMessage);
}
/// Select the right compiler for cross compiling to the specified target.
Tool? _selectCompiler() {
final targetOS = hookConfig.targetOS;
final targetArch = hookConfig.targetArchitecture;
// TODO(dacoharkes): Support falling back on other tools.
if (targetArch == hostArchitecture &&
targetOS == hostOS &&
hostOS == OS.linux) return clang;
if (targetOS == OS.macOS || targetOS == OS.iOS) return appleClang;
if (targetOS == OS.android) return androidNdkClang;
if (hostOS == OS.linux) {
switch (targetArch) {
case Architecture.arm:
return armLinuxGnueabihfGcc;
case Architecture.arm64:
return aarch64LinuxGnuGcc;
case Architecture.ia32:
return i686LinuxGnuGcc;
case Architecture.x64:
return x86_64LinuxGnuGcc;
case Architecture.riscv64:
return riscv64LinuxGnuGcc;
}
}
if (hostOS == OS.windows) {
switch (targetArch) {
case Architecture.ia32:
return clIA32;
case Architecture.arm64:
return clArm64;
case Architecture.x64:
return cl;
}
}
return null;
}
Future<ToolInstance?> _tryLoadCompilerFromConfig() async {
final configCcUri = hookConfig.cCompiler.compiler;
if (configCcUri != null) {
assert(await File.fromUri(configCcUri).exists());
logger?.finer('Using compiler ${configCcUri.toFilePath()} '
'from BuildConfig.cCompiler.cc.');
return (await CompilerRecognizer(configCcUri).resolve(logger: logger))
.first;
}
logger?.finer('No compiler set in BuildConfig.cCompiler.cc.');
return null;
}
Future<ToolInstance?> _tryLoadToolFromNativeToolchain(Tool tool) async {
final resolved = (await tool.defaultResolver!.resolve(logger: logger))
.where((i) => i.tool == tool)
.toList()
..sort();
return resolved.isEmpty ? null : resolved.first;
}
Future<ToolInstance> resolveArchiver() async {
// First, check if the launcher provided a direct path to the compiler.
var result = await _tryLoadArchiverFromConfig();
// Then, try to detect on the host machine.
final tool = _selectArchiver();
if (tool != null) {
result ??= await _tryLoadToolFromNativeToolchain(tool);
}
if (result != null) {
return result;
}
final targetOS = hookConfig.targetOS;
final targetArchitecture = hookConfig.targetArchitecture;
final errorMessage =
"No tools configured on host '${hostOS}_$hostArchitecture' with target "
"'${targetOS}_$targetArchitecture'.";
logger?.severe(errorMessage);
throw ToolError(errorMessage);
}
/// Select the right archiver for cross compiling to the specified target.
Tool? _selectArchiver() {
final targetOS = hookConfig.targetOS;
final targetArchitecture = hookConfig.targetArchitecture;
// TODO(dacoharkes): Support falling back on other tools.
if (targetArchitecture == hostArchitecture &&
targetOS == hostOS &&
hostOS == OS.linux) {
return llvmAr;
}
if (targetOS == OS.macOS || targetOS == OS.iOS) return appleAr;
if (targetOS == OS.android) return androidNdkLlvmAr;
if (hostOS == OS.linux) {
switch (targetArchitecture) {
case Architecture.arm:
return armLinuxGnueabihfGccAr;
case Architecture.arm64:
return aarch64LinuxGnuGccAr;
case Architecture.ia32:
return i686LinuxGnuGccAr;
case Architecture.x64:
return x86_64LinuxGnuGccAr;
case Architecture.riscv64:
return riscv64LinuxGnuGccAr;
}
}
if (hostOS == OS.windows) {
switch (targetArchitecture) {
case Architecture.ia32:
return libIA32;
case Architecture.arm64:
return libArm64;
case Architecture.x64:
return lib;
}
}
return null;
}
Future<ToolInstance?> _tryLoadArchiverFromConfig() async {
final configArUri = hookConfig.cCompiler.archiver;
if (configArUri != null) {
assert(await File.fromUri(configArUri).exists());
logger?.finer('Using archiver ${configArUri.toFilePath()} '
'from BuildConfig.cCompiler.ar.');
return (await ArchiverRecognizer(configArUri).resolve(logger: logger))
.first;
}
logger?.finer('No archiver set in BuildConfig.cCompiler.ar.');
return null;
}
Future<Uri?> toolchainEnvironmentScript(ToolInstance compiler) async {
final fromConfig = hookConfig.cCompiler.envScript;
if (fromConfig != null) {
logger?.fine('Using envScript from config: $fromConfig');
return fromConfig;
}
final compilerTool = compiler.tool;
assert(compilerTool == cl);
final vcvarsScript =
(await vcvars(compiler).defaultResolver!.resolve(logger: logger)).first;
return vcvarsScript.uri;
}
List<String>? toolchainEnvironmentScriptArguments() {
final fromConfig = hookConfig.cCompiler.envScriptArgs;
if (fromConfig != null) {
logger?.fine('Using envScriptArgs from config: $fromConfig');
return fromConfig;
}
// vcvars above already has x64 or x86 in the script name.
return null;
}
Future<ToolInstance> resolveLinker() async {
final targetOS = hookConfig.targetOS;
final targetArchitecture = hookConfig.targetArchitecture;
// First, check if the launcher provided a direct path to the compiler.
var result = await _tryLoadLinkerFromConfig();
// Then, try to detect on the host machine.
final tool = _selectLinker();
if (tool != null) {
result ??= await _tryLoadToolFromNativeToolchain(tool);
}
if (result != null) {
return result;
}
final errorMessage =
"No tools configured on host '${hostOS}_$hostArchitecture' with target "
"'${targetOS}_$targetArchitecture'.";
logger?.severe(errorMessage);
throw ToolError(errorMessage);
}
Future<ToolInstance?> _tryLoadLinkerFromConfig() async {
final configLdUri = hookConfig.cCompiler.linker;
if (configLdUri != null) {
assert(await File.fromUri(configLdUri).exists());
logger?.finer('Using linker ${configLdUri.toFilePath()} '
'from cCompiler.ld.');
final tools = await LinkerRecognizer(configLdUri).resolve(logger: logger);
return tools.first;
}
logger?.finer('No linker set in cCompiler.ld.');
return null;
}
/// Select the right compiler for cross compiling to the specified target.
Tool? _selectLinker() {
final targetOS = hookConfig.targetOS;
final targetArchitecture = hookConfig.targetArchitecture;
if (targetOS == OS.macOS || targetOS == OS.iOS) return appleLd;
if (targetOS == OS.android) return androidNdkLld;
if (hostOS == OS.linux) {
switch (targetArchitecture) {
case Architecture.arm:
return armLinuxGnueabihfLd;
case Architecture.arm64:
return aarch64LinuxGnuLd;
case Architecture.ia32:
return i686LinuxGnuLd;
case Architecture.x64:
return x86_64LinuxGnuLd;
case Architecture.riscv64:
return riscv64LinuxGnuLd;
}
}
if (hostOS == OS.windows) {
switch (targetArchitecture) {
case Architecture.ia32:
return linkIA32;
case Architecture.arm64:
return linkArm64;
case Architecture.x64:
return msvcLink;
}
}
return null;
}
}