From 9248925b7745db9995877ae3f0ae7ec00b419888 Mon Sep 17 00:00:00 2001 From: Acrux Development Date: Sat, 12 Sep 2026 15:29:06 -0300 Subject: [PATCH] CMake template clenaups and script adaptations --- CMakeLists.txt | 11 +- README.md | 298 ++++++++++-------- cmake/BuildHelpers.cmake | 4 +- cmake/Documentation.cmake | 4 +- cmake/HotReload.cmake | 28 ++ scripts/build.bat | 13 +- scripts/build.sh | 15 +- scripts/docs.bat | 2 +- scripts/docs.sh | 2 +- scripts/install.bat | 2 +- scripts/install.sh | 2 +- scripts/refactor.bat | 8 +- scripts/refactor.sh | 2 +- scripts/run.bat | 4 +- scripts/run.sh | 4 +- source/HotReload/README.md | 93 +++++- .../HotReload/include/hot_reload/hot_reload.h | 23 ++ .../HotReloadLiveDemo/Plugin/CMakeLists.txt | 12 +- .../Plugin/src/LivePlugin.cpp | 4 +- 19 files changed, 361 insertions(+), 170 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94dd67a..96a20df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,16 +2,15 @@ cmake_minimum_required(VERSION 3.25) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( - DemoApp + HotReload VERSION 0.1.0 - DESCRIPTION "Demo app exercising the generator-friendly CMake module system" + DESCRIPTION "Cross-platform, cross-compiler C/C++ hot-reload library, plus its examples and tests" LANGUAGES CXX ) # Machine-friendly identifier baked into every target as PROJECT_NAME_CODE -# (used by BuildHelpers.cmake's _finalize_module_target). Edit this -# alongside the project() call above when retargeting this template. -set(PROJECT_CODE_NAME "demo") +# (used by BuildHelpers.cmake's _finalize_module_target). +set(PROJECT_CODE_NAME "hotreload") # All output-path configuration (out/, install/, dist/, docs/, and the # bin/lib subdirectory names) lives in Configuration.cmake -- included @@ -206,7 +205,7 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/BuildHelpers.cmake") # Third-party dependencies -- see cmake/Dependencies.cmake for # add_vendor_header_only / add_vendor_subdirectory / add_dependency. # A module declares its own dependencies in its own CMakeLists.txt (see -# source/Core/CMakeLists.txt for an example); nothing to add here. +# source/HotReload/CMakeLists.txt for an example); nothing to add here. # ------------------------------------------------------- include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/Dependencies.cmake") diff --git a/README.md b/README.md index 7433734..d54c1f2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,35 @@ -# CMake module-system demo +# Hot Reload -A minimal C++20 project showing a per-module CMake setup: each module owns -a tiny, self-contained `CMakeLists.txt`, the root `CMakeLists.txt` -discovers modules automatically, three ways to bring in third-party code -(vendored, installed, or fetched) stay out of your warnings and your -compile database, every module gets its own browsable documentation via a -pinned Doxygen that's entirely opt-in (never required just to build), -Ninja itself is pinned and fetched the same way with a graceful fallback -if it's unavailable, and every output path is configured in one place. +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 +immediately, without restarting -- see +[`source/HotReload/README.md`](source/HotReload/README.md) for what it +does, what it guarantees, and its honest limitations. This file covers +the project around it: the CMake module system, its tooling, and how +the examples and tests fit together. + +```sh +scripts/setup.sh # optional, one-time -- see "Setup" below +scripts/build.sh Release -DBUILD_EXAMPLES=ON +./out/build/bin/examples/hotreload_demo_host +``` + +## What's here + +| | | +|---|---| +| [`source/HotReload/`](source/HotReload/README.md) | The library itself: `hotreload_lib`, a `STATIC` library with a plain C API (`hot_reload.h`) over a C++ implementation | +| [`source/examples/HotReloadDemo/`](source/examples/HotReloadDemo) | A scripted, deterministic demo: a host + two builds of one plugin, exercising every guarantee the library makes | +| [`source/examples/HotReloadLiveDemo/`](source/examples/HotReloadLiveDemo/README.md) | An interactive demo: a host that stays running, and one plugin file you edit and rebuild yourself | +| [`source/tests/HotReloadTests/`](source/tests/HotReloadTests) | An automated `ctest` suite covering the library end to end, including a regression test for a real bug found while building this | + +The rest of this file is about the project *around* that: a per-module +CMake setup adapted from a generic template (each module owns a tiny, +self-contained `CMakeLists.txt`; the root `CMakeLists.txt` discovers +them automatically), `scripts/` for the everyday commands, and the +code-style tooling. None of it is specific to hot-reloading -- if +you're only here for the library, `source/HotReload/README.md` is the +one to read. ## Layout @@ -21,15 +43,12 @@ cmake/DoxygenPin.cmake shared version/URL/hash parameters for the pinne cmake/FetchDoxygen.cmake standalone downloader (run via `cmake -P`, not during normal configure) cmake/NinjaPin.cmake shared version/URL/hash parameters for the pinned Ninja cmake/FetchNinja.cmake standalone downloader (run via `cmake -P`, not during normal configure) -cmake/HotReload.cmake hot_reload_enable_patchable_functions() -- see source/HotReload/README.md -source/Core/CMakeLists.txt library module "core_lib" (SHARED), depends on fmt -source/HelloApp/CMakeLists.txt executable module "hello_exe", depends on vendored tiny_ansi -source/FarewellApp/CMakeLists.txt executable module "farewell_exe", depends on vendored tiny_case -source/HotReload/CMakeLists.txt library module "hotreload_lib" (STATIC) -- see source/HotReload/README.md +cmake/HotReload.cmake hot_reload_enable_patchable_functions() / hot_reload_restrict_exports() +source/HotReload/ the library -- library module "hotreload_lib" (STATIC) source/examples/HotReloadDemo/ worked example: a host + two builds of one hot-reloadable plugin source/examples/HotReloadLiveDemo/ interactive: a persistent host + one plugin file you edit yourself -vendor/tiny_ansi/ header-only vendored dependency (no build of its own) -vendor/tiny_case/ vendored dependency with its own CMakeLists.txt +source/tests/HotReloadTests/ the automated test suite +source/tests/UndefinedSymbolFixture/ a fixture the test suite loads, deliberately broken -- see its own comment scripts/setup.sh, setup.bat one-time: fetch pinned Ninja + Doxygen, check tooling, activate git hook scripts/build.sh, build.bat configure + build -- prefers a pinned Ninja, falls back gracefully scripts/run.sh, run.bat run a module by its CMake target name @@ -39,8 +58,8 @@ scripts/refactor.sh, refactor.bat auto-fix formatting/naming across this projec .clang-format, .clang-tidy C++ style guide tooling (see "Code style & tooling" below) .editorconfig, .gitattributes -- same .gitignore Visual Studio template + a block auto-synced from Configuration.cmake -c-api/.clang-tidy naming override template for a C API subtree -- now also applied for - real at source/HotReload/src/c-api/ and source/HotReload/include/hot_reload/ +c-api/.clang-tidy naming override template for a C API subtree -- applied for real at + source/HotReload/src/c-api/ and source/HotReload/include/hot_reload/ .githooks/pre-commit formatting/lint pre-commit hook cpp-style-guide.md, TOOLING.md the style guide itself + tooling docs ``` @@ -49,21 +68,20 @@ Everything generated -- the build tree, install output, docs, and reserved packaging output -- lives under one `out/` directory (see "Output paths" below); nothing under `out/` is source-controlled. -Each module's `CMakeLists.txt` is (conventionally) a couple of lines: +Each module's `CMakeLists.txt` is (conventionally) one line: ```cmake -# source/Core/CMakeLists.txt -add_dependency(fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 11.0.2 - FIND_PACKAGE_ARGS NAMES fmt -) -add_lib_module(core_lib TYPE SHARED OUTPUT_NAME "demo_core" DEPENDS fmt::fmt) +# source/HotReload/CMakeLists.txt +add_lib_module(hotreload_lib TYPE STATIC OUTPUT_NAME "hotreload") ``` -A module declares its own dependencies in its own `CMakeLists.txt` -- same -"minimal tool intervention" principle as module registration: nothing to -add in the root file. +A module declares its own dependencies in its own `CMakeLists.txt` -- +same "minimal tool intervention" principle as module registration: +nothing to add in the root file. This project's own modules don't +currently need any third-party dependency, so the table in "Three ways +to bring in a dependency" below is showing you machinery this project +isn't using itself right now -- it's still there, exercised, and ready +if you add a module that needs it. ## Output paths (`Configuration.cmake`) @@ -134,11 +152,16 @@ normal reconfigure doesn't spuriously touch its contents or mtime. ## Three ways to bring in a dependency (`cmake/Dependencies.cmake`) -| Helper | For | Example | +Not currently used by any module in this project (`hotreload_lib` and +everything under `source/examples/`/`source/tests/` are entirely +self-contained -- no network access is needed to build any of it), but +fully exercised machinery, ready for a module that does need one: + +| Helper | For | Usage | |---|---|---| -| `add_vendor_header_only(NAME [INCLUDE_DIR ])` | A buildless header-only drop under `vendor//include` | `vendor/tiny_ansi/` | -| `add_vendor_subdirectory(SUBDIR)` | A vendored dependency with its own `CMakeLists.txt`, under `vendor/` | `vendor/tiny_case/` | -| `add_dependency(NAME )` | An installed package (`FIND_PACKAGE_ARGS ...`) with a fetch-from-source fallback | `fmt` in `source/Core/` | +| `add_vendor_header_only(NAME [INCLUDE_DIR ])` | A buildless header-only drop under `vendor//include` | `add_vendor_header_only(some_header_lib)` | +| `add_vendor_subdirectory(SUBDIR)` | A vendored dependency with its own `CMakeLists.txt`, under `vendor/` | `add_vendor_subdirectory(some_vendored_lib)` | +| `add_dependency(NAME )` | An installed package (`FIND_PACKAGE_ARGS ...`) with a fetch-from-source fallback | `add_dependency(fmt GIT_REPOSITORY ... FIND_PACKAGE_ARGS NAMES fmt)` | All three are safe to call more than once for the same name (e.g. two modules that need the same dependency) and mark the dependency's headers @@ -153,10 +176,9 @@ code you don't control. Their targets are also dropped from found. Omit `FIND_PACKAGE_ARGS` to always fetch. Requires network access the first time a dependency isn't already installed. -A module `DEPENDS` on whatever target the chosen path defines (`fmt::fmt`, -`tiny_case`, `tiny_ansi`, ...) exactly like depending on another module -- -`add_lib_module`/`add_exe_module` don't need to know or care which of the -three supplied it. +A module `DEPENDS` on whatever target the chosen path defines exactly +like depending on another module -- `add_lib_module`/`add_exe_module` +don't need to know or care which of the three supplied it. ## Documentation (automatic per module, pinned Doxygen, entirely opt-in) @@ -191,11 +213,11 @@ Once set up: ```bash scripts/docs.sh # build docs for every module, prints where to open it -scripts/docs.sh core_lib # build docs for just one module (docs_) +scripts/docs.sh hotreload_lib # build docs for just one module (docs_) ``` ```bat scripts\docs.bat -scripts\docs.bat core_lib +scripts\docs.bat hotreload_lib ``` Output goes to `out/docs//html/index.html` (a sibling of @@ -205,15 +227,8 @@ Documentation is **not** built as part of a normal `scripts/build.sh` -- it's opt-in, the same way tests and examples are. **Writing docs for a module** is just normal Doxygen comments in that -module's headers -- e.g. `source/Core/include/core/Greeting.hpp`: - -```cpp -/// Builds a greeting for @p name. -/// -/// @param name the person (or thing) being greeted. -/// @return a formatted greeting string, e.g. "Hello, Ale! (from demo_core)". -std::string makeGreeting(const std::string& name); -``` +module's headers -- e.g. `source/HotReload/include/hot_reload/hot_reload.h`, +which is already written this way throughout. Even with zero comments, a module's docs are still generated and useful -- file lists, function/class signatures, and (if [Graphviz](https://graphviz.org/) @@ -222,7 +237,7 @@ itself, is still detected rather than pinned, since it's optional) call graphs are produced regardless, since `DOXYGEN_EXTRACT_ALL` is on by default. Comments just make it richer. Drop a `README.md` in a module's own directory and it's automatically used as that module's Doxygen main -page. +page -- `source/HotReload/README.md` already does this. **Configuring it** -- nothing is required, but everything is overridable: @@ -266,19 +281,24 @@ cmake -G Ninja -S . -B out/build -DCMAKE_CXX_COMPILER=clang++ -DDIST_TARGET=linu cmake --build out/build ``` +Verified, not assumed, throughout this project's own development: every +module here (the library, both example demos, and the test suite) was +built and its tests/demos run to completion under both GCC and Clang. + ## Code style & tooling This project follows [`cpp-style-guide.md`](cpp-style-guide.md) (tabs, Allman braces, `PascalCase` file names, `camelCase` functions/variables, -`m_`/`s_`/`g_` member/static/global prefixes -- see the file itself for -the full rules and rationale), enforced by the tooling described in -[`TOOLING.md`](TOOLING.md): +`m_`/`s_`/`g_` member/static/global prefixes, plus a distinct Part II +style -- `snake_case`, library-prefixed names -- for any C API boundary; +see the file itself for the full rules and rationale), enforced by the +tooling described in [`TOOLING.md`](TOOLING.md): | File | Enforces | |---|---| | `.clang-format` | All formatting: tabs, Allman braces, pointer alignment, include order, line length | | `.clang-tidy` | Naming conventions and a subset of language-feature rules, for this project's own C++ code | -| `c-api/.clang-tidy` | A template naming override (`snake_case`) for a C API subtree, if/when this project has one -- see below | +| `c-api/.clang-tidy` | A naming override (`snake_case`) for a C API subtree -- see below for where this is actually applied | | `.editorconfig` | Baseline tabs/whitespace for editors that don't run clang-format live | | `.gitattributes` | Forces LF line endings for source files on every OS | | `.githooks/pre-commit` | Runs clang-format + clang-tidy on staged files before each commit; warns and skips (never blocks) if a tool isn't installed | @@ -286,34 +306,48 @@ the full rules and rationale), enforced by the tooling described in **Fixing violations automatically**, instead of hunting them down by hand: ```bash -scripts/refactor.sh # every file under source/ and c-api/ by default -scripts/refactor.sh source/Core # just one file or directory +scripts/refactor.sh # every file under source/ and c-api/ by default +scripts/refactor.sh source/HotReload # just one file or directory ``` ```bat scripts\refactor.bat -scripts\refactor.bat source\Core +scripts\refactor.bat source\HotReload ``` Runs `clang-format -i` (always) and `clang-tidy --fix` (if `compile_commands.json` exists -- build once first if it doesn't) across the given scope. The pre-commit hook only *checks*; this is the "just fix it" counterpart -- review the result with `git diff` afterward, same as -any auto-formatter. This is exactly how this project's own code -(`source/`) was brought into compliance when the toolkit was integrated -- -see the renames noted further down. +any auto-formatter. + +**Be especially careful reviewing `git diff` on a file with more than one +architecture/OS branch** (`#if defined(__x86_64__) ... #elif ... #endif`, +several of which exist under `source/HotReload/src/` -- see +`Architecture.cpp`, `ModuleHandle.cpp`, `MemoryProtection.cpp`, +`SymbolTable.cpp`). `clang-tidy` only ever analyzes the ONE branch active +for whichever platform you're running it on -- it cannot see, and will +not touch, the source text inside the other, inactive `#elif` branches at +all. Verified directly while building this project: running `--fix` on +`Architecture.cpp` from this x86-64 Linux machine renamed a local +constant inside the x86-64 branch only, then updated a later line shared +by *all three* architecture branches to match the new name -- silently +breaking the build for the other two architectures, which still declared +the old name. Caught only by reviewing the diff and separately +cross-compiling every branch, which is exactly the review step this +paragraph is asking you to actually do on a file shaped like this, not +skip. With no argument, only `source/` and `c-api/` are ever scanned -- `out/`, -`.cache/`, `vendor/`, and `.git/` are never walked, so this can't -accidentally spend minutes reformatting/analyzing a fetched dependency's -entire source tree (verified against `out/build/_deps/fmt-src/` -specifically, since that's what triggered this exact problem before the -fix). Passing an explicit scope takes it as given: `refactor.sh` still -excludes `vendor`/`out`/`.cache`/`.git` defensively if your scope happens -to overlap with them; `refactor.bat` does not (its Windows batch -equivalent of that filter turned out to be unreliable to verify, so it -was removed rather than shipped uncertain -- pick a scope that doesn't -overlap with those directories, which is the normal case anyway, e.g. -`source\Core`). +`.cache/`, and `.git/` are never walked, so this can't accidentally spend +minutes reformatting/analyzing a fetched dependency's entire source tree +(this project doesn't currently have one under `out/build/_deps/`, but the +exclusion is unconditional, not conditional on one existing). Passing an +explicit scope takes it as given: `refactor.sh` still excludes +`out`/`.cache`/`.git` defensively if your scope happens to overlap with +them; `refactor.bat` does not (its Windows batch equivalent of that filter +turned out to be unreliable to verify, so it was removed rather than +shipped uncertain -- pick a scope that doesn't overlap with those +directories, which is the normal case anyway, e.g. `source\HotReload`). **One-time setup per clone** to activate the hook -- `scripts/setup.sh` / `setup.bat` does this for you (see "Setup" below) along with fetching the @@ -330,42 +364,18 @@ this project already generates and copies it to the repo root on every build (see above), so as long as you've built at least once, the hook's clang-tidy check works with no extra setup. -Two adjustments made when integrating the toolkit into this specific -project (not part of the toolkit as provided -- see `TOOLING.md` in this -repo for the toolkit's own documentation of everything else): - -- **`vendor/` is excluded** from the pre-commit hook's staged-file check - (`git diff ... ':!vendor/*'`). Vendored code (`cmake/Dependencies.cmake`) - isn't this project's code, so it isn't held to this style guide -- same - reasoning as its `compile_commands.json` and warning-flag exclusions - elsewhere in this project. -- **`.clang-tidy` gained one option**, `LocalConstantCase: camelBack`. The - style guide's own text says only *global/static* `const`/`constexpr` - values are "constants" for naming purposes -- everything else stays - under the `camelCase` Local Variables rule. Without this option, a plain - local `const` (e.g. `const std::string name = ...;` inside a function) - silently fell through to the general `PascalCase` constant rule instead, - which would have flagged completely ordinary code. Verified against a - small isolated test file: with the fix, a local `const` is accepted in - `camelCase` while a `static const`/`constexpr` still correctly requires - `PascalCase`. - -`c-api/.clang-tidy` is included as a template, positioned exactly as the -toolkit ships it (`c-api/.clang-tidy`) -- this project doesn't currently -have a C API (Part II) surface. If you add one, move this file to that -subtree's root (`c-api/`, `public-include/`, wherever your `extern "C"` -headers live); `clang-tidy` always uses the closest `.clang-tidy` file up -the directory tree, so files under that directory automatically pick up -the C API's `snake_case` naming instead of this project's `camelCase`. - -This project's own code was reformatted and renamed to comply when the -toolkit was integrated -- e.g. `Greeting.hpp`/`.cpp` (was `greeting.h`/ -`.cpp`) and `makeGreeting`/`makeFarewell` (was `make_greeting`/ -`make_farewell`) in `source/Core/`. `main.cpp` in each app module became -`Main.cpp` for the same reason (`PascalCase` file names) -- the style -guide doesn't call out an entry-point exception, so none was assumed; -rename back to lowercase if you'd rather treat that specific convention -as an exception. +**This project genuinely has a C API subtree**, unlike the generic +template this was adapted from: `source/HotReload/include/hot_reload/` +(the public header) and `source/HotReload/src/c-api/` (its `extern "C"` +implementation) each carry a copy of `c-api/.clang-tidy`, positioned at +the root of the subtree they apply to -- `clang-tidy` always uses the +closest `.clang-tidy` file up the directory tree, so files under either +directory pick up `snake_case` naming instead of the rest of this +project's `PascalCase`/`camelCase`, while `source/HotReload/src/`'s own +Part I (C++) files, one level up, keep inheriting this repo-root +`.clang-tidy`'s rules exactly as normal. See either copy's own header +comment for the full explanation of how clang-tidy's directory-scoped +config resolution makes that work. ## How module discovery works @@ -388,19 +398,32 @@ Any immediate child directory that contains its own `CMakeLists.txt` gets need them, not auto-built by default). This uses `CONFIGURE_DEPENDS`, so the *next build* reconfigures on its own after you add or remove a module directory -- no manual `cmake` re-run needed. A module may `DEPENDS` on a -module discovered later in this list; order doesn't affect linking (CMake -resolves target names across the whole project at generate time -- -verified, not assumed). +module discovered later in this list, or reference one via +`add_dependencies()`/a generator expression before that other module's +own `CMakeLists.txt` has even run -- order doesn't affect linking or +target existence (CMake resolves target names, and dependency edges to +not-yet-declared targets, across the whole project at generate time -- +verified directly with an isolated, minimal reproduction while building +this project, not assumed). + +A module directory can itself contain more than one CMake target when its +shape doesn't fit the "one target per module" default -- see +`source/examples/HotReloadDemo/PluginV1/CMakeLists.txt`'s own comment for +a real example (a hot-reloadable plugin needs to exist as two separate +builds of the same exported names, which `add_lib_module` doesn't assume) +and `source/examples/HotReloadDemo/CMakeLists.txt` for the small grouping +file that pattern needs, one level up, purely so discovery (which only +looks one level deep into `source/examples/`) finds it at all. ## Requirements -CMake >= 3.25, a C++20 compiler (GCC/Clang/MSVC), `git` (for -FetchContent's `GIT_REPOSITORY`-based dependencies like `fmt`), and -network access the first time you configure (for `fmt`, cached afterward -under `out/build/_deps`). Notably **not** in this list: Ninja and Doxygen -are both pinned and fetched automatically (see "Setup" below), not system -dependencies, and `scripts/build.sh` warns rather than fails if Ninja -isn't available at all (see "Build" below). +CMake >= 3.25, a C++20 compiler (GCC/Clang/MSVC). **No network access is +needed to build this project** -- every module here is self-contained, +with no `add_dependency()`-fetched third-party library anywhere (see +"Three ways to bring in a dependency" above). Ninja and Doxygen are both +pinned and fetched automatically (see "Setup" below) if you want them, +not system dependencies, and `scripts/build.sh` warns rather than fails +if Ninja isn't available at all (see "Build" below). [Graphviz](https://graphviz.org/) is optional for Doxygen call graphs, and [clang-format](https://clang.llvm.org/docs/ClangFormat.html)/ [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) are optional for @@ -409,6 +432,11 @@ above) -- both are detected, not pinned, since they're editor/workflow tools rather than part of the build itself. `scripts/setup.sh` (below) checks for both and prints install guidance if either is missing. +To actually try hot-reloading something (rather than just build the +library), you'll also want a way to rebuild one file while another +process keeps running -- covered in +[`source/examples/HotReloadLiveDemo/README.md`](source/examples/HotReloadLiveDemo/README.md). + ## Setup Optional, and separate from building (see "Requirements", "Build", and @@ -443,14 +471,24 @@ Safe to re-run any time -- each step is a no-op if there's nothing to do ## Build ```bash -scripts/build.sh # Release (default) -scripts/build.sh Debug # or Debug / RelWithDebInfo / MinSizeRel +scripts/build.sh # Release (default), library + tests only if BUILD_TESTS was set before +scripts/build.sh Release -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON # + the automated tests + both demos ``` ```bat scripts\build.bat -scripts\build.bat Debug +scripts\build.bat Release -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON ``` +`BUILD_TESTS` and `BUILD_EXAMPLES` both default to `OFF` (see +`CMakeLists.txt`) -- plain `scripts/build.sh` with no extra arguments +builds only `hotreload_lib` itself. Anything after the build type is +passed straight through to `cmake`'s configure step (tested directly: +`scripts/build.sh Debug -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON` builds the +library, both demos, and the test suite together), and CMake caches `-D` +options across reconfigures, so passing them once and calling +`scripts/build.sh` plain afterward for incremental builds keeps them set +-- no need to repeat them on every call. + Picks a generator in this order, each falling back to the next: 1. **A pinned Ninja**, if `scripts/setup.sh`/`setup.bat` has fetched one -- @@ -474,19 +512,21 @@ a Visual Studio generator on Windows) instead. ## Run ```bash -scripts/run.sh hello_exe -scripts/run.sh farewell_exe -- Ale +scripts/run.sh hotreload_demo_host +scripts/run.sh hotreload_live_demo_host scripts/run.sh # no args: lists available targets ``` ```bat -scripts\run.bat hello_exe -scripts\run.bat farewell_exe -- Ale +scripts\run.bat hotreload_demo_host +scripts\run.bat hotreload_live_demo_host ``` `run.sh`/`run.bat` resolve the CMake **target id** (not the binary's `OUTPUT_NAME`) to the actual binary via a manifest CMake generates at configure time, so these scripts never need editing when a module is -added, removed, or renamed. +added, removed, or renamed. Requires `-DBUILD_EXAMPLES=ON` to have been +set at configure time for either of the two hot-reload targets above, +since they live under `source/examples/`. ## Install @@ -497,14 +537,16 @@ scripts/install.sh scripts\install.bat ``` -Always use the install script (or pass `--component demo` yourself if +Always use the install script (or pass `--component hotreload` yourself if calling `cmake --install` directly). A vendored/fetched dependency's own `CMakeLists.txt` usually has `install()` rules of its own with no component tag; installing without `--component` would also run those, typically dumping files under `CMAKE_INSTALL_PREFIX` (e.g. `/usr/local`). Tagging this project's own `install(TARGETS ...)` calls with `COMPONENT -demo` and always filtering on it is what keeps `cmake --install` scoped to -just this project's own artifacts. +hotreload` and always filtering on it is what keeps `cmake --install` +scoped to just this project's own artifacts -- currently just +`hotreload_lib` itself, since examples and tests are opt-in, dev-time +targets rather than something you'd install. ## Adding a module @@ -517,7 +559,7 @@ just this project's own artifacts. ```cmake # source/MyTool/CMakeLists.txt -add_exe_module(my_tool_exe OUTPUT_NAME "my-tool" DEPENDS core_lib) +add_exe_module(my_tool_exe OUTPUT_NAME "my-tool" DEPENDS hotreload_lib) ``` `scripts/run.sh my_tool_exe` picks it up automatically once it's built, diff --git a/cmake/BuildHelpers.cmake b/cmake/BuildHelpers.cmake index 493ecac..6948cf8 100644 --- a/cmake/BuildHelpers.cmake +++ b/cmake/BuildHelpers.cmake @@ -217,7 +217,7 @@ endfunction() # add_lib_module(NAME [TYPE STATIC|SHARED] [OUTPUT_NAME name] [DEPENDS ...] # [EXTRA_SOURCES ...] [NO_DOCS]) # -# Called from a library module's own CMakeLists.txt (e.g. source/Core/CMakeLists.txt). +# Called from a library module's own CMakeLists.txt (e.g. source/HotReload/CMakeLists.txt). # Sources/headers are globbed relative to that module's own directory: # src/*.cpp and include/*.h(pp) underneath it. TYPE picks STATIC or SHARED # for this module only; omit it to use the project-wide default @@ -283,7 +283,7 @@ endmacro() # ============================================================================= # add_exe_module(NAME [OUTPUT_NAME name] [DEPENDS ...] [EXTRA_SOURCES ...] [NO_DOCS]) # -# Called from an app module's own CMakeLists.txt (e.g. HelloApp/CMakeLists.txt). +# Called from an app module's own CMakeLists.txt (e.g. HotReloadDemo/Host/CMakeLists.txt). # Sources/headers are globbed relative to that module's own directory: # *.cpp and include/*.h(pp) underneath it. # ============================================================================= diff --git a/cmake/Documentation.cmake b/cmake/Documentation.cmake index 7f67ab9..6cdb9ca 100644 --- a/cmake/Documentation.cmake +++ b/cmake/Documentation.cmake @@ -26,7 +26,7 @@ # -DDOXYGEN_=... any Doxyfile tag can be set this way -- # see https://www.doxygen.nl/manual/config.html # A module can opt out with NO_DOCS, e.g.: -# add_lib_module(core_lib TYPE SHARED NO_DOCS ...) +# add_lib_module(hotreload_lib TYPE STATIC NO_DOCS ...) # ============================================================================= # ============================================================================= @@ -65,7 +65,7 @@ # -DDOXYGEN_=... any Doxyfile tag can be set this way -- # see https://www.doxygen.nl/manual/config.html # A module can opt out with NO_DOCS, e.g.: -# add_lib_module(core_lib TYPE SHARED NO_DOCS ...) +# add_lib_module(hotreload_lib TYPE STATIC NO_DOCS ...) # ============================================================================= include_guard(GLOBAL) diff --git a/cmake/HotReload.cmake b/cmake/HotReload.cmake index 2ee4a2c..02a0e57 100644 --- a/cmake/HotReload.cmake +++ b/cmake/HotReload.cmake @@ -52,3 +52,31 @@ function(hot_reload_enable_patchable_functions TARGET_NAME) "see HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL in hot_reload.h.") endif() endfunction() + +# Restricts TARGET's export surface to only what's explicitly marked +# with HOT_RELOAD_EXPORT (hot_reload.h) -- the opposite of both +# GCC/Clang's default (export every symbol with external linkage) and +# WINDOWS_EXPORT_ALL_SYMBOLS. Narrower, not quieter: see the top-level +# README's "Why do std::string/std::vector symbols show up as +# PATCHED?" section for the one specific kind of noise this +# deliberately does NOT remove, and why removing it would need +# excluding all weak-linkage symbols by default -- which would just as +# happily hide a hot-reloadable template of your OWN. +# +# What this DOES remove: your own internal helpers you never meant to +# expose, and (on GCC/Clang, if the target statically links its C++ +# runtime) incidental runtime-library internals -- both real, +# observed cases from building this library and its examples. +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 -- WINDOWS_EXPORT_ALL_SYMBOLS + # is what opts OUT of that; simply not setting it (and not adding + # individual __declspec(dllexport)/HOT_RELOAD_EXPORT other than where + # you want them) is this function's Windows equivalent. Nothing to + # apply here, but calling this on every platform your target builds + # for, unconditionally, is still the point -- see hot_reload_enable_ + # patchable_functions() immediately above for the same pattern. + endif() +endfunction() diff --git a/scripts/build.bat b/scripts/build.bat index 59814b9..255e1e0 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -2,10 +2,18 @@ REM Configure (if needed) and build the project, preferably with Ninja. REM REM Usage (from the project root): -REM scripts\build.bat [BUILD_TYPE] +REM scripts\build.bat [BUILD_TYPE] [extra cmake -D args...] REM REM BUILD_TYPE Debug^|Release^|RelWithDebInfo^|MinSizeRel (default: Release) REM +REM Anything after BUILD_TYPE is passed straight through to cmake's +REM configure step (up to 8 extra arguments -- %2 through %9, batch's own +REM positional-parameter limit), e.g.: +REM scripts\build.bat Debug -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON +REM CMake caches -D options across reconfigures, so passing them once and +REM then calling scripts\build.bat plain afterward for incremental builds +REM keeps them set -- no need to repeat them on every call. +REM REM Set BUILD_DIR to build somewhere other than out\build (see Configuration.cmake). setlocal enabledelayedexpansion @@ -15,6 +23,7 @@ if "%BUILD_DIR%"=="" set "BUILD_DIR=out\build" set "BUILD_TYPE=%~1" if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release" if "%TOOLCACHE_DIR%"=="" set "TOOLCACHE_DIR=.cache\tools" +set "EXTRA_CMAKE_ARGS=%2 %3 %4 %5 %6 %7 %8 %9" where cmake >nul 2>nul if errorlevel 1 ( @@ -60,7 +69,7 @@ set "GENERATOR_ARGS=" :configure echo ==^> Configuring (%BUILD_TYPE%) into %BUILD_DIR%\ -cmake %GENERATOR_ARGS% -S . -B "%BUILD_DIR%" -DCMAKE_BUILD_TYPE=%BUILD_TYPE% +cmake %GENERATOR_ARGS% -S . -B "%BUILD_DIR%" -DCMAKE_BUILD_TYPE=%BUILD_TYPE% %EXTRA_CMAKE_ARGS% if errorlevel 1 exit /b 1 echo ==^> Building diff --git a/scripts/build.sh b/scripts/build.sh index 31dc6c4..9f9aef2 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,10 +2,17 @@ # Configure (if needed) and build the project, preferably with Ninja. # # Usage (from the project root): -# scripts/build.sh [BUILD_TYPE] +# scripts/build.sh [BUILD_TYPE] [extra cmake -D args...] # # BUILD_TYPE Debug|Release|RelWithDebInfo|MinSizeRel (default: Release) # +# Anything after BUILD_TYPE is passed straight through to cmake's +# configure step, e.g.: +# scripts/build.sh Debug -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON +# CMake caches -D options across reconfigures, so passing them once and +# then calling `scripts/build.sh` plain afterward for incremental builds +# keeps them set -- no need to repeat them on every call. +# # Set BUILD_DIR to build somewhere other than out/build (see Configuration.cmake). set -euo pipefail shopt -s nullglob @@ -15,6 +22,10 @@ cd "$SCRIPT_DIR/.." BUILD_DIR="${BUILD_DIR:-out/build}" BUILD_TYPE="${1:-Release}" +if [ "$#" -gt 0 ]; then + shift +fi +EXTRA_CMAKE_ARGS=("$@") command -v cmake >/dev/null 2>&1 || { echo "error: cmake not found on PATH." >&2; exit 1; } @@ -51,7 +62,7 @@ else fi echo "==> Configuring (${BUILD_TYPE}) into ${BUILD_DIR}/" -cmake "${GENERATOR_ARGS[@]}" -S . -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" +cmake "${GENERATOR_ARGS[@]}" -S . -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" "${EXTRA_CMAKE_ARGS[@]}" echo "==> Building" cmake --build "${BUILD_DIR}" diff --git a/scripts/docs.bat b/scripts/docs.bat index 3fc6d9a..8d75211 100644 --- a/scripts/docs.bat +++ b/scripts/docs.bat @@ -11,7 +11,7 @@ REM place either way -- this only affects the message below). REM REM Usage (from the project root): REM scripts\docs.bat REM every module -REM scripts\docs.bat core_lib REM just one module's docs (docs_) +REM scripts\docs.bat hotreload_lib REM just one module's docs (docs_) setlocal cd /d "%~dp0.." diff --git a/scripts/docs.sh b/scripts/docs.sh index a8fa03d..4f9c0e4 100644 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -11,7 +11,7 @@ # # Usage (from the project root): # scripts/docs.sh # every module -# scripts/docs.sh core_lib # just one module's docs (docs_) +# scripts/docs.sh hotreload_lib # just one module's docs (docs_) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/install.bat b/scripts/install.bat index a4e5cc5..396ec3b 100644 --- a/scripts/install.bat +++ b/scripts/install.bat @@ -18,7 +18,7 @@ cd /d "%~dp0.." if "%BUILD_DIR%"=="" set "BUILD_DIR=out\build" set "BUILD_TYPE=%~1" if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release" -set "COMPONENT=demo" +set "COMPONENT=hotreload" if not exist "%BUILD_DIR%" ( echo error: "%BUILD_DIR%" not found. Run scripts\build.bat first. diff --git a/scripts/install.sh b/scripts/install.sh index 66078bb..0efd86f 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -21,7 +21,7 @@ cd "$SCRIPT_DIR/.." BUILD_DIR="${BUILD_DIR:-out/build}" BUILD_TYPE="${1:-Release}" -COMPONENT="demo" # must match PROJECT_CODE_NAME in CMakeLists.txt +COMPONENT="hotreload" # must match PROJECT_CODE_NAME in CMakeLists.txt if [ ! -d "$BUILD_DIR" ]; then echo "error: '${BUILD_DIR}' not found. Run scripts/build.sh first." >&2 diff --git a/scripts/refactor.bat b/scripts/refactor.bat index 8631191..d5825d4 100644 --- a/scripts/refactor.bat +++ b/scripts/refactor.bat @@ -11,10 +11,10 @@ REM entire source tree the way scanning the whole repo from "." would. REM REM Usage, from the project root: scripts\refactor.bat [optional path] REM scripts\refactor.bat every file under source\ and c-api\ -REM scripts\refactor.bat source\Core just one file or directory -- taken -REM as given, with no exclusion filter, -REM so pick a path that doesn't overlap -REM with out\/.cache\/vendor\/.git\ +REM scripts\refactor.bat source\HotReload just one file or directory -- +REM taken as given, with no exclusion +REM filter, so pick a path that doesn't +REM overlap with out\/.cache\/.git\ setlocal enabledelayedexpansion cd /d "%~dp0.." diff --git a/scripts/refactor.sh b/scripts/refactor.sh index a42f69b..6c1261f 100644 --- a/scripts/refactor.sh +++ b/scripts/refactor.sh @@ -14,7 +14,7 @@ # # Usage (from the project root): # scripts/refactor.sh # every file under source/ and c-api/ -# scripts/refactor.sh source/Core # just one file or directory +# scripts/refactor.sh source/HotReload # just one file or directory set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/run.bat b/scripts/run.bat index c366b43..c73008d 100644 --- a/scripts/run.bat +++ b/scripts/run.bat @@ -8,8 +8,8 @@ REM Usage (from the project root): REM scripts\run.bat [-- program-args...] REM REM Examples: -REM scripts\run.bat hello_exe -REM scripts\run.bat farewell_exe -- Ale +REM scripts\run.bat hotreload_demo_host +REM scripts\run.bat hotreload_live_demo_host setlocal enabledelayedexpansion cd /d "%~dp0.." diff --git a/scripts/run.sh b/scripts/run.sh index 8a94113..2173c08 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -8,8 +8,8 @@ # scripts/run.sh [-- program-args...] # # Examples: -# scripts/run.sh hello_exe -# scripts/run.sh farewell_exe -- Ale +# scripts/run.sh hotreload_demo_host +# scripts/run.sh hotreload_live_demo_host set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/source/HotReload/README.md b/source/HotReload/README.md index 7188b0b..c762d69 100644 --- a/source/HotReload/README.md +++ b/source/HotReload/README.md @@ -195,6 +195,57 @@ holds on Linux/macOS as tested here; it's worth double-checking on Windows if you link the CRT in an unusual way (e.g. mismatched static/dynamic CRT between the host and a plugin). +## Why do std::string/std::vector symbols show up as PATCHED? + +Because they're real, addressable functions too, and classification +doesn't -- can't -- know that a symbol "belongs to the standard library" +as opposed to your own code. Confirmed directly: any plugin that uses +`std::string`/`std::vector` internally will show template +instantiations like `std::vector::_M_realloc_insert<...>` getting +`HOT_RELOAD_SYMBOL_PATCHED` on every single reload, even one that +changed nothing about how you use them -- because these are genuinely +present, genuinely exported (with default visibility, by default, on +every build tested here), and genuinely at a new address every time, +since that's just true of everything in a freshly-loaded `.so`/`.dll`. + +**This is safe.** It's the exact same template instantiation in both +builds -- same mangled name, same object layout, same ABI, and (using +the same compiler and standard library on both sides, which you already +need for mangled names to match at all -- see above) functionally +equivalent code. Redirecting old calls to the new copy changes where the +logic runs, never what it does. + +**It is not fixable by hiding symbols, and that's not just an +unimplemented feature.** The obvious fix -- compile with +`-fvisibility=hidden -fvisibility-inlines-hidden` -- does not work here, +confirmed directly: `libstdc++`'s headers mark these template +instantiations `default` visibility explicitly, in the header itself, +specifically so multiple `.so`s can share type identity for RTTI and +exception handling across shared-library boundaries. That annotation +wins over your translation unit's own `-fvisibility=hidden` default, +on purpose, and this library has no way to override it that wouldn't +also risk breaking that cross-DSO guarantee for anyone who needs it. + +**A blanket "don't patch weak-linkage symbols" rule would be the wrong +fix even if it worked**, which is why this library doesn't do that +either: weak/COMDAT linkage is also exactly how a *hot-reloadable +template of your own* would be classified. A rule that reliably filters +out the standard library's internals would just as reliably filter out +your own reloadable generic code. + +**What actually helps, if the log's noise level bothers you, is +narrowing your plugin's export surface** with +`hot_reload_restrict_exports()` (`cmake/HotReload.cmake`) and +`HOT_RELOAD_EXPORT` (`hot_reload.h`) -- see "Using it in your own +module" below. This doesn't remove the standard-library instantiations +specifically (see above), but it does stop your own internal helpers, +and (on GCC/Clang, if a target statically links its C++ runtime) +incidental runtime-library internals, from becoming part of the diff at +all -- both real cases observed while building this library's own +examples. Beyond that, filtering the event log for *display* is a +one-line addition in your own `on_symbol_event` callback -- e.g. skip +anything whose `hot_reload_demangle()`'d name starts with `"std::"`. + ## Scope: what's actually implemented and checked | | x86 | x86-64 | ARM64 | ARM32 | @@ -364,9 +415,11 @@ fix this in any shared library, hot-reloaded or not. ## Using it in your own module -1. Every function you want reloadable needs C linkage -- - `extern "C"` -- for the same reason any `dlopen()`'d interface - needs it (see `hot_reload.h`'s file comment and style guide ยง11). +1. `extern "C"` isn't required -- a real C++ mangled name works too + (see "Hot reloading C++ symbols" above) -- but it's still the + simplest choice for anything you want to look up by a plain, + human-typed name, and the only way to get a name stable across + compilers. 2. Build it as its own `SHARED` library target and call `hot_reload_enable_patchable_functions()` (`cmake/HotReload.cmake`) on it, so every exported function has @@ -374,16 +427,30 @@ fix this in any shared library, hot-reloaded or not. any function whose compiled body happens to be large enough on its own -- functions that aren't get skipped (`HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL`), not corrupted. -3. On Windows, also set `WINDOWS_EXPORT_ALL_SYMBOLS ON` on that - target, or add explicit `__declspec(dllexport)` to each function - yourself -- MSVC exports nothing by default. (Prefer explicit - per-symbol export in a real project, even though the demo uses the - blanket property to match this repo's existing `add_lib_module()` - convention: exporting everything means anything your build happens - to pull in -- including, as this project's own Windows testing - turned up, incidental runtime-library internals -- becomes part of - your reloadable surface too, redirected right along with the - functions you actually meant.) +3. Decide how much of the target's export surface you want to be + deliberate about: + - **Simplest**: do nothing else. On GCC/Clang/MinGW this exports + every symbol with external linkage by default; on MSVC, also set + `WINDOWS_EXPORT_ALL_SYMBOLS ON` to match (MSVC exports nothing by + default otherwise). `source/examples/HotReloadDemo`'s plugins use + this style. + - **Recommended for a real project**: call + `hot_reload_restrict_exports()` (`cmake/HotReload.cmake`) + instead, and mark each intended entry point with `HOT_RELOAD_EXPORT` + (`hot_reload.h`) -- `extern "C" HOT_RELOAD_EXPORT int my_function(...)` + for a plain-name free function (that exact order -- GCC/Clang want + the attribute after `extern "C"`), or just `HOT_RELOAD_EXPORT` alone + on a C++ class/method you want exported with its real mangled name + intact. This keeps anything you didn't explicitly mark -- your own + internal helpers, or (on GCC/Clang, if the target statically links + its C++ runtime) incidental runtime-library internals -- out of the + diff entirely. It does *not* narrow out `std::string`/`std::vector` + template instantiations specifically; see "Why do std::string/ + std::vector symbols show up as PATCHED?" above for why not, and why + that's fine either way. `source/examples/HotReloadLiveDemo`'s + plugin uses this style -- compare the two examples' exported symbol + lists (`nm -D --defined-only | c++filt`) to see the + difference directly. 4. Have your host `hot_reload_load()` it once at startup and `hot_reload_reload()` it after every rebuild, from a point where nothing else is calling into it (see "What this cannot detect" diff --git a/source/HotReload/include/hot_reload/hot_reload.h b/source/HotReload/include/hot_reload/hot_reload.h index 6c42ca4..5ad41de 100644 --- a/source/HotReload/include/hot_reload/hot_reload.h +++ b/source/HotReload/include/hot_reload/hot_reload.h @@ -90,6 +90,29 @@ #endif #endif +/* Marks a single declaration for export under + * hot_reload_restrict_exports() (cmake/HotReload.cmake) without + * otherwise changing its linkage: pair with extern "C" yourself for a + * free function you want a stable, human-typable name for (see this + * file's own comment on when you'd want that) -- extern "C" first, + * then this macro, then the return type, e.g. + * `extern "C" HOT_RELOAD_EXPORT int my_function(...)`, which is the + * one ordering GCC/Clang accept without a warning here. Leave the + * extern "C" off to keep a C++ class or method's real mangled name, + * e.g. a virtual method a host only ever reaches through a vtable + * slot (see the top-level README's "Hot reloading C++ symbols" + * section). + * + * hot_reload_restrict_exports() is a narrower export surface, not a + * quieter one: see the README's "Why do std::string/std::vector + * symbols show up as PATCHED?" section for why it does not, and + * structurally cannot, suppress those specifically. */ +#ifdef _WIN32 + #define HOT_RELOAD_EXPORT __declspec(dllexport) +#else + #define HOT_RELOAD_EXPORT __attribute__((visibility("default"))) +#endif + #ifdef __cplusplus extern "C" { diff --git a/source/examples/HotReloadLiveDemo/Plugin/CMakeLists.txt b/source/examples/HotReloadLiveDemo/Plugin/CMakeLists.txt index 32c56e5..f0b5713 100644 --- a/source/examples/HotReloadLiveDemo/Plugin/CMakeLists.txt +++ b/source/examples/HotReloadLiveDemo/Plugin/CMakeLists.txt @@ -17,12 +17,22 @@ endif() set_target_properties(hotreload_live_plugin PROPERTIES OUTPUT_NAME "hotreload_live_plugin" - WINDOWS_EXPORT_ALL_SYMBOLS ON) + WINDOWS_EXPORT_ALL_SYMBOLS OFF) +target_include_directories(hotreload_live_plugin PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../HotReload/include) _set_output_dirs(hotreload_live_plugin "${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/examples" "${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}/examples") _apply_compile_options(hotreload_live_plugin) hot_reload_enable_patchable_functions(hotreload_live_plugin) +# Demonstrates the narrower-export pattern (see hot_reload.h's +# HOT_RELOAD_EXPORT and this function's own comment in +# cmake/HotReload.cmake) -- contrast with HotReloadDemo's PluginV1/ +# PluginV2, which deliberately leave the GCC/Clang/MinGW default +# (export everything) in place to show that style too. Both are +# correct; this is the one this project would actually recommend. +hot_reload_restrict_exports(hotreload_live_plugin) + _register_module_target(EXAMPLE hotreload_live_plugin) add_dependencies(all_examples hotreload_live_plugin) diff --git a/source/examples/HotReloadLiveDemo/Plugin/src/LivePlugin.cpp b/source/examples/HotReloadLiveDemo/Plugin/src/LivePlugin.cpp index fbbef34..55c5204 100644 --- a/source/examples/HotReloadLiveDemo/Plugin/src/LivePlugin.cpp +++ b/source/examples/HotReloadLiveDemo/Plugin/src/LivePlugin.cpp @@ -39,9 +39,11 @@ * never patched, exactly like plugin_build_marker in * source/examples/HotReloadDemo. */ +#include "hot_reload/hot_reload.h" + #include -extern "C" int live_tick(int frame) +extern "C" HOT_RELOAD_EXPORT int live_tick(int frame) { int multiplier = 2; // <- try changing this int value = frame * multiplier;