mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-07 21:17:07 +09:00
Meta: Migrate find_compiler.sh logic to a python script
This will allow us to re-use this logic from within other python scripts. The find_compiler.sh script still exists, as it is used by some other bash scripts. The pick_host_compiler() function will now execute find_compiler.py and store its result in $CC and $CXX. Note that the python script supports Windows.
This commit is contained in:
parent
8e792cd094
commit
3d0fdaacff
Notes:
github-actions[bot]
2025-05-29 23:35:21 +00:00
Author: https://github.com/trflynn89
Commit: 3d0fdaacff
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4904
Reviewed-by: https://github.com/ADKaster
4 changed files with 247 additions and 69 deletions
|
@ -5,7 +5,7 @@
|
|||
Qt6 development packages, nasm, additional build tools, and a C++23 capable compiler are required.
|
||||
|
||||
We currently use gcc-14 and clang-20 in our CI pipeline. If these versions are not available on your system, see
|
||||
[`Meta/find_compiler.sh`](../Meta/find_compiler.sh) for the minimum compatible version.
|
||||
[`Meta/find_compiler.py`](../Meta/find_compiler.py) for the minimum compatible version.
|
||||
|
||||
CMake 3.25 or newer must be available in $PATH.
|
||||
|
||||
|
|
193
Meta/find_compiler.py
Executable file
193
Meta/find_compiler.py
Executable file
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from Meta.host_platform import HostSystem
|
||||
from Meta.host_platform import Platform
|
||||
from Meta.utils import run_command
|
||||
|
||||
|
||||
CLANG_MINIMUM_VERSION = 17
|
||||
GCC_MINIMUM_VERSION = 13
|
||||
XCODE_MINIMUM_VERSION = ("14.3", 14030022)
|
||||
|
||||
COMPILER_VERSION_REGEX = re.compile(r"(\d+)(\.\d+)*")
|
||||
|
||||
|
||||
def major_compiler_version_if_supported(platform: Platform, compiler: str) -> Optional[int]:
|
||||
if not shutil.which(compiler):
|
||||
return None
|
||||
|
||||
# On Windows, clang-cl is a driver that does not have the -dumpversion flag. We will use clang proper for this test.
|
||||
if platform.host_system == HostSystem.Windows:
|
||||
compiler = compiler.replace("clang-cl", "clang")
|
||||
|
||||
version = run_command([compiler, "-dumpversion"], return_output=True)
|
||||
if not version:
|
||||
return None
|
||||
|
||||
major_version = COMPILER_VERSION_REGEX.match(version)
|
||||
if not major_version:
|
||||
return None
|
||||
|
||||
major_version = int(major_version.group(1))
|
||||
|
||||
version = run_command([compiler, "--version"], return_output=True)
|
||||
if not version:
|
||||
return None
|
||||
|
||||
if platform.host_system == HostSystem.macOS and version.find("Apple clang") != -1:
|
||||
apple_definitions = run_command([compiler, "-dM", "-E", "-"], input="", return_output=True)
|
||||
if not apple_definitions:
|
||||
return None
|
||||
|
||||
apple_definitions = apple_definitions.split()
|
||||
|
||||
try:
|
||||
index = next(i for (i, v) in enumerate(apple_definitions) if "__apple_build_version__" in v)
|
||||
apple_build_version = int(apple_definitions[index + 1])
|
||||
except (IndexError, StopIteration, ValueError):
|
||||
return None
|
||||
|
||||
if apple_build_version >= XCODE_MINIMUM_VERSION[1]:
|
||||
# This inherently causes us to prefer Xcode clang over homebrew clang.
|
||||
return apple_build_version
|
||||
|
||||
elif version.find("clang") != -1:
|
||||
if major_version >= CLANG_MINIMUM_VERSION:
|
||||
return major_version
|
||||
|
||||
else:
|
||||
if major_version >= GCC_MINIMUM_VERSION:
|
||||
return major_version
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def find_newest_compiler(platform: Platform, compilers: list[str]) -> Optional[str]:
|
||||
best_compiler = None
|
||||
best_version = 0
|
||||
|
||||
for compiler in compilers:
|
||||
major_version = major_compiler_version_if_supported(platform, compiler)
|
||||
if not major_version:
|
||||
continue
|
||||
|
||||
if major_version > best_version:
|
||||
best_version = major_version
|
||||
best_compiler = compiler
|
||||
|
||||
return best_compiler
|
||||
|
||||
|
||||
def pick_host_compiler(platform: Platform, cc: str, cxx: str) -> tuple[str, str]:
|
||||
if platform.host_system == HostSystem.Windows and ("clang-cl" not in cc or "clang-cl" not in cxx):
|
||||
print(
|
||||
f"clang-cl {CLANG_MINIMUM_VERSION} or higher is required on Windows",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
# FIXME: Validate that the cc/cxx combination is compatible (e.g. don't allow CC=gcc and CXX=clang++)
|
||||
if major_compiler_version_if_supported(platform, cc) and major_compiler_version_if_supported(platform, cxx):
|
||||
return (cc, cxx)
|
||||
|
||||
if platform.host_system == HostSystem.Windows:
|
||||
clang_candidates = ["clang-cl"]
|
||||
gcc_candidates = []
|
||||
else:
|
||||
clang_candidates = [
|
||||
"clang",
|
||||
"clang-17",
|
||||
"clang-18",
|
||||
"clang-19",
|
||||
"clang-20",
|
||||
]
|
||||
|
||||
gcc_candidates = [
|
||||
"gcc",
|
||||
"gcc-13",
|
||||
"gcc-14",
|
||||
]
|
||||
|
||||
if platform.host_system == HostSystem.macOS:
|
||||
clang_homebrew_path = Path("/opt/homebrew/opt/llvm/bin")
|
||||
homebrew_path = Path("/opt/homebrew/bin")
|
||||
|
||||
clang_candidates.extend([str(clang_homebrew_path.joinpath(c)) for c in clang_candidates])
|
||||
clang_candidates.extend([str(homebrew_path.joinpath(c)) for c in clang_candidates])
|
||||
|
||||
gcc_candidates.extend([str(homebrew_path.joinpath(c)) for c in gcc_candidates])
|
||||
elif platform.host_system == HostSystem.Linux:
|
||||
local_path = Path("/usr/local/bin")
|
||||
|
||||
clang_candidates.extend([str(local_path.joinpath(c)) for c in clang_candidates])
|
||||
gcc_candidates.extend([str(local_path.joinpath(c)) for c in gcc_candidates])
|
||||
|
||||
clang = find_newest_compiler(platform, clang_candidates)
|
||||
if clang:
|
||||
if platform.host_system == HostSystem.Windows:
|
||||
return (clang, clang)
|
||||
return clang, clang.replace("clang", "clang++")
|
||||
|
||||
gcc = find_newest_compiler(platform, gcc_candidates)
|
||||
if gcc:
|
||||
return gcc, gcc.replace("gcc", "g++")
|
||||
|
||||
if platform.host_system == HostSystem.macOS:
|
||||
print(
|
||||
f"Please ensure that Xcode {XCODE_MINIMUM_VERSION[0]}, Homebrew clang {CLANG_MINIMUM_VERSION}, or higher is installed",
|
||||
file=sys.stderr,
|
||||
)
|
||||
elif platform.host_system == HostSystem.Windows:
|
||||
print(
|
||||
f"Please ensure that clang-cl {CLANG_MINIMUM_VERSION} or higher is installed",
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Please ensure that clang {CLANG_MINIMUM_VERSION}, gcc {GCC_MINIMUM_VERSION}, or higher is installed",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def default_host_compiler(platform: Platform) -> tuple[str, str]:
|
||||
if platform.host_system == HostSystem.Windows:
|
||||
return ("clang-cl", "clang-cl")
|
||||
return ("cc", "c++")
|
||||
|
||||
|
||||
def main():
|
||||
platform = Platform()
|
||||
(default_cc, default_cxx) = default_host_compiler(platform)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Find valid compilers")
|
||||
|
||||
parser.add_argument("--cc", required=False, default=default_cc)
|
||||
parser.add_argument("--cxx", required=False, default=default_cxx)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# The default action when this script is invoked is to provide the caller with content that may be evaluated by bash.
|
||||
(cc, cxx) = pick_host_compiler(platform, args.cc, args.cxx)
|
||||
print(f'export CC="{cc}"')
|
||||
print(f'export CXX="{cxx}"')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,78 +1,22 @@
|
|||
# shellcheck shell=bash
|
||||
|
||||
HOST_COMPILER=""
|
||||
|
||||
is_supported_compiler() {
|
||||
local COMPILER="$1"
|
||||
if [ -z "$COMPILER" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local VERSION=""
|
||||
VERSION="$($COMPILER -dumpversion 2> /dev/null)" || return 1
|
||||
local MAJOR_VERSION=""
|
||||
MAJOR_VERSION="${VERSION%%.*}"
|
||||
if $COMPILER --version 2>&1 | grep "Apple clang" >/dev/null; then
|
||||
# Apple Clang version check
|
||||
BUILD_VERSION=$(echo | $COMPILER -dM -E - | grep __apple_build_version__ | cut -d ' ' -f3)
|
||||
# Xcode 14.3, based on upstream LLVM 15
|
||||
[ "$BUILD_VERSION" -ge 14030022 ] && return 0
|
||||
elif $COMPILER --version 2>&1 | grep "clang" >/dev/null; then
|
||||
# Clang version check
|
||||
[ "$MAJOR_VERSION" -ge 17 ] && return 0
|
||||
else
|
||||
# GCC version check
|
||||
[ "$MAJOR_VERSION" -ge 13 ] && return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
find_newest_compiler() {
|
||||
local BEST_VERSION=0
|
||||
local BEST_CANDIDATE=""
|
||||
for CANDIDATE in "$@"; do
|
||||
if ! command -v "$CANDIDATE" >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
if ! $CANDIDATE -dumpversion >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
local VERSION=""
|
||||
VERSION="$($CANDIDATE -dumpversion)"
|
||||
local MAJOR_VERSION="${VERSION%%.*}"
|
||||
if [ "$MAJOR_VERSION" -gt "$BEST_VERSION" ]; then
|
||||
BEST_VERSION=$MAJOR_VERSION
|
||||
BEST_CANDIDATE="$CANDIDATE"
|
||||
fi
|
||||
done
|
||||
HOST_COMPILER=$BEST_CANDIDATE
|
||||
}
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
pick_host_compiler() {
|
||||
CC=${CC:-"cc"}
|
||||
CXX=${CXX:-"c++"}
|
||||
local output
|
||||
local status
|
||||
|
||||
if is_supported_compiler "$CC" && is_supported_compiler "$CXX"; then
|
||||
return
|
||||
output=$("${DIR}/find_compiler.py")
|
||||
status=$?
|
||||
|
||||
if [[ ${status} -ne 0 ]] ; then
|
||||
exit ${status}
|
||||
fi
|
||||
|
||||
find_newest_compiler clang clang-17 clang-18 clang-19 clang-20 /opt/homebrew/opt/llvm/bin/clang
|
||||
if is_supported_compiler "$HOST_COMPILER"; then
|
||||
export CC="${HOST_COMPILER}"
|
||||
export CXX="${HOST_COMPILER/clang/clang++}"
|
||||
return
|
||||
if [[ "${output}" != *"CC="* || "${output}" != *"CXX="* ]] ; then
|
||||
echo "Unexpected output from find_compiler.py"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find_newest_compiler egcc gcc gcc-13 gcc-14 /usr/local/bin/gcc-{13,14} /opt/homebrew/bin/gcc-{13,14}
|
||||
if is_supported_compiler "$HOST_COMPILER"; then
|
||||
export CC="${HOST_COMPILER}"
|
||||
export CXX="${HOST_COMPILER/gcc/g++}"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
die "Please make sure that Xcode 14.3, Homebrew Clang 17, or higher is installed."
|
||||
else
|
||||
die "Please make sure that GCC version 13, Clang version 17, or higher is installed."
|
||||
fi
|
||||
eval "${output}"
|
||||
}
|
||||
|
|
41
Meta/utils.py
Normal file
41
Meta/utils.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
|
||||
def run_command(
|
||||
command: list[str],
|
||||
input: Union[str, None] = None,
|
||||
return_output: bool = False,
|
||||
exit_on_failure: bool = False,
|
||||
) -> Optional[str]:
|
||||
stdin = subprocess.PIPE if type(input) is str else None
|
||||
stdout = subprocess.PIPE if return_output else None
|
||||
|
||||
try:
|
||||
# FIXME: For Windows, set the working directory so DLLs are found.
|
||||
with subprocess.Popen(command, stdin=stdin, stdout=stdout, text=True) as process:
|
||||
(output, _) = process.communicate(input=input)
|
||||
|
||||
if process.returncode != 0:
|
||||
if exit_on_failure:
|
||||
sys.exit(process.returncode)
|
||||
return None
|
||||
|
||||
except KeyboardInterrupt:
|
||||
process.send_signal(signal.SIGINT)
|
||||
process.wait()
|
||||
|
||||
sys.exit(process.returncode)
|
||||
|
||||
if return_output:
|
||||
return output.strip()
|
||||
|
||||
return None
|
Loading…
Add table
Add a link
Reference in a new issue