mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-11-04 15:20:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			215 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/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.BSD:
 | 
						|
            gcc_candidates.append("egcc")
 | 
						|
 | 
						|
    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 in (HostSystem.Linux, HostSystem.BSD):
 | 
						|
        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 pick_swift_compilers(platform: Platform, project_root: Path) -> tuple[Path, Path, Path]:
 | 
						|
    if platform.host_system == HostSystem.Windows:
 | 
						|
        print("Swift builds are not supported on Windows", file=sys.stderr)
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    if not shutil.which("swiftly"):
 | 
						|
        print("swiftly is required to manage Swift toolchains", file=sys.stderr)
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    swiftly_toolchain_path = run_command(["swiftly", "use", "--print-location"], return_output=True, cwd=project_root)
 | 
						|
    if not swiftly_toolchain_path:
 | 
						|
        run_command(["swiftly", "install"], exit_on_failure=True, cwd=project_root)
 | 
						|
        swiftly_toolchain_path = run_command(
 | 
						|
            ["swiftly", "use", "--print-location"], return_output=True, exit_on_failure=True, cwd=project_root
 | 
						|
        )
 | 
						|
 | 
						|
    swiftly_toolchain_path = Path(swiftly_toolchain_path.strip())
 | 
						|
    swiftly_bin_dir = swiftly_toolchain_path.joinpath("usr", "bin")
 | 
						|
 | 
						|
    if not swiftly_toolchain_path.exists() or not swiftly_bin_dir.exists():
 | 
						|
        print(f"swiftly toolchain path {swiftly_toolchain_path} does not exist", file=sys.stderr)
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    return swiftly_bin_dir / "clang", swiftly_bin_dir / "clang++", swiftly_bin_dir / "swiftc"
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    platform = Platform()
 | 
						|
    (default_cc, default_cxx) = platform.default_compiler()
 | 
						|
 | 
						|
    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()
 |