aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore14
-rw-r--r--README.md26
-rw-r--r--meson.build351
-rw-r--r--meson_options.txt6
-rw-r--r--premake5.lua740
-rw-r--r--src/ui/ui.cpp22
-rw-r--r--tools/copy_files.py34
-rw-r--r--tools/fetch_llvm.py128
-rw-r--r--tools/find_llvm.py155
9 files changed, 702 insertions, 774 deletions
diff --git a/.gitignore b/.gitignore
index 4af3331..19440d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,6 @@
-bin/
-bin-int/
-
-/Makefile
-*.make
-
-*.sln
-*.vcxproj
-*.vcxproj.*
-
-*.xcodeproj/
+# meson build directories
+/build/
+/build-*/
.cache/
.DS_Store
diff --git a/README.md b/README.md
index 6f17e68..7fbc952 100644
--- a/README.md
+++ b/README.md
@@ -13,21 +13,33 @@ The console lets you type C or C++ directly into the debugger, which LLDB JIT-co
![screenshot](branding/screenshot.png)
## 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())