CMake template clenaups and script adaptations
This commit is contained in:
1 parent
3087fcea8a
commit
9248925b77
19 files changed
+360
-169
No files matched your search
+5
-6
@@ -2,16 +2,15 @@
|
|||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
project(
|
project(
|
||||||
DemoApp
|
HotReload
|
||||||
VERSION 0.1.0
|
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
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
# Machine-friendly identifier baked into every target as PROJECT_NAME_CODE
|
# Machine-friendly identifier baked into every target as PROJECT_NAME_CODE
|
||||||
# (used by BuildHelpers.cmake's _finalize_module_target). Edit this
|
# (used by BuildHelpers.cmake's _finalize_module_target).
|
||||||
# alongside the project() call above when retargeting this template.
|
set(PROJECT_CODE_NAME "hotreload")
|
||||||
set(PROJECT_CODE_NAME "demo")
|
|
||||||
|
|
||||||
# All output-path configuration (out/, install/, dist/, docs/, and the
|
# All output-path configuration (out/, install/, dist/, docs/, and the
|
||||||
# bin/lib subdirectory names) lives in Configuration.cmake -- included
|
# 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
|
# Third-party dependencies -- see cmake/Dependencies.cmake for
|
||||||
# add_vendor_header_only / add_vendor_subdirectory / add_dependency.
|
# add_vendor_header_only / add_vendor_subdirectory / add_dependency.
|
||||||
# A module declares its own dependencies in its own CMakeLists.txt (see
|
# 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")
|
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/Dependencies.cmake")
|
||||||
|
|
||||||
|
|||||||
@@ -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 cross-platform, cross-compiler C/C++ library that patches a running
|
||||||
a tiny, self-contained `CMakeLists.txt`, the root `CMakeLists.txt`
|
process's own code in place, so a new build of a function takes effect
|
||||||
discovers modules automatically, three ways to bring in third-party code
|
immediately, without restarting -- see
|
||||||
(vendored, installed, or fetched) stay out of your warnings and your
|
[`source/HotReload/README.md`](source/HotReload/README.md) for what it
|
||||||
compile database, every module gets its own browsable documentation via a
|
does, what it guarantees, and its honest limitations. This file covers
|
||||||
pinned Doxygen that's entirely opt-in (never required just to build),
|
the project around it: the CMake module system, its tooling, and how
|
||||||
Ninja itself is pinned and fetched the same way with a graceful fallback
|
the examples and tests fit together.
|
||||||
if it's unavailable, and every output path is configured in one place.
|
|
||||||
|
```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
|
## 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/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/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/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
|
cmake/HotReload.cmake hot_reload_enable_patchable_functions() / hot_reload_restrict_exports()
|
||||||
source/Core/CMakeLists.txt library module "core_lib" (SHARED), depends on fmt
|
source/HotReload/ the library -- library module "hotreload_lib" (STATIC)
|
||||||
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
|
|
||||||
source/examples/HotReloadDemo/ worked example: a host + two builds of one hot-reloadable plugin
|
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
|
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)
|
source/tests/HotReloadTests/ the automated test suite
|
||||||
vendor/tiny_case/ vendored dependency with its own CMakeLists.txt
|
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/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/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
|
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)
|
.clang-format, .clang-tidy C++ style guide tooling (see "Code style & tooling" below)
|
||||||
.editorconfig, .gitattributes -- same
|
.editorconfig, .gitattributes -- same
|
||||||
.gitignore Visual Studio template + a block auto-synced from Configuration.cmake
|
.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
|
c-api/.clang-tidy naming override template for a C API subtree -- applied for real at
|
||||||
real at source/HotReload/src/c-api/ and source/HotReload/include/hot_reload/
|
source/HotReload/src/c-api/ and source/HotReload/include/hot_reload/
|
||||||
.githooks/pre-commit formatting/lint pre-commit hook
|
.githooks/pre-commit formatting/lint pre-commit hook
|
||||||
cpp-style-guide.md, TOOLING.md the style guide itself + tooling docs
|
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"
|
packaging output -- lives under one `out/` directory (see "Output paths"
|
||||||
below); nothing under `out/` is source-controlled.
|
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
|
```cmake
|
||||||
# source/Core/CMakeLists.txt
|
# source/HotReload/CMakeLists.txt
|
||||||
add_dependency(fmt
|
add_lib_module(hotreload_lib TYPE STATIC OUTPUT_NAME "hotreload")
|
||||||
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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A module declares its own dependencies in its own `CMakeLists.txt` -- same
|
A module declares its own dependencies in its own `CMakeLists.txt` --
|
||||||
"minimal tool intervention" principle as module registration: nothing to
|
same "minimal tool intervention" principle as module registration:
|
||||||
add in the root file.
|
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`)
|
## 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`)
|
## 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 <dir>])` | A buildless header-only drop under `vendor/<NAME>/include` | `vendor/tiny_ansi/` |
|
| `add_vendor_header_only(NAME [INCLUDE_DIR <dir>])` | A buildless header-only drop under `vendor/<NAME>/include` | `add_vendor_header_only(some_header_lib)` |
|
||||||
| `add_vendor_subdirectory(SUBDIR)` | A vendored dependency with its own `CMakeLists.txt`, under `vendor/<SUBDIR>` | `vendor/tiny_case/` |
|
| `add_vendor_subdirectory(SUBDIR)` | A vendored dependency with its own `CMakeLists.txt`, under `vendor/<SUBDIR>` | `add_vendor_subdirectory(some_vendored_lib)` |
|
||||||
| `add_dependency(NAME <FetchContent_Declare args>)` | An installed package (`FIND_PACKAGE_ARGS ...`) with a fetch-from-source fallback | `fmt` in `source/Core/` |
|
| `add_dependency(NAME <FetchContent_Declare args>)` | 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
|
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
|
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
|
found. Omit `FIND_PACKAGE_ARGS` to always fetch. Requires network access
|
||||||
the first time a dependency isn't already installed.
|
the first time a dependency isn't already installed.
|
||||||
|
|
||||||
A module `DEPENDS` on whatever target the chosen path defines (`fmt::fmt`,
|
A module `DEPENDS` on whatever target the chosen path defines exactly
|
||||||
`tiny_case`, `tiny_ansi`, ...) exactly like depending on another module --
|
like depending on another module -- `add_lib_module`/`add_exe_module`
|
||||||
`add_lib_module`/`add_exe_module` don't need to know or care which of the
|
don't need to know or care which of the three supplied it.
|
||||||
three supplied it.
|
|
||||||
|
|
||||||
## Documentation (automatic per module, pinned Doxygen, entirely opt-in)
|
## Documentation (automatic per module, pinned Doxygen, entirely opt-in)
|
||||||
|
|
||||||
@@ -191,11 +213,11 @@ Once set up:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/docs.sh # build docs for every module, prints where to open it
|
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_<target>)
|
scripts/docs.sh hotreload_lib # build docs for just one module (docs_<target>)
|
||||||
```
|
```
|
||||||
```bat
|
```bat
|
||||||
scripts\docs.bat
|
scripts\docs.bat
|
||||||
scripts\docs.bat core_lib
|
scripts\docs.bat hotreload_lib
|
||||||
```
|
```
|
||||||
|
|
||||||
Output goes to `out/docs/<module>/html/index.html` (a sibling of
|
Output goes to `out/docs/<module>/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.
|
it's opt-in, the same way tests and examples are.
|
||||||
|
|
||||||
**Writing docs for a module** is just normal Doxygen comments in that
|
**Writing docs for a module** is just normal Doxygen comments in that
|
||||||
module's headers -- e.g. `source/Core/include/core/Greeting.hpp`:
|
module's headers -- e.g. `source/HotReload/include/hot_reload/hot_reload.h`,
|
||||||
|
which is already written this way throughout.
|
||||||
```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);
|
|
||||||
```
|
|
||||||
|
|
||||||
Even with zero comments, a module's docs are still generated and useful --
|
Even with zero comments, a module's docs are still generated and useful --
|
||||||
file lists, function/class signatures, and (if [Graphviz](https://graphviz.org/)
|
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
|
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
|
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
|
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:
|
**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
|
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
|
## Code style & tooling
|
||||||
|
|
||||||
This project follows [`cpp-style-guide.md`](cpp-style-guide.md) (tabs,
|
This project follows [`cpp-style-guide.md`](cpp-style-guide.md) (tabs,
|
||||||
Allman braces, `PascalCase` file names, `camelCase` functions/variables,
|
Allman braces, `PascalCase` file names, `camelCase` functions/variables,
|
||||||
`m_`/`s_`/`g_` member/static/global prefixes -- see the file itself for
|
`m_`/`s_`/`g_` member/static/global prefixes, plus a distinct Part II
|
||||||
the full rules and rationale), enforced by the tooling described in
|
style -- `snake_case`, library-prefixed names -- for any C API boundary;
|
||||||
[`TOOLING.md`](TOOLING.md):
|
see the file itself for the full rules and rationale), enforced by the
|
||||||
|
tooling described in [`TOOLING.md`](TOOLING.md):
|
||||||
|
|
||||||
| File | Enforces |
|
| File | Enforces |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `.clang-format` | All formatting: tabs, Allman braces, pointer alignment, include order, line length |
|
| `.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 |
|
| `.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 |
|
| `.editorconfig` | Baseline tabs/whitespace for editors that don't run clang-format live |
|
||||||
| `.gitattributes` | Forces LF line endings for source files on every OS |
|
| `.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 |
|
| `.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 |
|
||||||
@@ -287,33 +307,47 @@ the full rules and rationale), enforced by the tooling described in
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/refactor.sh # every file under source/ and c-api/ by default
|
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 source/HotReload # just one file or directory
|
||||||
```
|
```
|
||||||
```bat
|
```bat
|
||||||
scripts\refactor.bat
|
scripts\refactor.bat
|
||||||
scripts\refactor.bat source\Core
|
scripts\refactor.bat source\HotReload
|
||||||
```
|
```
|
||||||
|
|
||||||
Runs `clang-format -i` (always) and `clang-tidy --fix` (if
|
Runs `clang-format -i` (always) and `clang-tidy --fix` (if
|
||||||
`compile_commands.json` exists -- build once first if it doesn't) across
|
`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
|
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
|
it" counterpart -- review the result with `git diff` afterward, same as
|
||||||
any auto-formatter. This is exactly how this project's own code
|
any auto-formatter.
|
||||||
(`source/`) was brought into compliance when the toolkit was integrated --
|
|
||||||
see the renames noted further down.
|
**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/`,
|
With no argument, only `source/` and `c-api/` are ever scanned -- `out/`,
|
||||||
`.cache/`, `vendor/`, and `.git/` are never walked, so this can't
|
`.cache/`, and `.git/` are never walked, so this can't accidentally spend
|
||||||
accidentally spend minutes reformatting/analyzing a fetched dependency's
|
minutes reformatting/analyzing a fetched dependency's entire source tree
|
||||||
entire source tree (verified against `out/build/_deps/fmt-src/`
|
(this project doesn't currently have one under `out/build/_deps/`, but the
|
||||||
specifically, since that's what triggered this exact problem before the
|
exclusion is unconditional, not conditional on one existing). Passing an
|
||||||
fix). Passing an explicit scope takes it as given: `refactor.sh` still
|
explicit scope takes it as given: `refactor.sh` still excludes
|
||||||
excludes `vendor`/`out`/`.cache`/`.git` defensively if your scope happens
|
`out`/`.cache`/`.git` defensively if your scope happens to overlap with
|
||||||
to overlap with them; `refactor.bat` does not (its Windows batch
|
them; `refactor.bat` does not (its Windows batch equivalent of that filter
|
||||||
equivalent of that filter turned out to be unreliable to verify, so it
|
turned out to be unreliable to verify, so it was removed rather than
|
||||||
was removed rather than shipped uncertain -- pick a scope that doesn't
|
shipped uncertain -- pick a scope that doesn't overlap with those
|
||||||
overlap with those directories, which is the normal case anyway, e.g.
|
directories, which is the normal case anyway, e.g. `source\HotReload`).
|
||||||
`source\Core`).
|
|
||||||
|
|
||||||
**One-time setup per clone** to activate the hook -- `scripts/setup.sh` /
|
**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
|
`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
|
build (see above), so as long as you've built at least once, the hook's
|
||||||
clang-tidy check works with no extra setup.
|
clang-tidy check works with no extra setup.
|
||||||
|
|
||||||
Two adjustments made when integrating the toolkit into this specific
|
**This project genuinely has a C API subtree**, unlike the generic
|
||||||
project (not part of the toolkit as provided -- see `TOOLING.md` in this
|
template this was adapted from: `source/HotReload/include/hot_reload/`
|
||||||
repo for the toolkit's own documentation of everything else):
|
(the public header) and `source/HotReload/src/c-api/` (its `extern "C"`
|
||||||
|
implementation) each carry a copy of `c-api/.clang-tidy`, positioned at
|
||||||
- **`vendor/` is excluded** from the pre-commit hook's staged-file check
|
the root of the subtree they apply to -- `clang-tidy` always uses the
|
||||||
(`git diff ... ':!vendor/*'`). Vendored code (`cmake/Dependencies.cmake`)
|
closest `.clang-tidy` file up the directory tree, so files under either
|
||||||
isn't this project's code, so it isn't held to this style guide -- same
|
directory pick up `snake_case` naming instead of the rest of this
|
||||||
reasoning as its `compile_commands.json` and warning-flag exclusions
|
project's `PascalCase`/`camelCase`, while `source/HotReload/src/`'s own
|
||||||
elsewhere in this project.
|
Part I (C++) files, one level up, keep inheriting this repo-root
|
||||||
- **`.clang-tidy` gained one option**, `LocalConstantCase: camelBack`. The
|
`.clang-tidy`'s rules exactly as normal. See either copy's own header
|
||||||
style guide's own text says only *global/static* `const`/`constexpr`
|
comment for the full explanation of how clang-tidy's directory-scoped
|
||||||
values are "constants" for naming purposes -- everything else stays
|
config resolution makes that work.
|
||||||
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.
|
|
||||||
|
|
||||||
## How module discovery works
|
## 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
|
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
|
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
|
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
|
module discovered later in this list, or reference one via
|
||||||
resolves target names across the whole project at generate time --
|
`add_dependencies()`/a generator expression before that other module's
|
||||||
verified, not assumed).
|
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
|
## Requirements
|
||||||
|
|
||||||
CMake >= 3.25, a C++20 compiler (GCC/Clang/MSVC), `git` (for
|
CMake >= 3.25, a C++20 compiler (GCC/Clang/MSVC). **No network access is
|
||||||
FetchContent's `GIT_REPOSITORY`-based dependencies like `fmt`), and
|
needed to build this project** -- every module here is self-contained,
|
||||||
network access the first time you configure (for `fmt`, cached afterward
|
with no `add_dependency()`-fetched third-party library anywhere (see
|
||||||
under `out/build/_deps`). Notably **not** in this list: Ninja and Doxygen
|
"Three ways to bring in a dependency" above). Ninja and Doxygen are both
|
||||||
are both pinned and fetched automatically (see "Setup" below), not system
|
pinned and fetched automatically (see "Setup" below) if you want them,
|
||||||
dependencies, and `scripts/build.sh` warns rather than fails if Ninja
|
not system dependencies, and `scripts/build.sh` warns rather than fails
|
||||||
isn't available at all (see "Build" below).
|
if Ninja isn't available at all (see "Build" below).
|
||||||
[Graphviz](https://graphviz.org/) is optional for Doxygen call graphs, and
|
[Graphviz](https://graphviz.org/) is optional for Doxygen call graphs, and
|
||||||
[clang-format](https://clang.llvm.org/docs/ClangFormat.html)/
|
[clang-format](https://clang.llvm.org/docs/ClangFormat.html)/
|
||||||
[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) are optional for
|
[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)
|
tools rather than part of the build itself. `scripts/setup.sh` (below)
|
||||||
checks for both and prints install guidance if either is missing.
|
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
|
## Setup
|
||||||
|
|
||||||
Optional, and separate from building (see "Requirements", "Build", and
|
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
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/build.sh # Release (default)
|
scripts/build.sh # Release (default), library + tests only if BUILD_TESTS was set before
|
||||||
scripts/build.sh Debug # or Debug / RelWithDebInfo / MinSizeRel
|
scripts/build.sh Release -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON # + the automated tests + both demos
|
||||||
```
|
```
|
||||||
```bat
|
```bat
|
||||||
scripts\build.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:
|
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 --
|
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
|
## Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/run.sh hello_exe
|
scripts/run.sh hotreload_demo_host
|
||||||
scripts/run.sh farewell_exe -- Ale
|
scripts/run.sh hotreload_live_demo_host
|
||||||
scripts/run.sh # no args: lists available targets
|
scripts/run.sh # no args: lists available targets
|
||||||
```
|
```
|
||||||
```bat
|
```bat
|
||||||
scripts\run.bat hello_exe
|
scripts\run.bat hotreload_demo_host
|
||||||
scripts\run.bat farewell_exe -- Ale
|
scripts\run.bat hotreload_live_demo_host
|
||||||
```
|
```
|
||||||
|
|
||||||
`run.sh`/`run.bat` resolve the CMake **target id** (not the binary's
|
`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
|
`OUTPUT_NAME`) to the actual binary via a manifest CMake generates at
|
||||||
configure time, so these scripts never need editing when a module is
|
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
|
## Install
|
||||||
|
|
||||||
@@ -497,14 +537,16 @@ scripts/install.sh
|
|||||||
scripts\install.bat
|
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
|
calling `cmake --install` directly). A vendored/fetched dependency's own
|
||||||
`CMakeLists.txt` usually has `install()` rules of its own with no
|
`CMakeLists.txt` usually has `install()` rules of its own with no
|
||||||
component tag; installing without `--component` would also run those,
|
component tag; installing without `--component` would also run those,
|
||||||
typically dumping files under `CMAKE_INSTALL_PREFIX` (e.g. `/usr/local`).
|
typically dumping files under `CMAKE_INSTALL_PREFIX` (e.g. `/usr/local`).
|
||||||
Tagging this project's own `install(TARGETS ...)` calls with `COMPONENT
|
Tagging this project's own `install(TARGETS ...)` calls with `COMPONENT
|
||||||
demo` and always filtering on it is what keeps `cmake --install` scoped to
|
hotreload` and always filtering on it is what keeps `cmake --install`
|
||||||
just this project's own artifacts.
|
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
|
## Adding a module
|
||||||
|
|
||||||
@@ -517,7 +559,7 @@ just this project's own artifacts.
|
|||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
# source/MyTool/CMakeLists.txt
|
# 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,
|
`scripts/run.sh my_tool_exe` picks it up automatically once it's built,
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ endfunction()
|
|||||||
# add_lib_module(NAME [TYPE STATIC|SHARED] [OUTPUT_NAME name] [DEPENDS ...]
|
# add_lib_module(NAME [TYPE STATIC|SHARED] [OUTPUT_NAME name] [DEPENDS ...]
|
||||||
# [EXTRA_SOURCES ...] [NO_DOCS])
|
# [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:
|
# Sources/headers are globbed relative to that module's own directory:
|
||||||
# src/*.cpp and include/*.h(pp) underneath it. TYPE picks STATIC or SHARED
|
# 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
|
# 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])
|
# 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:
|
# Sources/headers are globbed relative to that module's own directory:
|
||||||
# *.cpp and include/*.h(pp) underneath it.
|
# *.cpp and include/*.h(pp) underneath it.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
# -DDOXYGEN_<ANY_DOXYFILE_TAG>=... any Doxyfile tag can be set this way --
|
# -DDOXYGEN_<ANY_DOXYFILE_TAG>=... any Doxyfile tag can be set this way --
|
||||||
# see https://www.doxygen.nl/manual/config.html
|
# see https://www.doxygen.nl/manual/config.html
|
||||||
# A module can opt out with NO_DOCS, e.g.:
|
# 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>=... any Doxyfile tag can be set this way --
|
# -DDOXYGEN_<ANY_DOXYFILE_TAG>=... any Doxyfile tag can be set this way --
|
||||||
# see https://www.doxygen.nl/manual/config.html
|
# see https://www.doxygen.nl/manual/config.html
|
||||||
# A module can opt out with NO_DOCS, e.g.:
|
# 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)
|
include_guard(GLOBAL)
|
||||||
|
|||||||
@@ -52,3 +52,31 @@ function(hot_reload_enable_patchable_functions TARGET_NAME)
|
|||||||
"see HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL in hot_reload.h.")
|
"see HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL in hot_reload.h.")
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
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()
|
||||||
+11
-2
@@ -2,10 +2,18 @@
|
|||||||
REM Configure (if needed) and build the project, preferably with Ninja.
|
REM Configure (if needed) and build the project, preferably with Ninja.
|
||||||
REM
|
REM
|
||||||
REM Usage (from the project root):
|
REM Usage (from the project root):
|
||||||
REM scripts\build.bat [BUILD_TYPE]
|
REM scripts\build.bat [BUILD_TYPE] [extra cmake -D args...]
|
||||||
REM
|
REM
|
||||||
REM BUILD_TYPE Debug^|Release^|RelWithDebInfo^|MinSizeRel (default: Release)
|
REM BUILD_TYPE Debug^|Release^|RelWithDebInfo^|MinSizeRel (default: Release)
|
||||||
REM
|
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).
|
REM Set BUILD_DIR to build somewhere other than out\build (see Configuration.cmake).
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
@@ -15,6 +23,7 @@ if "%BUILD_DIR%"=="" set "BUILD_DIR=out\build"
|
|||||||
set "BUILD_TYPE=%~1"
|
set "BUILD_TYPE=%~1"
|
||||||
if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release"
|
if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release"
|
||||||
if "%TOOLCACHE_DIR%"=="" set "TOOLCACHE_DIR=.cache\tools"
|
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
|
where cmake >nul 2>nul
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
@@ -60,7 +69,7 @@ set "GENERATOR_ARGS="
|
|||||||
|
|
||||||
:configure
|
:configure
|
||||||
echo ==^> Configuring (%BUILD_TYPE%) into %BUILD_DIR%\
|
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
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
echo ==^> Building
|
echo ==^> Building
|
||||||
|
|||||||
+13
-2
@@ -2,10 +2,17 @@
|
|||||||
# Configure (if needed) and build the project, preferably with Ninja.
|
# Configure (if needed) and build the project, preferably with Ninja.
|
||||||
#
|
#
|
||||||
# Usage (from the project root):
|
# 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)
|
# 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 BUILD_DIR to build somewhere other than out/build (see Configuration.cmake).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
@@ -15,6 +22,10 @@ cd "$SCRIPT_DIR/.."
|
|||||||
|
|
||||||
BUILD_DIR="${BUILD_DIR:-out/build}"
|
BUILD_DIR="${BUILD_DIR:-out/build}"
|
||||||
BUILD_TYPE="${1:-Release}"
|
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; }
|
command -v cmake >/dev/null 2>&1 || { echo "error: cmake not found on PATH." >&2; exit 1; }
|
||||||
|
|
||||||
@@ -51,7 +62,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> Configuring (${BUILD_TYPE}) into ${BUILD_DIR}/"
|
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"
|
echo "==> Building"
|
||||||
cmake --build "${BUILD_DIR}"
|
cmake --build "${BUILD_DIR}"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ REM place either way -- this only affects the message below).
|
|||||||
REM
|
REM
|
||||||
REM Usage (from the project root):
|
REM Usage (from the project root):
|
||||||
REM scripts\docs.bat REM every module
|
REM scripts\docs.bat REM every module
|
||||||
REM scripts\docs.bat core_lib REM just one module's docs (docs_<target>)
|
REM scripts\docs.bat hotreload_lib REM just one module's docs (docs_<target>)
|
||||||
setlocal
|
setlocal
|
||||||
|
|
||||||
cd /d "%~dp0.."
|
cd /d "%~dp0.."
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
#
|
#
|
||||||
# Usage (from the project root):
|
# Usage (from the project root):
|
||||||
# scripts/docs.sh # every module
|
# scripts/docs.sh # every module
|
||||||
# scripts/docs.sh core_lib # just one module's docs (docs_<target>)
|
# scripts/docs.sh hotreload_lib # just one module's docs (docs_<target>)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ cd /d "%~dp0.."
|
|||||||
if "%BUILD_DIR%"=="" set "BUILD_DIR=out\build"
|
if "%BUILD_DIR%"=="" set "BUILD_DIR=out\build"
|
||||||
set "BUILD_TYPE=%~1"
|
set "BUILD_TYPE=%~1"
|
||||||
if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release"
|
if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release"
|
||||||
set "COMPONENT=demo"
|
set "COMPONENT=hotreload"
|
||||||
|
|
||||||
if not exist "%BUILD_DIR%" (
|
if not exist "%BUILD_DIR%" (
|
||||||
echo error: "%BUILD_DIR%" not found. Run scripts\build.bat first.
|
echo error: "%BUILD_DIR%" not found. Run scripts\build.bat first.
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ cd "$SCRIPT_DIR/.."
|
|||||||
|
|
||||||
BUILD_DIR="${BUILD_DIR:-out/build}"
|
BUILD_DIR="${BUILD_DIR:-out/build}"
|
||||||
BUILD_TYPE="${1:-Release}"
|
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
|
if [ ! -d "$BUILD_DIR" ]; then
|
||||||
echo "error: '${BUILD_DIR}' not found. Run scripts/build.sh first." >&2
|
echo "error: '${BUILD_DIR}' not found. Run scripts/build.sh first." >&2
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ REM entire source tree the way scanning the whole repo from "." would.
|
|||||||
REM
|
REM
|
||||||
REM Usage, from the project root: scripts\refactor.bat [optional path]
|
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 every file under source\ and c-api\
|
||||||
REM scripts\refactor.bat source\Core just one file or directory -- taken
|
REM scripts\refactor.bat source\HotReload just one file or directory --
|
||||||
REM as given, with no exclusion filter,
|
REM taken as given, with no exclusion
|
||||||
REM so pick a path that doesn't overlap
|
REM filter, so pick a path that doesn't
|
||||||
REM with out\/.cache\/vendor\/.git\
|
REM overlap with out\/.cache\/.git\
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
cd /d "%~dp0.."
|
cd /d "%~dp0.."
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
# Usage (from the project root):
|
# Usage (from the project root):
|
||||||
# scripts/refactor.sh # every file under source/ and c-api/
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ REM Usage (from the project root):
|
|||||||
REM scripts\run.bat <target-name> [-- program-args...]
|
REM scripts\run.bat <target-name> [-- program-args...]
|
||||||
REM
|
REM
|
||||||
REM Examples:
|
REM Examples:
|
||||||
REM scripts\run.bat hello_exe
|
REM scripts\run.bat hotreload_demo_host
|
||||||
REM scripts\run.bat farewell_exe -- Ale
|
REM scripts\run.bat hotreload_live_demo_host
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
cd /d "%~dp0.."
|
cd /d "%~dp0.."
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@
|
|||||||
# scripts/run.sh <target-name> [-- program-args...]
|
# scripts/run.sh <target-name> [-- program-args...]
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# scripts/run.sh hello_exe
|
# scripts/run.sh hotreload_demo_host
|
||||||
# scripts/run.sh farewell_exe -- Ale
|
# scripts/run.sh hotreload_live_demo_host
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|||||||
+80
-13
@@ -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
|
Windows if you link the CRT in an unusual way (e.g. mismatched
|
||||||
static/dynamic CRT between the host and a plugin).
|
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<T>` internally will show template
|
||||||
|
instantiations like `std::vector<int>::_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
|
## Scope: what's actually implemented and checked
|
||||||
|
|
||||||
| | x86 | x86-64 | ARM64 | ARM32 |
|
| | 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
|
## Using it in your own module
|
||||||
|
|
||||||
1. Every function you want reloadable needs C linkage --
|
1. `extern "C"` isn't required -- a real C++ mangled name works too
|
||||||
`extern "C"` -- for the same reason any `dlopen()`'d interface
|
(see "Hot reloading C++ symbols" above) -- but it's still the
|
||||||
needs it (see `hot_reload.h`'s file comment and style guide §11).
|
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
|
2. Build it as its own `SHARED` library target and call
|
||||||
`hot_reload_enable_patchable_functions(<target>)`
|
`hot_reload_enable_patchable_functions(<target>)`
|
||||||
(`cmake/HotReload.cmake`) on it, so every exported function has
|
(`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
|
any function whose compiled body happens to be large enough on its
|
||||||
own -- functions that aren't get skipped
|
own -- functions that aren't get skipped
|
||||||
(`HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL`), not corrupted.
|
(`HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL`), not corrupted.
|
||||||
3. On Windows, also set `WINDOWS_EXPORT_ALL_SYMBOLS ON` on that
|
3. Decide how much of the target's export surface you want to be
|
||||||
target, or add explicit `__declspec(dllexport)` to each function
|
deliberate about:
|
||||||
yourself -- MSVC exports nothing by default. (Prefer explicit
|
- **Simplest**: do nothing else. On GCC/Clang/MinGW this exports
|
||||||
per-symbol export in a real project, even though the demo uses the
|
every symbol with external linkage by default; on MSVC, also set
|
||||||
blanket property to match this repo's existing `add_lib_module()`
|
`WINDOWS_EXPORT_ALL_SYMBOLS ON` to match (MSVC exports nothing by
|
||||||
convention: exporting everything means anything your build happens
|
default otherwise). `source/examples/HotReloadDemo`'s plugins use
|
||||||
to pull in -- including, as this project's own Windows testing
|
this style.
|
||||||
turned up, incidental runtime-library internals -- becomes part of
|
- **Recommended for a real project**: call
|
||||||
your reloadable surface too, redirected right along with the
|
`hot_reload_restrict_exports(<target>)` (`cmake/HotReload.cmake`)
|
||||||
functions you actually meant.)
|
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 <path> | c++filt`) to see the
|
||||||
|
difference directly.
|
||||||
4. Have your host `hot_reload_load()` it once at startup and
|
4. Have your host `hot_reload_load()` it once at startup and
|
||||||
`hot_reload_reload()` it after every rebuild, from a point where
|
`hot_reload_reload()` it after every rebuild, from a point where
|
||||||
nothing else is calling into it (see "What this cannot detect"
|
nothing else is calling into it (see "What this cannot detect"
|
||||||
|
|||||||
@@ -90,6 +90,29 @@
|
|||||||
#endif
|
#endif
|
||||||
#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
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,12 +17,22 @@ endif()
|
|||||||
|
|
||||||
set_target_properties(hotreload_live_plugin PROPERTIES
|
set_target_properties(hotreload_live_plugin PROPERTIES
|
||||||
OUTPUT_NAME "hotreload_live_plugin"
|
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
|
_set_output_dirs(hotreload_live_plugin
|
||||||
"${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/examples"
|
"${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/examples"
|
||||||
"${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}/examples")
|
"${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}/examples")
|
||||||
_apply_compile_options(hotreload_live_plugin)
|
_apply_compile_options(hotreload_live_plugin)
|
||||||
hot_reload_enable_patchable_functions(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)
|
_register_module_target(EXAMPLE hotreload_live_plugin)
|
||||||
add_dependencies(all_examples hotreload_live_plugin)
|
add_dependencies(all_examples hotreload_live_plugin)
|
||||||
@@ -39,9 +39,11 @@
|
|||||||
* never patched, exactly like plugin_build_marker in
|
* never patched, exactly like plugin_build_marker in
|
||||||
* source/examples/HotReloadDemo.
|
* source/examples/HotReloadDemo.
|
||||||
*/
|
*/
|
||||||
|
#include "hot_reload/hot_reload.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
extern "C" int live_tick(int frame)
|
extern "C" HOT_RELOAD_EXPORT int live_tick(int frame)
|
||||||
{
|
{
|
||||||
int multiplier = 2; // <- try changing this
|
int multiplier = 2; // <- try changing this
|
||||||
int value = frame * multiplier;
|
int value = frame * multiplier;
|
||||||
|
|||||||
Reference in new issue
Block a user