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 | |
| parent | 9a3a74070be2de9cd58f778e7a6d1df05cb60474 (diff) | |
feat: migrate over to meson
| -rw-r--r-- | .gitignore | 14 | ||||
| -rw-r--r-- | README.md | 26 | ||||
| -rw-r--r-- | meson.build | 351 | ||||
| -rw-r--r-- | meson_options.txt | 6 | ||||
| -rw-r--r-- | premake5.lua | 740 | ||||
| -rw-r--r-- | src/ui/ui.cpp | 22 | ||||
| -rw-r--r-- | tools/copy_files.py | 34 | ||||
| -rw-r--r-- | tools/fetch_llvm.py | 128 | ||||
| -rw-r--r-- | tools/find_llvm.py | 155 |
9 files changed, 702 insertions, 774 deletions
@@ -1,14 +1,6 @@ -bin/ -bin-int/ - -/Makefile -*.make - -*.sln -*.vcxproj -*.vcxproj.* - -*.xcodeproj/ +# meson build directories +/build/ +/build-*/ .cache/ .DS_Store @@ -13,21 +13,33 @@ The console lets you type C or C++ directly into the debugger, which LLDB JIT-co  ## Build -hsdbg builds on macOS, Linux, and Windows. You'll need [premake5](https://premake.github.io/) and a C++23 compiler. hsdbg links against LLVM/LLDB. By default, it pulls the pinned LLVM release (22.1.0) for your platform. Alternatively, you can point it at an existing LLVM installation that ships the LLDB headers with `LLVM_PREFIX`, or have `llvm-config` available on your `PATH`. +hsdbg builds on macOS, Linux, and Windows. You'll need [meson](https://mesonbuild.com/), [ninja](https://ninja-build.org/), Python 3, and a C++23 compiler. hsdbg links against LLVM/LLDB. Running `tools/fetch_llvm.py` pulls the pinned LLVM release (22.1.0) for your platform. Alternatively, you can point it at an existing LLVM installation that ships the LLDB headers with `-Dllvm_prefix=<path>` or the `LLVM_PREFIX` environment variable, or have `llvm-config` available on your `PATH`. ```bash -$ git clone --recursive https://git.hachem.wtf/hachem/hsdbg.git +$ git clone --recursive https://git.hachem.wtf/hsdbg.git $ cd hsdbg -$ premake5 fetch-llvm -$ premake5 gmake2 -$ make config=dist +$ python3 tools/fetch_llvm.py +$ meson setup build --buildtype=release -Db_ndebug=true +$ ninja -C build -$ ./bin/dist-macosx/hsdbg ./a.out +$ ./build/hsdbg ./a.out ``` -The binary lands in `bin/dist-<system>/hsdbg`. +The binary lands in `build/hsdbg`. +Each meson build directory is one configuration; set up whichever you need: + +```bash +# debug: symbols, asserts, address + UB sanitizers +$ meson setup build-debug --buildtype=debug -Db_sanitize=address,undefined +# release: optimized, asserts still on +$ meson setup build-release --buildtype=release +# dist: optimized, asserts off (defines HSDBG_DIST), stripped +$ meson setup build-dist --buildtype=release -Db_ndebug=true --strip +``` + +Build any of them with `ninja -C <dir>`. Two helper targets are also available: `ninja -C <dir> fetch-llvm` re-downloads the pinned LLVM, and `ninja -C <dir> cppcheck` runs static analysis against the generated `compile_commands.json` (analyze a `dist` build dir so cppcheck sees the code without the debug-only asserts and sanitizers). ## License hsdbg is released under the [MIT License](LICENSE). diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5daf95b --- /dev/null +++ b/meson.build @@ -0,0 +1,351 @@ +project( + 'hsdbg', + ['c', 'cpp'], + version : '0.1.0', + meson_version : '>= 1.1.0', + default_options : [ + 'c_std=c11', + 'cpp_std=c++23', + # the vendored dependencies keep their own warnings off; hsdbg opts back + # in explicitly below so the flag set matches what premake used + 'warning_level=0', + 'default_library=static', + ], +) + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +system = host_machine.system() +is_macos = system == 'darwin' +is_linux = system == 'linux' +is_windows = system == 'windows' +msvc = cpp.get_argument_syntax() == 'msvc' + +python = find_program('python3', 'python') + +# glfw cocoa is Objective-C; only pull in that toolchain where it is needed +if is_macos + add_languages('objc', required : true, native : false) +endif + +# --- locate llvm / lldb ------------------------------------------------------ +# tools/find_llvm.py carries the search logic (env, vendored ext/llvm, +# llvm-config, platform defaults) and prints KEY=value lines we parse here. +llvm_query = run_command( + python, files('tools/find_llvm.py'), get_option('llvm_prefix'), + check : false, +) + +if llvm_query.returncode() != 0 + error( + 'no llvm with lldb headers found. install one and pass ' + + '-Dllvm_prefix=<path> (or set $LLVM_PREFIX), or run ' + + '`python3 tools/fetch_llvm.py` to download the pinned release, ' + + 'then reconfigure.' + ) +endif + +llvm_prefix = '' +lldb_incdir = '' +lldb_libdir = '' +lldb_lib = '' +lldb_dlls = [] + +foreach line : llvm_query.stdout().strip().split('\n') + if line.contains('=') + pair = line.split('=') + key = pair[0] + value = pair[1] + if key == 'prefix' + llvm_prefix = value + elif key == 'incdir' + lldb_incdir = value + elif key == 'libdir' + lldb_libdir = value + elif key == 'lib' + lldb_lib = value + elif key == 'dll' + lldb_dlls += value + endif + endif +endforeach + +message('using llvm/lldb at ' + llvm_prefix) + +# lldb lives outside the source tree, so feed its include path through the +# compiler as a system dir (quiets its headers under our strict warnings) and +# link the resolved library by full path, exactly as premake did +if msvc + lldb_inc_args = ['/I' + lldb_incdir] +else + lldb_inc_args = ['-isystem', lldb_incdir] +endif + +lldb_dep = declare_dependency( + compile_args : lldb_inc_args, + link_args : [lldb_lib], +) + +# --- vendored dependencies --------------------------------------------------- +glfw_inc = include_directories('ext/glfw/include') +glad_inc = include_directories('ext/glad/include') +imgui_inc = include_directories('ext/imgui', 'ext/imgui/backends') + +# glfw +glfw_src = files( + 'ext/glfw/src/context.c', + 'ext/glfw/src/init.c', + 'ext/glfw/src/input.c', + 'ext/glfw/src/monitor.c', + 'ext/glfw/src/platform.c', + 'ext/glfw/src/vulkan.c', + 'ext/glfw/src/window.c', + 'ext/glfw/src/null_init.c', + 'ext/glfw/src/null_joystick.c', + 'ext/glfw/src/null_monitor.c', + 'ext/glfw/src/null_window.c', +) + +if is_linux + glfw_defines = ['-D_GLFW_X11'] + glfw_src += files( + 'ext/glfw/src/x11_init.c', + 'ext/glfw/src/x11_monitor.c', + 'ext/glfw/src/x11_window.c', + 'ext/glfw/src/xkb_unicode.c', + 'ext/glfw/src/wl_init.c', + 'ext/glfw/src/wl_monitor.c', + 'ext/glfw/src/wl_window.c', + 'ext/glfw/src/posix_module.c', + 'ext/glfw/src/posix_time.c', + 'ext/glfw/src/posix_thread.c', + 'ext/glfw/src/posix_poll.c', + 'ext/glfw/src/glx_context.c', + 'ext/glfw/src/egl_context.c', + 'ext/glfw/src/osmesa_context.c', + 'ext/glfw/src/linux_joystick.c', + ) +elif is_macos + glfw_defines = ['-D_GLFW_COCOA'] + glfw_src += files( + 'ext/glfw/src/cocoa_init.m', + 'ext/glfw/src/cocoa_joystick.m', + 'ext/glfw/src/cocoa_monitor.m', + 'ext/glfw/src/macos_time.c', + 'ext/glfw/src/cocoa_window.m', + 'ext/glfw/src/nsgl_context.m', + 'ext/glfw/src/egl_context.c', + 'ext/glfw/src/osmesa_context.c', + 'ext/glfw/src/posix_module.c', + 'ext/glfw/src/posix_thread.c', + ) +elif is_windows + glfw_defines = ['-D_GLFW_WIN32', '-D_CRT_SECURE_NO_WARNINGS'] + glfw_src += files( + 'ext/glfw/src/win32_init.c', + 'ext/glfw/src/win32_joystick.c', + 'ext/glfw/src/win32_module.c', + 'ext/glfw/src/win32_monitor.c', + 'ext/glfw/src/win32_thread.c', + 'ext/glfw/src/win32_time.c', + 'ext/glfw/src/win32_window.c', + 'ext/glfw/src/wgl_context.c', + 'ext/glfw/src/egl_context.c', + 'ext/glfw/src/osmesa_context.c', + ) +else + glfw_defines = [] +endif + +glfw = static_library( + 'glfw', + glfw_src, + c_args : glfw_defines, + objc_args : is_macos ? glfw_defines : [], + include_directories : glfw_inc, + override_options : ['warning_level=0'], +) +glfw_dep = declare_dependency( + link_with : glfw, + include_directories : glfw_inc, +) + +# glad +glad = static_library( + 'glad', + files('ext/glad/src/gl.c'), + include_directories : glad_inc, + override_options : ['warning_level=0'], +) +glad_dep = declare_dependency( + link_with : glad, + include_directories : glad_inc, +) + +# imgui (+ glfw / opengl3 backends) +imgui_args = ['-DGLFW_INCLUDE_NONE'] +if is_macos + imgui_args += '-DGL_SILENCE_DEPRECATION' +elif is_windows + imgui_args += '-D_CRT_SECURE_NO_WARNINGS' +endif + +imgui = static_library( + 'imgui', + files( + 'ext/imgui/imgui.cpp', + 'ext/imgui/imgui_draw.cpp', + 'ext/imgui/imgui_tables.cpp', + 'ext/imgui/imgui_widgets.cpp', + 'ext/imgui/imgui_demo.cpp', + 'ext/imgui/misc/cpp/imgui_stdlib.cpp', + 'ext/imgui/backends/imgui_impl_glfw.cpp', + 'ext/imgui/backends/imgui_impl_opengl3.cpp', + ), + cpp_args : imgui_args, + include_directories : [imgui_inc, glfw_inc], + override_options : ['warning_level=0'], +) +imgui_dep = declare_dependency( + link_with : imgui, + include_directories : imgui_inc, +) + +# --- hsdbg ------------------------------------------------------------------- +hsdbg_src = files( + 'src/main.cpp', + 'src/ui/ui.cpp', + 'src/ui/profiler.cpp', + 'src/ui/preferences.cpp', + 'src/ui/theme.cpp', + 'src/ui/image_renderer.cpp', + 'src/ui/macro_expander.cpp', + 'src/ui/widgets.cpp', + 'src/ui/source_view.cpp', + 'src/ui/animated_image.cpp', + 'src/core/log.cpp', + 'src/core/window.cpp', + 'src/core/window_manager.cpp', + 'src/core/application.cpp', + 'src/debugger/debugger.cpp', +) + +asset_dir = meson.project_source_root().replace('\\', '/') + '/assets' + +hsdbg_defines = [ + '-DGLFW_INCLUDE_NONE', + '-D_CRT_SECURE_NO_WARNINGS', + '-DHSDBG_LLVM_PREFIX="' + llvm_prefix + '"', + '-DHSDBG_ASSET_DIR="' + asset_dir + '"', +] + +if is_windows + hsdbg_defines += '-DHSDBG_WINDOWS' +elif is_linux + hsdbg_defines += '-DHSDBG_LINUX' +elif is_macos + hsdbg_defines += '-DHSDBG_MACOS' +endif + +# dist is a stripped release build; premake keyed its HSDBG_DIST off that +# configuration, so tie it to disabled asserts (-Db_ndebug=true) +ndebug = get_option('b_ndebug') +is_dist = ndebug == 'true' or ( + ndebug == 'if-release' + and get_option('buildtype') in ['release', 'plain'] +) +if is_dist + hsdbg_defines += '-DHSDBG_DIST' +endif + +hsdbg_warnings = [] +if not msvc + hsdbg_warnings = [ + '-Wall', + '-Wextra', + '-Wpedantic', + '-Wconversion', + '-Wsign-conversion', + '-Wshadow', + ] +endif + +# match premake's frame-pointer preservation whenever a sanitizer is active +if get_option('b_sanitize') != 'none' + hsdbg_warnings += '-fno-omit-frame-pointer' +endif + +hsdbg_deps = [lldb_dep, glfw_dep, glad_dep, imgui_dep] + +if is_macos + hsdbg_deps += dependency( + 'appleframeworks', + modules : [ + 'Cocoa', + 'IOKit', + 'CoreFoundation', + 'CoreVideo', + 'OpenGL', + 'QuartzCore', + ], + ) +elif is_linux + hsdbg_deps += dependency('gl') + hsdbg_deps += dependency('threads') + hsdbg_deps += cc.find_library('dl') + hsdbg_deps += cc.find_library('m') +elif is_windows + hsdbg_deps += cc.find_library('opengl32') +endif + +# on unix the loader needs to find liblldb at runtime; on windows we stage the +# runtime dlls next to the executable instead (below) +hsdbg_rpath = is_windows ? '' : lldb_libdir + +hsdbg = executable( + 'hsdbg', + hsdbg_src, + include_directories : include_directories('src', 'ext/stb'), + cpp_args : hsdbg_defines + hsdbg_warnings, + dependencies : hsdbg_deps, + build_rpath : hsdbg_rpath, +) + +if is_windows and lldb_dlls.length() > 0 + # stage the lldb / python runtime dlls beside hsdbg.exe (top-level targets + # land in the build root), mirroring premake's postbuild copies + custom_target( + 'llvm-runtime', + command : [ + python, + files('tools/copy_files.py'), + '@OUTPUT@', + meson.project_build_root(), + ] + lldb_dlls, + output : 'llvm-runtime.stamp', + build_by_default : true, + ) +endif + +# --- developer conveniences (old premake actions) ---------------------------- +run_target('fetch-llvm', command : [python, files('tools/fetch_llvm.py')]) + +cppcheck = find_program('cppcheck', required : false) +if cppcheck.found() + # meson emits compile_commands.json into the build dir, so no bear needed; + # analyse the configured build type (use a release/dist build dir to mirror + # premake, which checked the dist configuration) + run_target( + 'cppcheck', + command : [ + cppcheck, + '--project=' + meson.project_build_root() / 'compile_commands.json', + '-i', meson.project_source_root() / 'ext', + '--suppress=*:*/ext/*', + '--inline-suppr', + '--enable=warning,style,performance,portability', + '--error-exitcode=1', + ], + ) +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..825bb4d --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,6 @@ +option( + 'llvm_prefix', + type : 'string', + value : '', + description : 'Path to an LLVM install that ships the LLDB headers. Overrides autodetection and $LLVM_PREFIX.', +) diff --git a/premake5.lua b/premake5.lua deleted file mode 100644 index 45a6fc4..0000000 --- a/premake5.lua +++ /dev/null @@ -1,740 +0,0 @@ -output_dir = "%{cfg.buildcfg}-%{cfg.system}" - -llvm_version = "22.1.0" -llvm_vendored_dir = "ext/llvm" - -host_arm = os.hostarch() == "ARM64" or os.hostarch() == "arm64" -host_architecture = host_arm and "ARM64" or "x64" - -function llvm_release_asset() - local host = os.host() - - if host == "macosx" then - return host_arm and ("LLVM-" .. llvm_version .. "-macOS-ARM64.tar.xz") or nil - elseif host == "linux" then - return "LLVM-" .. llvm_version .. (host_arm and "-Linux-ARM64" or "-Linux-X64") .. ".tar.xz" - elseif host == "windows" then - return "clang+llvm-" .. llvm_version .. - (host_arm and "-aarch64-pc-windows-msvc" or "-x86_64-pc-windows-msvc") .. ".tar.xz" - end - - return nil -end - -function llvm_prefix() - local function usable(candidate) - if candidate == nil or candidate == "" then - return nil - end - - local prefix = path.getabsolute( - (candidate:gsub("%s+$", "")):gsub("\\", "/"), - _MAIN_SCRIPT_DIR - ) - - return os.isdir(prefix .. "/include/lldb") and prefix or nil - end - - local function newest_of(pattern) - local matches = os.matchdirs(pattern) - table.sort(matches) - - for index = #matches, 1, -1 do - local prefix = usable(matches[index]) - - if prefix ~= nil then - return prefix - end - end - - return nil - end - - local prefix = usable(os.getenv("LLVM_PREFIX")) - or usable(llvm_vendored_dir) - or usable(os.outputof("llvm-config --prefix")) - - if prefix == nil and os.host() == "windows" then - prefix = usable( - os.getenv("ProgramFiles") and - os.getenv("ProgramFiles") .. "/LLVM" - ) or newest_of("C:/Program Files/LLVM*") - end - - if prefix == nil and os.host() == "macosx" then - prefix = usable(os.outputof("brew --prefix llvm")) - end - - if prefix == nil and os.host() == "linux" then - prefix = newest_of("/usr/lib/llvm-*") - or newest_of("/usr/local/llvm*") - end - - return prefix -end - -llvm_dir = llvm_prefix() - -if llvm_dir == nil - and _ACTION ~= nil - and _ACTION ~= "fetch-llvm" - and _ACTION ~= "clean" - and _ACTION ~= "check" -then - error( - "no llvm with lldb headers found. install one and set LLVM_PREFIX, " .. - "or run `premake5 fetch-llvm` to download the pinned " .. - llvm_version .. " release" - ) -end - -llvm_dir = llvm_dir or "" - -include_dir = { - glfw = "ext/glfw/include", - glad = "ext/glad/include", - imgui = "ext/imgui", - imgui_backends = "ext/imgui/backends", - lldb = llvm_dir .. "/include", -} - -library_dir = { - lldb = llvm_dir .. "/lib", -} - -function lldb_library() - if llvm_dir == "" then - return "" - end - - local names = { "liblldb.so" } - - if os.target() == "windows" then - names = { "liblldb.lib" } - elseif os.target() == "macosx" then - names = { "liblldb.dylib" } - end - - for _, name in ipairs(names) do - local candidate = library_dir.lldb .. "/" .. name - - if os.isfile(candidate) then - return candidate - end - end - - local versioned = os.matchfiles( - library_dir.lldb .. "/liblldb.so.*" - ) - - table.sort(versioned) - - if versioned[#versioned] ~= nil then - return versioned[#versioned] - end - - if os.target() ~= os.host() then - return library_dir.lldb .. "/" .. names[1] - end - - error( - "llvm at " .. llvm_dir .. - " has lldb headers but no liblldb in " .. - library_dir.lldb - ) -end - -function windows_runtime_copies() - local commands = {} - - if llvm_dir == "" then - return commands - end - - local runtimes = os.matchfiles( - llvm_dir .. "/bin/liblldb.dll" - ) - - for _, runtime in ipairs( - os.matchfiles(llvm_dir .. "/bin/python*.dll") - ) do - table.insert(runtimes, runtime) - end - - if #runtimes == 0 then - runtimes = { - llvm_dir .. "/bin/liblldb.dll", - } - end - - for _, runtime in ipairs(runtimes) do - table.insert( - commands, - '{COPYFILE} "' .. runtime .. '" "%{cfg.targetdir}"' - ) - end - - return commands -end - -function setup_target() - targetdir("bin/" .. output_dir) - objdir("bin-int/" .. output_dir .. "/%{prj.name}") - - staticruntime "On" - - filter "system:windows" - staticruntime "Off" - filter {} -end - -function setup_c_target() - setup_target() - - filter { "system:linux or system:macosx" } - buildoptions { - "-Wall", - "-Wextra", - "-Wpedantic", - "-Wconversion", - "-Wsign-conversion", - "-Wshadow", - "-Wstrict-prototypes", - "-Wmissing-prototypes", - } - filter {} - - filter "configurations:debug" - runtime "Debug" - symbols "On" - - filter { "system:linux or system:macosx" } - buildoptions { - "-g", - "-fno-omit-frame-pointer", - "-fsanitize=address,undefined", - } - - linkoptions { - "-fsanitize=address,undefined", - } - filter {} - - filter { "configurations:release or configurations:dist" } - runtime "Release" - optimize "Speed" - filter {} - - filter "configurations:dist" - symbols "Off" - filter {} -end - -function setup_cpp_target() - setup_target() - - cppdialect "C++23" - - filter { "system:linux or system:macosx" } - buildoptions { - "-Wall", - "-Wextra", - "-Wpedantic", - "-Wconversion", - "-Wsign-conversion", - "-Wshadow", - } - filter {} - - filter "configurations:debug" - runtime "Debug" - symbols "On" - - filter { "system:linux or system:macosx" } - buildoptions { - "-g", - "-fno-omit-frame-pointer", - "-fsanitize=address,undefined", - } - - linkoptions { - "-fsanitize=address,undefined", - } - filter {} - - filter { "configurations:release or configurations:dist" } - runtime "Release" - optimize "Speed" - filter {} - - filter "configurations:dist" - symbols "Off" - filter {} -end - -newaction { - trigger = "clean", - description = "Remove build output and generated project files", - - execute = function() - os.rmdir("bin") - os.rmdir("bin-int") - os.rmdir(".vs") - os.remove("Makefile") - os.remove("imgui.ini") - os.remove("compile_commands.json") - - local patterns = { - "*.make", - "*.sln", - "*.vcxproj", - "*.vcxproj.*", - } - - for _, pattern in ipairs(patterns) do - for _, file in ipairs(os.matchfiles(pattern)) do - os.remove(file) - end - end - - print("Cleaned build output and generated project files") - end -} - -newaction { - trigger = "check", - description = "Run static analysis with cppcheck", - - execute = function() - -- clean the same configuration we are about to build, otherwise its - -- objects stay up to date, the build below compiles nothing, and bear - -- captures an empty compile_commands.json - local result = os.execute("make clean config=dist") - - if result ~= true and result ~= 0 then - error("Failed to clean project") - end - - -- analyze the dist configuration: it drops the debug-only asserts and - -- sanitizers, so cppcheck sees the code as it actually ships and catches - -- issues the debug build hides (e.g. variables only read by an assert) - result = os.execute("bear -- make config=dist") - - if result ~= true and result ~= 0 then - error("Failed to generate compile_commands.json") - end - - -- only our own code is in scope: skip the vendored translation units and - -- drop any finding that lands in a third-party header our sources include - result = os.execute( - "cppcheck " .. - "--project=compile_commands.json " .. - "-i ext " .. - "--suppress='*:ext/*' " .. - "--inline-suppr " .. - "--enable=warning,style,performance,portability " .. - "--error-exitcode=1" - ) - - if result ~= true and result ~= 0 then - error("cppcheck found issues") - end - end -} - -newaction { - trigger = "fetch-llvm", - description = "Download the pinned llvm release into " .. llvm_vendored_dir, - - execute = function() - if os.isdir(llvm_vendored_dir .. "/include/lldb") then - print( - llvm_vendored_dir .. - " already has an llvm with lldb headers" - ) - return - end - - local asset = llvm_release_asset() - - if asset == nil then - error( - "no pinned llvm release for " .. - os.host() .. " " .. os.hostarch() .. - ", install llvm yourself and set LLVM_PREFIX" - ) - end - - local archive = "ext/" .. asset - local staging = "ext/llvm-staging" - - if not os.isfile(archive) then - local url = - "https://github.com/llvm/llvm-project/releases/download/llvmorg-" .. - llvm_version .. "/" .. asset - - print("downloading " .. url) - - local reported = 0 - - local result, code = http.download( - url, - archive, - { - progress = function(total, current) - if total > 0 - and current - reported >= total / 20 - then - reported = current - - printf( - " %d%% (%.0f mb)", - math.floor(current / total * 100), - current / 1048576 - ) - end - end - } - ) - - if result ~= "OK" then - os.remove(archive) - - error( - "download failed with " .. - tostring(code) .. ": " .. - tostring(result) - ) - end - end - - os.rmdir(staging) - os.mkdir(staging) - - print("extracting " .. asset) - - local extracted = os.execute( - 'tar -xf "' .. archive .. - '" -C "' .. staging .. '"' - ) - - if extracted ~= true and extracted ~= 0 then - error( - "could not extract " .. archive .. - ", the system tar may lack xz support, " .. - "unpack it into " .. llvm_vendored_dir .. - " by hand" - ) - end - - local unpacked = os.matchdirs(staging .. "/*")[1] - - if unpacked == nil then - error(archive .. " did not contain an llvm directory") - end - - os.rmdir(llvm_vendored_dir) - - if not os.rename(unpacked, llvm_vendored_dir) then - error( - "could not move " .. unpacked .. - " to " .. llvm_vendored_dir - ) - end - - os.rmdir(staging) - os.remove(archive) - - print( - "llvm " .. llvm_version .. - " is ready in " .. llvm_vendored_dir - ) - end -} - -workspace "hsdbg" - architecture(host_architecture) - startproject "hsdbg" - multiprocessorcompile "On" - - configurations { - "debug", - "release", - "dist", - } - - filter "system:windows" - defines "HSDBG_WINDOWS" - - filter "system:linux" - defines "HSDBG_LINUX" - - filter "system:macosx" - defines "HSDBG_MACOS" - - filter {} - -group "dependencies" - -project "glfw" - kind "StaticLib" - language "C" - warnings "Off" - - setup_c_target() - - files { - "ext/glfw/src/context.c", - "ext/glfw/src/init.c", - "ext/glfw/src/input.c", - "ext/glfw/src/monitor.c", - "ext/glfw/src/platform.c", - "ext/glfw/src/vulkan.c", - "ext/glfw/src/window.c", - - "ext/glfw/src/internal.h", - "ext/glfw/src/platform.h", - "ext/glfw/src/mappings.h", - - "ext/glfw/src/null_init.c", - "ext/glfw/src/null_joystick.c", - "ext/glfw/src/null_joystick.h", - "ext/glfw/src/null_monitor.c", - "ext/glfw/src/null_platform.h", - "ext/glfw/src/null_window.c", - } - - filter "system:linux" - pic "On" - defines "_GLFW_X11" - - files { - "ext/glfw/src/x11_init.c", - "ext/glfw/src/x11_monitor.c", - "ext/glfw/src/x11_platform.h", - "ext/glfw/src/x11_window.c", - "ext/glfw/src/xkb_unicode.c", - "ext/glfw/src/xkb_unicode.h", - - "ext/glfw/src/wl_init.c", - "ext/glfw/src/wl_monitor.c", - "ext/glfw/src/wl_platform.h", - "ext/glfw/src/wl_window.c", - - "ext/glfw/src/posix_module.c", - "ext/glfw/src/posix_time.c", - "ext/glfw/src/posix_time.h", - "ext/glfw/src/posix_thread.c", - "ext/glfw/src/posix_thread.h", - "ext/glfw/src/posix_poll.c", - "ext/glfw/src/posix_poll.h", - - "ext/glfw/src/glx_context.c", - "ext/glfw/src/egl_context.c", - "ext/glfw/src/osmesa_context.c", - - "ext/glfw/src/linux_joystick.c", - "ext/glfw/src/linux_joystick.h", - } - - filter "system:macosx" - pic "On" - defines "_GLFW_COCOA" - - files { - "ext/glfw/src/cocoa_init.m", - "ext/glfw/src/cocoa_joystick.h", - "ext/glfw/src/cocoa_joystick.m", - "ext/glfw/src/cocoa_monitor.m", - "ext/glfw/src/cocoa_platform.h", - "ext/glfw/src/macos_time.c", - "ext/glfw/src/macos_time.h", - "ext/glfw/src/cocoa_window.m", - - "ext/glfw/src/nsgl_context.m", - "ext/glfw/src/egl_context.c", - "ext/glfw/src/osmesa_context.c", - - "ext/glfw/src/posix_module.c", - "ext/glfw/src/posix_thread.c", - "ext/glfw/src/posix_thread.h", - } - - filter "system:windows" - defines { - "_GLFW_WIN32", - "_CRT_SECURE_NO_WARNINGS", - } - - files { - "ext/glfw/src/win32_init.c", - "ext/glfw/src/win32_joystick.c", - "ext/glfw/src/win32_joystick.h", - "ext/glfw/src/win32_module.c", - "ext/glfw/src/win32_monitor.c", - "ext/glfw/src/win32_platform.h", - "ext/glfw/src/win32_thread.c", - "ext/glfw/src/win32_thread.h", - "ext/glfw/src/win32_time.c", - "ext/glfw/src/win32_time.h", - "ext/glfw/src/win32_window.c", - "ext/glfw/src/wgl_context.c", - "ext/glfw/src/egl_context.c", - "ext/glfw/src/osmesa_context.c", - } - - filter {} - -project "glad" - kind "StaticLib" - language "C" - warnings "Off" - - setup_c_target() - - files { - "ext/glad/include/**.h", - "ext/glad/src/**.c", - } - - includedirs "%{include_dir.glad}" - - filter { "system:linux or system:macosx" } - pic "On" - filter {} - -project "imgui" - kind "StaticLib" - language "C++" - warnings "Off" - - setup_cpp_target() - - defines "GLFW_INCLUDE_NONE" - - files { - "ext/imgui/imconfig.h", - "ext/imgui/imgui.h", - "ext/imgui/imgui.cpp", - "ext/imgui/imgui_draw.cpp", - "ext/imgui/imgui_internal.h", - "ext/imgui/imgui_tables.cpp", - "ext/imgui/imgui_widgets.cpp", - "ext/imgui/imgui_demo.cpp", - "ext/imgui/imstb_rectpack.h", - "ext/imgui/imstb_textedit.h", - "ext/imgui/imstb_truetype.h", - - "ext/imgui/misc/cpp/imgui_stdlib.h", - "ext/imgui/misc/cpp/imgui_stdlib.cpp", - - "ext/imgui/backends/imgui_impl_glfw.h", - "ext/imgui/backends/imgui_impl_glfw.cpp", - "ext/imgui/backends/imgui_impl_opengl3.h", - "ext/imgui/backends/imgui_impl_opengl3.cpp", - "ext/imgui/backends/imgui_impl_opengl3_loader.h", - } - - includedirs { - "%{include_dir.imgui}", - "%{include_dir.imgui_backends}", - "%{include_dir.glfw}", - } - - filter { "system:linux or system:macosx" } - pic "On" - - filter "system:macosx" - defines "GL_SILENCE_DEPRECATION" - - filter "system:windows" - defines "_CRT_SECURE_NO_WARNINGS" - - filter {} - -group "" - -project "hsdbg" - kind "ConsoleApp" - language "C++" - - setup_cpp_target() - - defines { - "_CRT_SECURE_NO_WARNINGS", - "GLFW_INCLUDE_NONE", - } - - files { - "src/**.h", - "src/**.cpp", - } - - includedirs { - "src", - "%{include_dir.glfw}", - "%{include_dir.glad}", - "%{include_dir.imgui}", - "%{include_dir.imgui_backends}", - "%{include_dir.lldb}", - "ext/stb", - } - - links { - "glfw", - "glad", - "imgui", - } - - defines { - "HSDBG_LLVM_PREFIX=\"" .. llvm_dir .. "\"", - "HSDBG_ASSET_DIR=\"" .. _MAIN_SCRIPT_DIR .. "/assets\"", - } - - linkoptions { - lldb_library(), - } - - filter "system:windows" - systemversion "latest" - - links "opengl32" - - postbuildcommands(windows_runtime_copies()) - - filter "system:not windows" - linkoptions { - "-Wl,-rpath," .. library_dir.lldb, - } - - filter "system:linux" - links { - "GL", - "dl", - "m", - "pthread", - } - - filter "system:macosx" - defines "GL_SILENCE_DEPRECATION" - - links { - "Cocoa.framework", - "IOKit.framework", - "CoreFoundation.framework", - "CoreVideo.framework", - "OpenGL.framework", - "QuartzCore.framework", - } - - filter "configurations:debug" - defines "HSDBG_DEBUG" - - filter "configurations:release" - defines "HSDBG_RELEASE" - - filter "configurations:dist" - defines "HSDBG_DIST" - - filter {} - diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 9a851a3..78d585d 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -1409,14 +1409,9 @@ namespace Hsdbg ImGui::NewLine(); const char* current = m_preferences.theme.c_str(); - for (const auto& entry : m_themes) - { - if (entry.id == m_preferences.theme) - { - current = entry.name.c_str(); - break; - } - } + const auto named = std::ranges::find(m_themes, m_preferences.theme, &ThemeEntry::id); + if (named != m_themes.end()) + current = named->name.c_str(); if (ImGui::BeginCombo("Theme", current)) { @@ -2992,14 +2987,9 @@ namespace Hsdbg // resolve the saved id to a file; fall back to the first theme found, // then to the baked-in default, so the ui is never left unstyled std::filesystem::path path; - for (const auto& entry : m_themes) - { - if (entry.id == m_preferences.theme) - { - path = entry.path; - break; - } - } + const auto saved = std::ranges::find(m_themes, m_preferences.theme, &ThemeEntry::id); + if (saved != m_themes.end()) + path = saved->path; if (path.empty() && !m_themes.empty()) { path = m_themes.front().path; 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()) |
