Skip to content

Commit fa171c3

Browse files
author
Justin Nolan
committed
CMake support scripts
1 parent 9f8318f commit fa171c3

6 files changed

Lines changed: 1188 additions & 0 deletions

File tree

cmake/OpenVicCodegen.cmake

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Generated-header support shared by all OpenVic repos, driving gen/codegen.py
2+
# (the standalone port of the SCons git/license/author/memory builders).
3+
4+
include_guard(GLOBAL)
5+
6+
get_filename_component(_openvic_codegen_script "${CMAKE_CURRENT_LIST_DIR}/../gen/codegen.py" ABSOLUTE)
7+
set(OPENVIC_CODEGEN_SCRIPT "${_openvic_codegen_script}" CACHE INTERNAL "Path to the OpenVic codegen CLI")
8+
9+
# commit-info is filled in at configure time with configure_file() from local
10+
# git metadata -- no codegen.py (Python startup) and no `gh release list`
11+
# network call on the build's critical path.
12+
get_filename_component(_openvic_commit_info_template "${CMAKE_CURRENT_LIST_DIR}/../gen/commit_info.gen.hpp.in" ABSOLUTE)
13+
set(OPENVIC_COMMIT_INFO_TEMPLATE "${_openvic_commit_info_template}" CACHE INTERNAL "Path to the OpenVic commit-info header template")
14+
15+
#[[ openvic_generate_commit_info(TARGET <tgt> PREFIX <game|sim|...> REPO_DIR <dir> OUTPUT <path>)
16+
Fills <path> at configure time with configure_file(), substituting
17+
<PREFIX>_TAG/_RELEASE/_COMMIT_HASH/_COMMIT_TIMESTAMP from local git metadata (no
18+
network). RELEASE mirrors TAG unless OPENVIC_RELEASE is set; OPENVIC_TAG likewise
19+
forces TAG. The stamp is captured at configure time only -- it refreshes on the
20+
next reconfigure, not on every build. Release/CI builds should configure fresh
21+
(and typically set OPENVIC_RELEASE/OPENVIC_TAG) before building. TARGET is
22+
accepted for call-site symmetry with the other generators but is unused (the
23+
header exists on disk before the build starts). ]]
24+
function(openvic_generate_commit_info)
25+
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "TARGET;PREFIX;REPO_DIR;OUTPUT" "")
26+
27+
string(TOUPPER "${ARG_PREFIX}" PREFIX_UPPER)
28+
29+
# --- tag ---
30+
if(DEFINED ENV{OPENVIC_TAG})
31+
set(GIT_TAG "$ENV{OPENVIC_TAG}")
32+
else()
33+
execute_process(
34+
COMMAND git describe --tags --abbrev=0
35+
WORKING_DIRECTORY "${ARG_REPO_DIR}"
36+
OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE
37+
ERROR_QUIET RESULT_VARIABLE _res
38+
)
39+
if(NOT _res EQUAL 0 OR GIT_TAG STREQUAL "")
40+
set(GIT_TAG "<tag missing>")
41+
endif()
42+
endif()
43+
44+
# --- release: OPENVIC_RELEASE if set, otherwise the tag (no `gh`/network) ---
45+
if(DEFINED ENV{OPENVIC_RELEASE})
46+
set(GIT_RELEASE "$ENV{OPENVIC_RELEASE}")
47+
else()
48+
set(GIT_RELEASE "${GIT_TAG}")
49+
endif()
50+
51+
# --- commit hash ---
52+
execute_process(
53+
COMMAND git rev-parse HEAD
54+
WORKING_DIRECTORY "${ARG_REPO_DIR}"
55+
OUTPUT_VARIABLE GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE
56+
ERROR_QUIET RESULT_VARIABLE _res
57+
)
58+
if(NOT _res EQUAL 0 OR GIT_HASH STREQUAL "")
59+
set(GIT_HASH "0000000000000000000000000000000000000000")
60+
endif()
61+
62+
# --- commit timestamp (UNIX seconds) ---
63+
execute_process(
64+
COMMAND git log -1 --pretty=format:%ct --no-show-signature
65+
WORKING_DIRECTORY "${ARG_REPO_DIR}"
66+
OUTPUT_VARIABLE GIT_TIMESTAMP OUTPUT_STRIP_TRAILING_WHITESPACE
67+
ERROR_QUIET RESULT_VARIABLE _res
68+
)
69+
if(NOT _res EQUAL 0 OR GIT_TIMESTAMP STREQUAL "")
70+
set(GIT_TIMESTAMP "0")
71+
endif()
72+
73+
configure_file("${OPENVIC_COMMIT_INFO_TEMPLATE}" "${ARG_OUTPUT}" @ONLY NEWLINE_STYLE UNIX)
74+
endfunction()
75+
76+
#[[ openvic_generate_license_info(TARGET <tgt> PREFIX <p> COPYRIGHT <file> LICENSE <file> OUTPUT <path>) ]]
77+
function(openvic_generate_license_info)
78+
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "TARGET;PREFIX;COPYRIGHT;LICENSE;OUTPUT" "")
79+
80+
add_custom_command(
81+
OUTPUT ${ARG_OUTPUT}
82+
COMMAND
83+
${Python3_EXECUTABLE} ${OPENVIC_CODEGEN_SCRIPT} license-info --prefix ${ARG_PREFIX} --copyright
84+
${ARG_COPYRIGHT} --license ${ARG_LICENSE} -o ${ARG_OUTPUT}
85+
DEPENDS ${ARG_COPYRIGHT} ${ARG_LICENSE} ${OPENVIC_CODEGEN_SCRIPT}
86+
COMMENT "Generating ${ARG_PREFIX} license_info.gen.hpp"
87+
VERBATIM
88+
)
89+
target_sources(${ARG_TARGET} PRIVATE ${ARG_OUTPUT})
90+
endfunction()
91+
92+
#[[ openvic_generate_author_info(TARGET <tgt> PREFIX <p> AUTHORS <file> OUTPUT <path> SECTIONS <Heading=MACRO>...) ]]
93+
function(openvic_generate_author_info)
94+
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "TARGET;PREFIX;AUTHORS;OUTPUT" "SECTIONS")
95+
96+
set(section_args "")
97+
foreach(section IN LISTS ARG_SECTIONS)
98+
list(APPEND section_args --section "${section}")
99+
endforeach()
100+
101+
add_custom_command(
102+
OUTPUT ${ARG_OUTPUT}
103+
COMMAND
104+
${Python3_EXECUTABLE} ${OPENVIC_CODEGEN_SCRIPT} author-info --prefix ${ARG_PREFIX} --authors
105+
${ARG_AUTHORS} ${section_args} -o ${ARG_OUTPUT}
106+
DEPENDS ${ARG_AUTHORS} ${OPENVIC_CODEGEN_SCRIPT}
107+
COMMENT "Generating ${ARG_PREFIX} author_info.gen.hpp"
108+
VERBATIM
109+
)
110+
target_sources(${ARG_TARGET} PRIVATE ${ARG_OUTPUT})
111+
endfunction()
112+
113+
#[[ openvic_generate_memory_headers(MEMORY_DIR <foonathan-memory-root> DEFINES <KEY=VALUE>...)
114+
Writes config_impl.hpp and detail/container_node_sizes_impl.hpp into the
115+
foonathan/memory source tree at configure time, byte-identical to the files
116+
the SCons build generates (both build systems share these source-tree files,
117+
and codegen.py leaves them untouched when the content already matches). ]]
118+
function(openvic_generate_memory_headers)
119+
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "MEMORY_DIR" "DEFINES")
120+
121+
set(define_args "")
122+
foreach(define IN LISTS ARG_DEFINES)
123+
list(APPEND define_args --define "${define}")
124+
endforeach()
125+
126+
set(inner_include "${ARG_MEMORY_DIR}/include/foonathan/memory")
127+
execute_process(
128+
COMMAND
129+
${Python3_EXECUTABLE} ${OPENVIC_CODEGEN_SCRIPT} memory-config ${define_args} -o
130+
"${inner_include}/config_impl.hpp"
131+
RESULT_VARIABLE result
132+
)
133+
if(NOT result EQUAL 0)
134+
message(FATAL_ERROR "codegen.py memory-config failed (${result})")
135+
endif()
136+
137+
execute_process(
138+
COMMAND
139+
${Python3_EXECUTABLE} ${OPENVIC_CODEGEN_SCRIPT} memory-node-sizes -o
140+
"${inner_include}/detail/container_node_sizes_impl.hpp"
141+
RESULT_VARIABLE result
142+
)
143+
if(NOT result EQUAL 0)
144+
message(FATAL_ERROR "codegen.py memory-node-sizes failed (${result})")
145+
endif()
146+
endfunction()

