1
0
Fork 0
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:
Timothy Flynn 2025-05-27 17:49:38 -04:00 committed by Tim Flynn
parent 8e792cd094
commit 3d0fdaacff
Notes: github-actions[bot] 2025-05-29 23:35:21 +00:00
4 changed files with 247 additions and 69 deletions

View file

@ -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
View 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()

View file

@ -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
View 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