diff --git a/.githooks/pre-commit b/.githooks/pre-commit old mode 100644 new mode 100755 diff --git a/README.md b/README.md index 0398905..836e6e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,15 @@ -# Hot Reload +# Volkan + +
+ Logo + + # Volkan + + **C++ Hot-Reload library** +
+ +
+ A cross-platform, cross-compiler C/C++ library that patches a running process's own code in place, so a new build of a function takes effect diff --git a/integration/HotReload.cmake b/integration/HotReload.cmake new file mode 100644 index 0000000..47c6543 --- /dev/null +++ b/integration/HotReload.cmake @@ -0,0 +1,250 @@ +# ============================================================================= +# integration/HotReload.cmake +# +# Everything another CMake project needs to use this library, in one file, +# with no dependency on anything else under this repository's own cmake/ +# directory (that's internal build machinery for *this* repo -- see +# CONTRIBUTING.md -- and pulling it into someone else's build would also +# pull in this project's own test/example discovery, output-directory +# layout, and Doxygen integration, none of which a consumer asked for). +# Reading this file top to bottom is reading the whole integration surface; +# there's nothing else to go find. +# +# What you get, after including this file: +# - A `hotreload_lib` target (alias: `HotReload::hotreload_lib`) -- +# built from this repository's own source/HotReload/src, so there is +# exactly one implementation, never a second copy to drift out of sync. +# - hot_reload_enable_patchable_functions() and +# hot_reload_restrict_exports() -- identical in behavior to +# cmake/HotReload.cmake's own (see that file for the full reasoning +# behind each); duplicated here, not include()'d from there, so this +# one file has zero path dependency on the rest of this repo's layout. +# - hotreload_add_plugin( SOURCES [RESTRICT_EXPORTS]) +# -- the one-call version of "build a SHARED library correctly set up +# to be hot-reloaded", for when you don't need anything custom about +# the target. Skip it and call add_library() yourself, plus the two +# functions above, for anything it doesn't cover (EXTRA_SOURCES, +# target_compile_definitions, DEPENDS, ...). +# +# ---- Three ways to get this file into your own CMake project ---- +# +# 1) Vendor a copy of this repository (git submodule, or just copy the +# tree) and include() it directly -- the simplest option, and the one +# this file is tuned for by default: +# +# include(path/to/hotreload/integration/HotReload.cmake) +# target_link_libraries(my_app PRIVATE HotReload::hotreload_lib) +# hotreload_add_plugin(my_plugin SOURCES src/my_plugin.cpp) +# +# With no other configuration, this file finds this repository's own +# source/HotReload next to itself (see HOTRELOAD_SOURCE_DIR below) -- +# nothing else to set up. +# +# 2) FetchContent, if you'd rather not vendor a copy. Point SOURCE_SUBDIR +# at this directory (integration/) rather than the repository root -- +# it has no CMakeLists.txt of its own, so FetchContent_MakeAvailable() +# populates the source but does NOT add_subdirectory() this +# repository's own top-level CMakeLists.txt (that's the documented +# behavior of SOURCE_SUBDIR pointing at a directory with no +# CMakeLists.txt, CMake 3.18+ -- not a trick or a workaround), so none +# of the internal build system this file exists to let you skip ever +# runs. Verified directly: configuring this way produces no warning, +# deprecated or otherwise, on CMake versions from 3.18 through 4.4. +# +# include(FetchContent) +# FetchContent_Declare(hotreload +# GIT_REPOSITORY +# GIT_TAG +# SOURCE_SUBDIR integration) +# FetchContent_MakeAvailable(hotreload) +# include(${hotreload_SOURCE_DIR}/integration/HotReload.cmake) +# target_link_libraries(my_app PRIVATE HotReload::hotreload_lib) +# +# (Older CMake without SOURCE_SUBDIR support (pre-3.18): use +# FetchContent_Declare() + FetchContent_Populate() instead of +# MakeAvailable -- the same idea, fetch without add_subdirectory(), +# just via the older API for it.) +# +# 3) Point HOTRELOAD_SOURCE_DIR at source/HotReload yourself (any layout +# at all -- a sparse checkout, a copy with a different folder name, +# anything) and include() this file; see the variable below. +# +# ---- What this deliberately does NOT do ---- +# +# No install()/export()/find_package() support -- this is a "build it as +# part of your own project" integration, not an installed system package. +# No BUILD_EXAMPLES/BUILD_TESTS, no Doxygen, no output-directory +# redirection, no warning-flag opinions forced on your own targets, and +# this file never calls project() or cmake_minimum_required() itself (see +# below) -- all deliberate: a consumer's build should only ever notice +# this file added what it explicitly asked for. +# ============================================================================= + +include_guard(GLOBAL) + +# A version check, not a `cmake_minimum_required()` call: this file is +# include()'d into the CALLER's own scope (it's not its own subdirectory +# with a fresh policy scope), so calling cmake_minimum_required() here +# would silently change policy versions for the rest of *your* CMakeLists +# too. 3.16 is the floor this file's own features actually need: +# FetchContent (3.11+), file(GLOB CONFIGURE_DEPENDS) (3.12+), +# target_compile_features(... cxx_std_20) (3.12+), include_guard (3.10+). +if(CMAKE_VERSION VERSION_LESS "3.16") + message(FATAL_ERROR "integration/HotReload.cmake needs CMake 3.16 or newer (found ${CMAKE_VERSION}).") +endif() + +# ---- Where source/HotReload actually lives ---- +# Defaults to right next to this file, since that's where it is in this +# repository; override by setting this BEFORE including this file if +# you've laid your checkout out some other way. +if(NOT DEFINED HOTRELOAD_SOURCE_DIR) + get_filename_component(HOTRELOAD_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../source/HotReload" ABSOLUTE) +endif() + +if(NOT EXISTS "${HOTRELOAD_SOURCE_DIR}/include/hot_reload/hot_reload.h") + message(FATAL_ERROR + "integration/HotReload.cmake: no hot_reload.h under " + "'${HOTRELOAD_SOURCE_DIR}/include/hot_reload/'. Set HOTRELOAD_SOURCE_DIR " + "to wherever source/HotReload actually is before including this file.") +endif() + +# ============================================================================= +# hotreload_lib -- the library itself. +# +# Mirrors add_lib_module(hotreload_lib TYPE STATIC)'s own behavior +# (cmake/BuildHelpers.cmake + source/HotReload/CMakeLists.txt) exactly, +# source-globbing included, so this file and the real project build +# identical code: src/*.cpp recursively under source/HotReload (including +# src/c-api), include/ as the one PUBLIC include directory, STATIC + +# position-independent so it's safe to end up inside a SHARED library or +# PIE executable later. cxx_std_20 is PRIVATE, not PUBLIC: that's what +# this library's own implementation needs internally, but hot_reload.h +# itself is plain C-compatible (valid C11 and valid C++14 through C++20+, +# confirmed directly by compiling the header alone under each), so this +# deliberately does not force C++20 on your own targets just for calling +# into this one. +# ============================================================================= +if(NOT TARGET hotreload_lib) + file(GLOB_RECURSE _hotreload_integration_sources CONFIGURE_DEPENDS "${HOTRELOAD_SOURCE_DIR}/src/*.cpp") + file(GLOB_RECURSE _hotreload_integration_headers CONFIGURE_DEPENDS + "${HOTRELOAD_SOURCE_DIR}/include/*.h" "${HOTRELOAD_SOURCE_DIR}/include/*.hpp") + + add_library(hotreload_lib STATIC ${_hotreload_integration_sources} ${_hotreload_integration_headers}) + add_library(HotReload::hotreload_lib ALIAS hotreload_lib) + + target_include_directories(hotreload_lib PUBLIC "${HOTRELOAD_SOURCE_DIR}/include") + target_compile_features(hotreload_lib PRIVATE cxx_std_20) + set_target_properties(hotreload_lib PROPERTIES + OUTPUT_NAME hotreload + POSITION_INDEPENDENT_CODE ON) + + if(NOT WIN32) + # dlopen/dlclose/dlinfo (ModuleHandle.cpp). PUBLIC: for a STATIC + # library this needs to reach whatever executable ultimately + # links hotreload_lib, matching source/HotReload/CMakeLists.txt. + target_link_libraries(hotreload_lib PUBLIC ${CMAKE_DL_LIBS}) + endif() + if(MSVC) + # UnDecorateSymbolName (Demangle.cpp) lives in dbghelp.lib, which + # MSVC does not link by default. Guarded on MSVC specifically + # (true for cl.exe and clang-cl, false for MinGW), matching + # source/HotReload/CMakeLists.txt exactly. + target_link_libraries(hotreload_lib PUBLIC dbghelp) + endif() + + unset(_hotreload_integration_sources) + unset(_hotreload_integration_headers) +endif() + +# ============================================================================= +# hot_reload_enable_patchable_functions() / +# hot_reload_restrict_exports() +# +# Identical behavior to cmake/HotReload.cmake's versions of the same two +# functions -- see that file for the full design reasoning behind each +# (the 32-byte patchable-entry size, why /hotpatch is the primary safety +# mechanism on MSVC specifically, why restrict_exports can't and doesn't +# try to quiet std::string/std::vector noise). Guarded so this plays +# nicely if a consumer somehow also ends up with the real +# cmake/HotReload.cmake included in the same configure. +# ============================================================================= +if(NOT COMMAND hot_reload_enable_patchable_functions) + function(hot_reload_enable_patchable_functions TARGET_NAME) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${TARGET_NAME} PRIVATE -fpatchable-function-entry=32,0) + elseif(MSVC) + target_compile_options(${TARGET_NAME} PRIVATE /hotpatch) + else() + message(WARNING + "hot_reload_enable_patchable_functions(${TARGET_NAME}): " + "unrecognized compiler '${CMAKE_CXX_COMPILER_ID}', no patchable-entry " + "flag applied. Hot-reloading ${TARGET_NAME} will still work for any " + "function whose compiled body happens to be large enough on its own; " + "see HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL in hot_reload.h.") + endif() + endfunction() +endif() + +if(NOT COMMAND hot_reload_restrict_exports) + function(hot_reload_restrict_exports TARGET_NAME) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${TARGET_NAME} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden) + elseif(MSVC) + # MSVC already exports nothing by default; simply not setting + # WINDOWS_EXPORT_ALL_SYMBOLS is this function's Windows + # equivalent. Nothing to apply here -- see + # hotreload_add_plugin()'s RESTRICT_EXPORTS branch below, + # which is what actually turns that property off. + endif() + endfunction() +endif() + +# ============================================================================= +# hotreload_add_plugin( SOURCES [RESTRICT_EXPORTS]) +# +# The one-call version of "build a SHARED library correctly set up to be +# hot-reloaded": add_library(... SHARED), patchable function entries, and +# an export policy -- exactly the three steps source/HotReload/README.md's +# "Using it in your own module" walks through by hand. Does NOT link the +# plugin against hotreload_lib: a plugin loaded via dlopen()/LoadLibrary() +# doesn't call into this library's API at all (the HOST does), so it only +# gets the include directory, for HOT_RELOAD_EXPORT -- matching +# source/examples/HotReloadLiveDemo/Plugin/CMakeLists.txt exactly, which +# does the same thing by hand for the same reason. +# +# Default export policy is "export everything" (GCC/Clang/MinGW's own +# default, plus WINDOWS_EXPORT_ALL_SYMBOLS so MSVC matches it) -- zero +# extra annotation needed to get a working plugin, matching +# source/examples/HotReloadDemo's PluginV1/PluginV2. Pass RESTRICT_EXPORTS +# for the narrower, recommended-for-a-real-project surface instead (every +# intended entry point then needs `extern "C" HOT_RELOAD_EXPORT` -- +# hot_reload.h -- or plain `HOT_RELOAD_EXPORT` on a C++ class/method); +# matches HotReloadLiveDemo's plugin. +# +# For anything beyond a plain list of source files -- EXTRA_SOURCES, +# target_compile_definitions, linking your own other libraries into the +# plugin -- call add_library() yourself and the two functions above +# directly instead; this wrapper only covers the common case. +# ============================================================================= +function(hotreload_add_plugin TARGET_NAME) + cmake_parse_arguments(ARG "RESTRICT_EXPORTS" "" "SOURCES" ${ARGN}) + + if(NOT ARG_SOURCES) + message(FATAL_ERROR "hotreload_add_plugin(${TARGET_NAME}): SOURCES is required.") + endif() + if(ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "hotreload_add_plugin(${TARGET_NAME}): unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + add_library(${TARGET_NAME} SHARED ${ARG_SOURCES}) + target_include_directories(${TARGET_NAME} PRIVATE "${HOTRELOAD_SOURCE_DIR}/include") + + hot_reload_enable_patchable_functions(${TARGET_NAME}) + + if(ARG_RESTRICT_EXPORTS) + hot_reload_restrict_exports(${TARGET_NAME}) + set_target_properties(${TARGET_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF) + else() + set_target_properties(${TARGET_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + endif() +endfunction() diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 diff --git a/scripts/docs.sh b/scripts/docs.sh old mode 100644 new mode 100755 diff --git a/scripts/install.sh b/scripts/install.sh old mode 100644 new mode 100755 diff --git a/scripts/refactor.sh b/scripts/refactor.sh old mode 100644 new mode 100755 diff --git a/scripts/run.sh b/scripts/run.sh old mode 100644 new mode 100755 diff --git a/scripts/setup.sh b/scripts/setup.sh old mode 100644 new mode 100755 diff --git a/source/examples/HotReloadLiveDemo/watch.sh b/source/examples/HotReloadLiveDemo/watch.sh old mode 100644 new mode 100755