cmake/OpenVicDeps.cmake

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Third-party dependency fetching shared by all OpenVic repos.
2+
#
3+
# Deps are fetched as pinned GitHub source tarballs into the build tree's
4+
# `_deps/` dir (via FetchContent) instead of being read from the git submodule
5+
# checkouts. This decouples them from the working tree: switching branches (or
6+
# `git submodule update --force`, rebase, CI checkouts) rewrites submodule file
7+
# mtimes and makes Ninja rebuild byte-identical dep objects; fetched sources
8+
# live outside the working tree, so returning to an already-built pin is a
9+
# no-op. The submodules stay in place for the SCons build, which still reads
10+
# them; CMake simply stops looking at the vendored third-party dirs.
11+
#
12+
# First-party code (openvic-simulation, openvic-dataloader, lexy-vdf, and the
13+
# `scripts` repo itself) is NOT fetched -- it is the code under development and
14+
# must rebuild on branch switches, so it keeps building from the submodule tree
15+
# via add_subdirectory().
16+
17+
include_guard(GLOBAL)
18+
19+
include(FetchContent)
20+
21+
# CMP0168 (CMake >= 3.30): FetchContent populates directly during configure
22+
# with no per-dep sub-build -- a meaningful configure-time win across the ~15
23+
# fetched deps. Guarded so it is a no-op on 3.28/3.29. Set at include (directory)
24+
# scope so it propagates to every repo's MakeAvailable call sites.
25+
if(POLICY CMP0168)
26+
cmake_policy(SET CMP0168 NEW)
27+
endif()
28+
29+
#[[ openvic_declare_dep(<name>
30+
REPO <org/repo> GitHub owner/repo the tarball comes from
31+
SHA <full-commit-sha> commit the tarball is pinned to
32+
SHA256 <hash> tarball URL_HASH (integrity + download-skip)
33+
[SUBMODULE_PATH <path>] gitlink path (relative to REPO_DIR) to check
34+
the pin against; enables the drift warning
35+
[REPO_DIR <dir>] repo the SUBMODULE_PATH lives in
36+
(default: CMAKE_CURRENT_SOURCE_DIR)
37+
[DOWNLOAD_ONLY] populate the source but do NOT add_subdirectory
38+
it (for deps whose CMakeLists we don't use)
39+
[SOURCE_SUBDIR <dir>] add_subdirectory this subdir of the tarball
40+
[SOURCE_DIR <dir>] override the extraction directory
41+
)
42+
43+
Declares a dep with FetchContent. The caller follows with
44+
FetchContent_MakeAvailable(<name>) (after any option-setting the dep needs),
45+
then uses ${<name>_SOURCE_DIR} for DOWNLOAD_ONLY deps.
46+
47+
Fetched as a source tarball rather than a git clone: a full-SHA GIT_TAG cannot
48+
use a shallow clone, so git-based fetches of the larger repos (godot-cpp,
49+
range-v3, spdlog) would pull full history. None of these deps need nested git
50+
submodules at their pinned commit, so tarballs are strictly cheaper.
51+
52+
Local-dev escape hatch: -DFETCHCONTENT_SOURCE_DIR_<UPPER_NAME>=<path> points a
53+
single dep back at a working-tree checkout (this reintroduces mtime coupling
54+
for that one dep -- it is a debugging tool, not the default).
55+
]]
56+
function(openvic_declare_dep name)
57+
cmake_parse_arguments(
58+
PARSE_ARGV 1 ARG
59+
"DOWNLOAD_ONLY"
60+
"REPO;SHA;SHA256;SUBMODULE_PATH;REPO_DIR;SOURCE_SUBDIR;SOURCE_DIR"
61+
""
62+
)
63+
64+
if(NOT ARG_REPO OR NOT ARG_SHA OR NOT ARG_SHA256)
65+
message(FATAL_ERROR "openvic_declare_dep(${name}) requires REPO, SHA and SHA256")
66+
endif()
67+
68+
_openvic_check_dep_pin("${name}" "${ARG_SHA}" "${ARG_SUBMODULE_PATH}" "${ARG_REPO_DIR}")
69+
70+
set(declare_args "")
71+
if(ARG_DOWNLOAD_ONLY)
72+
# SOURCE_SUBDIR pointing at a nonexistent dir makes MakeAvailable
73+
# populate the source but skip add_subdirectory (documented behavior,
74+
# CMake >= 3.28). Used for deps whose upstream CMakeLists we replace
75+
# with a hand-rolled target (or whose CMakeLists pollutes global state).
76+
list(APPEND declare_args SOURCE_SUBDIR "_openvic_do_not_add")
77+
elseif(ARG_SOURCE_SUBDIR)
78+
list(APPEND declare_args SOURCE_SUBDIR "${ARG_SOURCE_SUBDIR}")
79+
endif()
80+
if(ARG_SOURCE_DIR)
81+
list(APPEND declare_args SOURCE_DIR "${ARG_SOURCE_DIR}")
82+
endif()
83+
84+
FetchContent_Declare(
85+
${name}
86+
URL "https://github.com/${ARG_REPO}/archive/${ARG_SHA}.tar.gz"
87+
URL_HASH SHA256=${ARG_SHA256}
88+
SYSTEM
89+
EXCLUDE_FROM_ALL
90+
${declare_args}
91+
)
92+
endfunction()
93+
94+
#[[ Warn loudly when a dep's CMake pin has drifted from the submodule gitlink
95+
(e.g. a dependabot bump moved the submodule but not the pin here), and note
96+
when the same FetchContent name is declared twice with different SHAs (CMake
97+
silently keeps the first -- this is expected for the two vendored lexy copies,
98+
so it is only a STATUS message). Warn-only; tolerant of missing git/submodule. ]]
99+
function(_openvic_check_dep_pin name sha submodule_path repo_dir)
100+
# Duplicate-name detection: record name -> SHA in a global property.
101+
get_property(prev GLOBAL PROPERTY "_openvic_dep_pin_${name}")
102+
if(prev AND NOT prev STREQUAL sha)
103+
message(STATUS "OpenVic dep '${name}' already declared at ${prev}; ignoring later pin ${sha} (first declaration wins)")
104+
endif()
105+
set_property(GLOBAL PROPERTY "_openvic_dep_pin_${name}" "${sha}")
106+
107+
if(NOT submodule_path)
108+
return()
109+
endif()
110+
if(NOT repo_dir)
111+
set(repo_dir "${CMAKE_CURRENT_SOURCE_DIR}")
112+
endif()
113+
114+
find_program(GIT_EXECUTABLE git)
115+
if(NOT GIT_EXECUTABLE)
116+
return()
117+
endif()
118+
119+
execute_process(
120+
COMMAND "${GIT_EXECUTABLE}" -C "${repo_dir}" ls-tree HEAD "${submodule_path}"
121+
OUTPUT_VARIABLE ls_tree
122+
ERROR_QUIET
123+
RESULT_VARIABLE git_result
124+
OUTPUT_STRIP_TRAILING_WHITESPACE
125+
)
126+
if(NOT git_result EQUAL 0 OR ls_tree STREQUAL "")
127+
return()
128+
endif()
129+
130+
# gitlink line: "160000 commit <sha>\t<path>"
131+
if(ls_tree MATCHES "160000 commit ([0-9a-f]+)")
132+
set(gitlink_sha "${CMAKE_MATCH_1}")
133+
if(NOT gitlink_sha STREQUAL sha)
134+
message(
135+
WARNING
136+
"OpenVic dep '${name}' pin drift: CMake fetches ${sha} but submodule "
137+
"'${submodule_path}' points at ${gitlink_sha}. Update the SHA/SHA256 in "
138+
"the openvic_declare_dep() call to match (or the submodule)."
139+
)
140+
endif()
141+
endif()
142+
endfunction()

cmake/OpenVicFlags.cmake

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Compiler/define setup shared by all OpenVic repos.
2+
#
3+
# The Godot build axis reuses godot-cpp's cache variable names so one setting
4+
# drives both godot-cpp and the OpenVic libraries; the declarations below also
5+
# make them available in standalone builds that don't include godot-cpp.
6+
7+
include_guard(GLOBAL)
8+
9+
set(GODOTCPP_TARGET
10+
"template_debug"
11+
CACHE STRING
12+
"Which Godot target to build for: editor, template_debug, template_release"
13+
)
14+
set_property(CACHE GODOTCPP_TARGET PROPERTY STRINGS "template_debug;template_release;editor")
15+
option(GODOTCPP_DEV_BUILD "Developer build with dev-only debugging code (DEV_ENABLED)" OFF)
16+
17+
if(NOT GODOTCPP_TARGET MATCHES "^(editor|template_debug|template_release)$")
18+
message(FATAL_ERROR "GODOTCPP_TARGET must be editor, template_debug or template_release, got '${GODOTCPP_TARGET}'")
19+
endif()
20+
21+
#[[ Directory-scope flags every OpenVic repo builds with. Guarded by an
22+
inherited (non-cache) variable so nested repos in a composed build inherit the
23+
flags from their parent directory without re-adding them. Callers must invoke
24+
this BEFORE add_subdirectory() of any code that should build with these flags
25+
(and the OpenVic root invokes it AFTER add_subdirectory(godot-cpp), which owns
26+
its own flag setup). ]]
27+
macro(openvic_setup_base_flags)
28+
if(NOT OPENVIC_BASE_FLAGS_APPLIED)
29+
set(OPENVIC_BASE_FLAGS_APPLIED TRUE)
30+
31+
# Third-party deps declare old cmake_minimum_required versions; give
32+
# them modern policy behavior (option() and set(CACHE) honor normal
33+
# variables, MSVC runtime via CMAKE_MSVC_RUNTIME_LIBRARY, and accept
34+
# minimums older than CMake 4 supports).
35+
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
36+
set(CMAKE_POLICY_DEFAULT_CMP0091 NEW)
37+
set(CMAKE_POLICY_DEFAULT_CMP0126 NEW)
38+
if(NOT DEFINED CMAKE_POLICY_VERSION_MINIMUM)
39+
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
40+
endif()
41+
42+
set(CMAKE_CXX_STANDARD 20)
43+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
44+
set(CMAKE_CXX_EXTENSIONS OFF)
45+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
46+
47+
# No C++20 modules are used anywhere in the OpenVic sources, so skip the
48+
# per-translation-unit module-import scan CMake adds for C++20 targets
49+
# (CMP0155, on since 3.28). Each scan is a separate preprocessor pass
50+
# per file -- pure overhead here, felt most on incremental builds.
51+
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
52+
53+
if(MSVC)
54+
# /Zc:preprocessor is required: the code uses __VA_OPT__.
55+
add_compile_options(/utf-8 /Zc:preprocessor)
56+
# Godot doesn't use exceptions anywhere; match the SCons builds.
57+
# /EHsc (a CMake default) must go too: it defines __cpp_exceptions,
58+
# which flips exception-detection macros (e.g. vmcontainer's) onto
59+
# code paths the SCons build never compiles.
60+
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
61+
add_compile_definitions(NOMINMAX _HAS_EXCEPTIONS=0)
62+
else()
63+
add_compile_options(-fno-exceptions -fvisibility=hidden)
64+
endif()
65+
66+
if(NOT GODOTCPP_TARGET STREQUAL "template_release")
67+
add_compile_definitions(DEBUG_ENABLED)
68+
endif()
69+
if(GODOTCPP_DEV_BUILD)
70+
add_compile_definitions(DEV_ENABLED)
71+
endif()
72+
endif()
73+
endmacro()
74+
75+
#[[ Directory-scope RTTI disable. The simulation stack (openvic-simulation,
76+
openvic-dataloader, lexy-vdf) builds without RTTI; the extension itself keeps
77+
RTTI on, so the OpenVic root must NOT call this. ]]
78+
macro(openvic_disable_rtti)
79+
if(NOT OPENVIC_RTTI_DISABLED)
80+
set(OPENVIC_RTTI_DISABLED TRUE)
81+
if(MSVC)
82+
add_compile_options(/GR-)
83+
else()
84+
add_compile_options(-fno-rtti)
85+
endif()
86+
endif()
87+
endmacro()

cmake/OpenVicScripts.cmake

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Shared CMake support for OpenVic projects (OpenVic, openvic-simulation,
2+
# openvic-dataloader, lexy-vdf). Each repo vendors this `scripts` repo as a
3+
# submodule and loads this module via:
4+
#
5+
# if(NOT COMMAND openvic_setup_base_flags)
6+
# include("${CMAKE_CURRENT_SOURCE_DIR}/scripts/cmake/OpenVicScripts.cmake")
7+
# endif()
8+
#
9+
# In a composed build (a parent already loaded its own copy) the include is
10+
# skipped, so the first-loaded (outermost) module version wins.
11+
12+
include_guard(GLOBAL)
13+
14+
find_package(Python3 COMPONENTS Interpreter REQUIRED)
15+
16+
include("${CMAKE_CURRENT_LIST_DIR}/OpenVicFlags.cmake")
17+
include("${CMAKE_CURRENT_LIST_DIR}/OpenVicCodegen.cmake")
18+
include("${CMAKE_CURRENT_LIST_DIR}/OpenVicDeps.cmake")

0 commit comments

Comments
 (0)