diff options
| author | hachem <im@hachem.wtf> | 2026-09-06 06:21:52 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-06 06:21:52 +0200 |
| commit | 392c49d61fec199605c9cff6e1e2975660e1b846 (patch) | |
| tree | ab77f5deee5aa993b9bc9224904845af3107203c /tools | |
| parent | 9a3a74070be2de9cd58f778e7a6d1df05cb60474 (diff) | |
feat: migrate over to meson
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/copy_files.py | 34 | ||||
| -rw-r--r-- | tools/fetch_llvm.py | 128 | ||||
| -rw-r--r-- | tools/find_llvm.py | 155 |
3 files changed, 317 insertions, 0 deletions
diff --git a/tools/copy_files.py b/tools/copy_files.py new file mode 100644 index 0000000..8ea67a9 --- /dev/null +++ b/tools/copy_files.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# copy_files.py STAMP DEST SRC [SRC ...] +# +# Copy each SRC next to the built executable (DEST) and touch STAMP so Meson can +# track the custom_target. Used to stage the LLDB/Python runtime DLLs beside +# hsdbg.exe on Windows, mirroring the old premake postbuild copies. + +import os +import shutil +import sys + + +def main(argv): + if len(argv) < 3: + sys.stderr.write("usage: copy_files.py STAMP DEST SRC [SRC ...]\n") + return 1 + + stamp, dest = argv[1], argv[2] + os.makedirs(dest, exist_ok=True) + + for src in argv[3:]: + if os.path.isfile(src): + shutil.copy2(src, dest) + else: + sys.stderr.write(f"warning: {src} not found, skipping\n") + + with open(stamp, "w") as handle: + handle.write("") + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tools/fetch_llvm.py b/tools/fetch_llvm.py new file mode 100644 index 0000000..8a596a8 --- /dev/null +++ b/tools/fetch_llvm.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# Download the pinned LLVM release and unpack it into ext/llvm, mirroring the old +# premake5.lua `fetch-llvm` action. Run this once before `meson setup` on a +# machine without a suitable LLVM install: +# +# python3 tools/fetch_llvm.py +# +# It is a no-op when ext/llvm already carries the LLDB headers. + +import os +import platform +import shutil +import sys +import tarfile +import urllib.request + +LLVM_VERSION = "22.1.0" + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +EXT = os.path.join(REPO_ROOT, "ext") +VENDORED = os.path.join(EXT, "llvm") +STAGING = os.path.join(EXT, "llvm-staging") + + +def is_arm(): + machine = platform.machine().lower() + return machine in ("arm64", "aarch64") + + +def release_asset(): + host = platform.system() + + if host == "Darwin": + # LLVM only publishes an Apple-silicon macOS binary + return f"LLVM-{LLVM_VERSION}-macOS-ARM64.tar.xz" if is_arm() else None + + if host == "Linux": + arch = "ARM64" if is_arm() else "X64" + return f"LLVM-{LLVM_VERSION}-Linux-{arch}.tar.xz" + + if host == "Windows": + arch = "aarch64" if is_arm() else "x86_64" + return f"clang+llvm-{LLVM_VERSION}-{arch}-pc-windows-msvc.tar.xz" + + return None + + +def download(url, dest): + print("downloading " + url) + reported = [0] + + def hook(block, block_size, total): + current = block * block_size + if total > 0 and current - reported[0] >= total / 20: + reported[0] = current + pct = min(100, int(current / total * 100)) + print(f" {pct}% ({current / 1048576:.0f} mb)") + + tmp = dest + ".part" + try: + urllib.request.urlretrieve(url, tmp, hook) + except OSError as error: + if os.path.isfile(tmp): + os.remove(tmp) + raise SystemExit(f"download failed: {error}") + os.replace(tmp, dest) + + +def extract(archive, dest): + print("extracting " + os.path.basename(archive)) + try: + with tarfile.open(archive, "r:xz") as tar: + tar.extractall(dest) + return + except (tarfile.TarError, OSError, EOFError) as error: + # some stripped-down Pythons lack the lzma module; fall back to system tar + print(f" python extraction failed ({error}), trying system tar") + + if os.system(f'tar -xf "{archive}" -C "{dest}"') != 0: + raise SystemExit( + f"could not extract {archive}; unpack it into {VENDORED} by hand" + ) + + +def main(): + if os.path.isdir(os.path.join(VENDORED, "include", "lldb")): + print(f"{VENDORED} already has an llvm with lldb headers") + return 0 + + asset = release_asset() + if asset is None: + raise SystemExit( + f"no pinned llvm release for {platform.system()} " + f"{platform.machine()}; install llvm yourself and set LLVM_PREFIX" + ) + + archive = os.path.join(EXT, asset) + if not os.path.isfile(archive): + url = ( + "https://github.com/llvm/llvm-project/releases/download/" + f"llvmorg-{LLVM_VERSION}/{asset}" + ) + download(url, archive) + + shutil.rmtree(STAGING, ignore_errors=True) + os.makedirs(STAGING) + extract(archive, STAGING) + + unpacked = [ + os.path.join(STAGING, name) + for name in os.listdir(STAGING) + if os.path.isdir(os.path.join(STAGING, name)) + ] + if not unpacked: + raise SystemExit(f"{archive} did not contain an llvm directory") + + shutil.rmtree(VENDORED, ignore_errors=True) + os.rename(unpacked[0], VENDORED) + + shutil.rmtree(STAGING, ignore_errors=True) + os.remove(archive) + + print(f"llvm {LLVM_VERSION} is ready in {VENDORED}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/find_llvm.py b/tools/find_llvm.py new file mode 100644 index 0000000..a0420bf --- /dev/null +++ b/tools/find_llvm.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# Locate an LLVM install that ships the LLDB headers, mirroring the search order +# the old premake5.lua used. Meson calls this at configure time and parses the +# KEY=value lines below; run it by hand to see what would be picked. +# +# Precedence: -Dllvm_prefix (argv[1]) -> $LLVM_PREFIX -> vendored ext/llvm -> +# `llvm-config --prefix` -> per-platform well-known locations. A candidate only +# counts if it actually contains include/lldb. +# +# Output (stdout), only when a usable prefix is found: +# prefix=<dir> +# incdir=<dir>/include +# libdir=<dir>/lib +# lib=<full path to liblldb> +# dll=<path> (zero or more, Windows runtime libraries only) +# Exit status is non-zero when nothing usable was found. + +import glob +import os +import platform +import subprocess +import sys + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +VENDORED = os.path.join(REPO_ROOT, "ext", "llvm") + + +def norm(path): + # forward slashes so the value can be baked into a C string literal without + # a backslash being read as an escape (matters for HSDBG_LLVM_PREFIX) + return os.path.abspath(path).replace("\\", "/") + + +def usable(candidate): + if not candidate: + return None + candidate = candidate.strip() + if not candidate: + return None + prefix = norm(candidate) + if os.path.isdir(os.path.join(prefix, "include", "lldb")): + return prefix + return None + + +def command_output(*args): + try: + out = subprocess.run( + args, capture_output=True, text=True, check=False + ) + except OSError: + return None + if out.returncode != 0: + return None + return out.stdout.strip() or None + + +def newest_of(pattern): + matches = sorted(glob.glob(pattern)) + for candidate in reversed(matches): + prefix = usable(candidate) + if prefix: + return prefix + return None + + +def find_prefix(override): + prefix = usable(override) or usable(os.environ.get("LLVM_PREFIX")) + if prefix: + return prefix + + prefix = usable(VENDORED) + if prefix: + return prefix + + prefix = usable(command_output("llvm-config", "--prefix")) + if prefix: + return prefix + + host = platform.system() + + if host == "Windows": + program_files = os.environ.get("ProgramFiles") + if program_files: + prefix = usable(os.path.join(program_files, "LLVM")) + if prefix: + return prefix + return newest_of("C:/Program Files/LLVM*") + + if host == "Darwin": + return usable(command_output("brew", "--prefix", "llvm")) + + if host == "Linux": + return newest_of("/usr/lib/llvm-*") or newest_of("/usr/local/llvm*") + + return None + + +def lldb_library(libdir): + host = platform.system() + if host == "Windows": + names = ["liblldb.lib"] + elif host == "Darwin": + names = ["liblldb.dylib"] + else: + names = ["liblldb.so"] + + for name in names: + candidate = os.path.join(libdir, name) + if os.path.isfile(candidate): + return norm(candidate) + + versioned = sorted(glob.glob(os.path.join(libdir, "liblldb.so.*"))) + if versioned: + return norm(versioned[-1]) + + # nothing on disk yet (e.g. a cross setup); hand back the expected path so + # the caller gets a clear link-time error rather than a silent misconfigure + return norm(os.path.join(libdir, names[0])) + + +def windows_dlls(prefix): + bindir = os.path.join(prefix, "bin") + runtimes = glob.glob(os.path.join(bindir, "liblldb.dll")) + runtimes += glob.glob(os.path.join(bindir, "python*.dll")) + if not runtimes: + runtimes = [os.path.join(bindir, "liblldb.dll")] + return [norm(path) for path in runtimes] + + +def main(): + override = sys.argv[1] if len(sys.argv) > 1 else "" + prefix = find_prefix(override) + + if not prefix: + sys.stderr.write("no llvm with lldb headers found\n") + return 1 + + incdir = norm(os.path.join(prefix, "include")) + libdir = norm(os.path.join(prefix, "lib")) + + print("prefix=" + prefix) + print("incdir=" + incdir) + print("libdir=" + libdir) + print("lib=" + lldb_library(libdir)) + + if platform.system() == "Windows": + for dll in windows_dlls(prefix): + print("dll=" + dll) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |
