Skip to content

Commit 81c4dc4

Browse files
authored
Merge pull request #6 from geometryprocessing/cmake-embedding
embedder?
2 parents 0436a4c + 7c83303 commit 81c4dc4

7 files changed

Lines changed: 647 additions & 3 deletions

File tree

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ include(jse_use_colors)
5959
# IPC Toolkit utils
6060
include(jse_prepend_current_path)
6161
include(jse_set_source_group)
62+
include(jse_add_spec)
6263

6364
# Sort projects inside the solution
6465
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
@@ -105,6 +106,11 @@ target_link_libraries(jse PUBLIC nlohmann_json::nlohmann_json)
105106
# Use C++17
106107
target_compile_features(jse PUBLIC cxx_std_17)
107108

109+
add_executable(jse_embed_spec_tool EXCLUDE_FROM_ALL tools/jse_embed_spec.cpp)
110+
target_link_libraries(jse_embed_spec_tool PRIVATE jse::jse)
111+
target_compile_features(jse_embed_spec_tool PRIVATE cxx_std_17)
112+
set_target_properties(jse_embed_spec_tool PROPERTIES FOLDER "JSE")
113+
108114
################################################################################
109115
# Tests
110116
################################################################################

cmake/jse/jse_add_spec.cmake

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# jse_add_spec(<target>
2+
# [ALIAS alias::target]
3+
# [INPUT root_spec.json]
4+
# [OUTPUT generated/spec.hpp]
5+
# [INCLUDE_DIRS include_dir_1 include_dir_2]
6+
# [LINK_SPECS dependency::spec ...]
7+
# [NAMESPACE cxx::namespace]
8+
# [FUNCTION function_name]
9+
# )
10+
#
11+
# Creates or extends a CMake target carrying JSON spec metadata. If INPUT and
12+
# OUTPUT are provided, generated .hpp/.cpp files are added to the target. The
13+
# generated header exposes jse::embed::<target>::<output_stem>::spec() by default.
14+
include_guard(GLOBAL)
15+
16+
function(_jse_resolve_target OUTPUT_VARIABLE TARGET_NAME)
17+
get_target_property(_jse_aliased_target "${TARGET_NAME}" ALIASED_TARGET)
18+
if(_jse_aliased_target)
19+
set(${OUTPUT_VARIABLE} "${_jse_aliased_target}" PARENT_SCOPE)
20+
else()
21+
set(${OUTPUT_VARIABLE} "${TARGET_NAME}" PARENT_SCOPE)
22+
endif()
23+
endfunction()
24+
25+
function(_jse_target_usage_scope OUTPUT_VARIABLE TARGET_NAME)
26+
get_target_property(_jse_target_type "${TARGET_NAME}" TYPE)
27+
if(_jse_target_type STREQUAL "INTERFACE_LIBRARY")
28+
set(${OUTPUT_VARIABLE} INTERFACE PARENT_SCOPE)
29+
elseif(_jse_target_type STREQUAL "EXECUTABLE")
30+
set(${OUTPUT_VARIABLE} PRIVATE PARENT_SCOPE)
31+
else()
32+
set(${OUTPUT_VARIABLE} PUBLIC PARENT_SCOPE)
33+
endif()
34+
endfunction()
35+
36+
function(_jse_append_target_property_unique TARGET_NAME PROPERTY_NAME)
37+
if(ARGC LESS 3)
38+
return()
39+
endif()
40+
41+
get_target_property(_jse_values "${TARGET_NAME}" "${PROPERTY_NAME}")
42+
if(NOT _jse_values)
43+
set(_jse_values)
44+
endif()
45+
46+
list(APPEND _jse_values ${ARGN})
47+
list(REMOVE_DUPLICATES _jse_values)
48+
set_property(TARGET "${TARGET_NAME}" PROPERTY "${PROPERTY_NAME}" "${_jse_values}")
49+
endfunction()
50+
51+
function(_jse_collect_spec_include_dirs OUTPUT_VARIABLE)
52+
set(_jse_queue ${ARGN})
53+
set(_jse_seen)
54+
set(_jse_result)
55+
56+
while(_jse_queue)
57+
list(GET _jse_queue 0 _jse_target)
58+
list(REMOVE_AT _jse_queue 0)
59+
60+
if(NOT TARGET "${_jse_target}")
61+
message(FATAL_ERROR "Unknown JSE spec dependency target: ${_jse_target}")
62+
endif()
63+
64+
_jse_resolve_target(_jse_real_target "${_jse_target}")
65+
if("${_jse_real_target}" IN_LIST _jse_seen)
66+
continue()
67+
endif()
68+
list(APPEND _jse_seen "${_jse_real_target}")
69+
70+
get_target_property(_jse_include_dirs "${_jse_real_target}" INTERFACE_JSE_SPEC_INCLUDE_DIRS)
71+
if(_jse_include_dirs)
72+
list(APPEND _jse_result ${_jse_include_dirs})
73+
endif()
74+
75+
get_target_property(_jse_link_specs "${_jse_real_target}" INTERFACE_JSE_LINK_SPECS)
76+
if(_jse_link_specs)
77+
list(APPEND _jse_queue ${_jse_link_specs})
78+
endif()
79+
endwhile()
80+
81+
list(REMOVE_DUPLICATES _jse_result)
82+
set(${OUTPUT_VARIABLE} "${_jse_result}" PARENT_SCOPE)
83+
endfunction()
84+
85+
function(jse_add_spec TARGET_NAME)
86+
set(options)
87+
set(one_value_args ALIAS INPUT OUTPUT NAMESPACE FUNCTION)
88+
set(multi_value_args INCLUDE_DIRS LINK_SPECS)
89+
cmake_parse_arguments(JSE_SPEC
90+
"${options}"
91+
"${one_value_args}"
92+
"${multi_value_args}"
93+
${ARGN}
94+
)
95+
96+
if(JSE_SPEC_UNPARSED_ARGUMENTS)
97+
message(FATAL_ERROR "Unknown arguments for jse_add_spec: ${JSE_SPEC_UNPARSED_ARGUMENTS}")
98+
endif()
99+
100+
if("${TARGET_NAME}" STREQUAL "")
101+
message(FATAL_ERROR "jse_add_spec requires a target name")
102+
endif()
103+
104+
if((JSE_SPEC_INPUT AND NOT JSE_SPEC_OUTPUT) OR (JSE_SPEC_OUTPUT AND NOT JSE_SPEC_INPUT))
105+
message(FATAL_ERROR "jse_add_spec requires both INPUT and OUTPUT when generating an embedded spec")
106+
endif()
107+
108+
set(_jse_generation_requested OFF)
109+
if(JSE_SPEC_INPUT)
110+
set(_jse_generation_requested ON)
111+
endif()
112+
113+
if(TARGET "${TARGET_NAME}")
114+
_jse_resolve_target(_jse_spec_target "${TARGET_NAME}")
115+
else()
116+
if("${TARGET_NAME}" MATCHES "::")
117+
message(FATAL_ERROR
118+
"Cannot create target ${TARGET_NAME}; CMake target names containing :: must be aliases. "
119+
"Use jse_add_spec(real_target ALIAS ${TARGET_NAME} ...)."
120+
)
121+
endif()
122+
123+
if(_jse_generation_requested)
124+
add_library(${TARGET_NAME})
125+
else()
126+
add_library(${TARGET_NAME} INTERFACE)
127+
endif()
128+
set(_jse_spec_target "${TARGET_NAME}")
129+
endif()
130+
131+
get_target_property(_jse_spec_target_type "${_jse_spec_target}" TYPE)
132+
if(_jse_generation_requested AND "${_jse_spec_target_type}" STREQUAL "INTERFACE_LIBRARY")
133+
message(FATAL_ERROR
134+
"jse_add_spec cannot add generated sources to interface target ${_jse_spec_target}. "
135+
"Create the spec target first with a call that has INPUT and OUTPUT, or use a separate "
136+
"metadata-only spec target as a LINK_SPECS dependency."
137+
)
138+
endif()
139+
140+
if(JSE_SPEC_ALIAS)
141+
if(TARGET "${JSE_SPEC_ALIAS}")
142+
get_target_property(_jse_existing_alias_target "${JSE_SPEC_ALIAS}" ALIASED_TARGET)
143+
if(NOT "${_jse_existing_alias_target}" STREQUAL "${_jse_spec_target}")
144+
message(FATAL_ERROR "Target ${JSE_SPEC_ALIAS} already exists and is not an alias for ${_jse_spec_target}")
145+
endif()
146+
else()
147+
add_library(${JSE_SPEC_ALIAS} ALIAS ${_jse_spec_target})
148+
endif()
149+
endif()
150+
151+
_jse_target_usage_scope(_jse_usage_scope "${_jse_spec_target}")
152+
153+
foreach(_jse_link_spec IN LISTS JSE_SPEC_LINK_SPECS)
154+
if(NOT TARGET "${_jse_link_spec}")
155+
message(FATAL_ERROR "Unknown JSE spec dependency target: ${_jse_link_spec}")
156+
endif()
157+
endforeach()
158+
159+
if(JSE_SPEC_LINK_SPECS)
160+
_jse_append_target_property_unique(${_jse_spec_target} INTERFACE_JSE_LINK_SPECS ${JSE_SPEC_LINK_SPECS})
161+
endif()
162+
163+
set(_jse_local_include_dirs ${JSE_SPEC_INCLUDE_DIRS})
164+
if(JSE_SPEC_INPUT)
165+
get_filename_component(_jse_input_abs "${JSE_SPEC_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
166+
get_filename_component(_jse_input_dir "${_jse_input_abs}" DIRECTORY)
167+
list(APPEND _jse_local_include_dirs "${_jse_input_dir}")
168+
endif()
169+
170+
set(_jse_abs_include_dirs)
171+
foreach(_jse_include_dir IN LISTS _jse_local_include_dirs)
172+
get_filename_component(_jse_abs_include_dir "${_jse_include_dir}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
173+
list(APPEND _jse_abs_include_dirs "${_jse_abs_include_dir}")
174+
endforeach()
175+
if(_jse_abs_include_dirs)
176+
list(REMOVE_DUPLICATES _jse_abs_include_dirs)
177+
_jse_append_target_property_unique(${_jse_spec_target} INTERFACE_JSE_SPEC_INCLUDE_DIRS ${_jse_abs_include_dirs})
178+
endif()
179+
180+
if(NOT _jse_generation_requested)
181+
return()
182+
endif()
183+
184+
if(NOT TARGET jse_embed_spec_tool)
185+
message(FATAL_ERROR "jse_add_spec requires the jse_embed_spec_tool generator target")
186+
endif()
187+
188+
get_filename_component(_jse_embed_header "${JSE_SPEC_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
189+
get_filename_component(_jse_embed_output_dir "${_jse_embed_header}" DIRECTORY)
190+
get_filename_component(_jse_embed_output_stem "${_jse_embed_header}" NAME_WE)
191+
set(_jse_embed_source "${_jse_embed_output_dir}/${_jse_embed_output_stem}.cpp")
192+
string(MAKE_C_IDENTIFIER "${_jse_spec_target}" _jse_embed_target_namespace)
193+
string(MAKE_C_IDENTIFIER "${_jse_embed_output_stem}" _jse_embed_spec_namespace)
194+
195+
if(JSE_SPEC_NAMESPACE)
196+
set(_jse_embed_namespace "${JSE_SPEC_NAMESPACE}")
197+
else()
198+
set(_jse_embed_namespace "jse::embed::${_jse_embed_target_namespace}::${_jse_embed_spec_namespace}")
199+
endif()
200+
201+
if(JSE_SPEC_FUNCTION)
202+
set(_jse_embed_function "${JSE_SPEC_FUNCTION}")
203+
else()
204+
set(_jse_embed_function "spec")
205+
endif()
206+
207+
get_property(_jse_embed_namespaces GLOBAL PROPERTY JSE_EMBED_SPEC_NAMESPACES)
208+
if("${_jse_embed_namespace}" IN_LIST _jse_embed_namespaces)
209+
message(FATAL_ERROR "jse_add_spec already has an embedded spec named ${_jse_embed_namespace}")
210+
endif()
211+
set_property(GLOBAL APPEND PROPERTY JSE_EMBED_SPEC_NAMESPACES "${_jse_embed_namespace}")
212+
213+
_jse_collect_spec_include_dirs(_jse_embed_include_dirs "${_jse_spec_target}")
214+
215+
set(_jse_embed_include_args)
216+
set(_jse_embed_depends "${_jse_input_abs}")
217+
foreach(_jse_embed_include_dir IN LISTS _jse_embed_include_dirs)
218+
list(APPEND _jse_embed_include_args "--include-dir" "${_jse_embed_include_dir}")
219+
file(GLOB_RECURSE _jse_embed_json_depends
220+
CONFIGURE_DEPENDS
221+
"${_jse_embed_include_dir}/*.json"
222+
)
223+
list(APPEND _jse_embed_depends ${_jse_embed_json_depends})
224+
endforeach()
225+
list(REMOVE_DUPLICATES _jse_embed_depends)
226+
227+
file(MAKE_DIRECTORY "${_jse_embed_output_dir}")
228+
229+
add_custom_command(
230+
OUTPUT
231+
"${_jse_embed_header}"
232+
"${_jse_embed_source}"
233+
COMMAND
234+
jse_embed_spec_tool
235+
--input "${_jse_input_abs}"
236+
--output-header "${_jse_embed_header}"
237+
--output-source "${_jse_embed_source}"
238+
--namespace "${_jse_embed_namespace}"
239+
--function "${_jse_embed_function}"
240+
${_jse_embed_include_args}
241+
DEPENDS
242+
jse_embed_spec_tool
243+
${_jse_embed_depends}
244+
COMMENT
245+
"Generating embedded JSON spec ${_jse_embed_output_stem}"
246+
VERBATIM
247+
)
248+
249+
set_source_files_properties(
250+
"${_jse_embed_header}"
251+
"${_jse_embed_source}"
252+
PROPERTIES GENERATED TRUE
253+
)
254+
255+
target_sources(${_jse_spec_target} PRIVATE
256+
"${_jse_embed_source}"
257+
"${_jse_embed_header}"
258+
)
259+
target_include_directories(${_jse_spec_target} ${_jse_usage_scope}
260+
"$<BUILD_INTERFACE:${_jse_embed_output_dir}>"
261+
)
262+
target_link_libraries(${_jse_spec_target} ${_jse_usage_scope} nlohmann_json::nlohmann_json)
263+
target_compile_features(${_jse_spec_target} ${_jse_usage_scope} cxx_std_17)
264+
265+
source_group("Generated" FILES "${_jse_embed_source}" "${_jse_embed_header}")
266+
267+
set(JSE_SPEC_HEADER "${_jse_embed_header}" PARENT_SCOPE)
268+
set(JSE_SPEC_SOURCE "${_jse_embed_source}" PARENT_SCOPE)
269+
endfunction()

src/jse/jse.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ namespace jse
367367
{
368368
assert(rule.at("type") == "float");
369369

370-
if (!input.is_number() && !input.is_null())
370+
if (!input.is_number() && !(allow_null_numbers && input.is_null()))
371371
return false;
372372

373373
if (rule.contains("min") && input < rule["min"])
@@ -383,7 +383,7 @@ namespace jse
383383
{
384384
assert(rule.at("type") == "int");
385385

386-
if (!input.is_number_integer() && !input.is_null())
386+
if (!input.is_number_integer() && !(allow_null_numbers && input.is_null()))
387387
return false;
388388

389389
if (rule.contains("min") && input < rule["min"])

src/jse/jse.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ namespace jse
4040
// if all rules fail for a basic type, try boxing it once and try again
4141
bool boxing_primitive = true;
4242

43+
// allow null values to satisfy int and float rules
44+
bool allow_null_numbers = false;
45+
4346
// message list
4447
typedef std::pair<std::string, std::string> log_item;
4548
std::vector<log_item> log;

tests/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ set(test_sources
88
)
99
add_executable(unit_tests ${test_sources})
1010

11+
jse_add_spec(test_dependency_specs
12+
ALIAS jse::test_dependency_specs
13+
INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/data"
14+
)
15+
16+
jse_add_spec(test_embedded_specs
17+
ALIAS jse::test_embedded_specs
18+
INPUT "${CMAKE_SOURCE_DIR}/data/rules_04.json"
19+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embedded_specs/rules_04.hpp"
20+
LINK_SPECS jse::test_dependency_specs
21+
)
22+
23+
jse_add_spec(test_embedded_specs
24+
INPUT "${CMAKE_SOURCE_DIR}/data/rules_01.json"
25+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embedded_specs/rules_01.hpp"
26+
)
27+
1128
################################################################################
1229
# Required Libraries
1330
################################################################################
@@ -16,6 +33,7 @@ include(catch2)
1633
target_link_libraries(unit_tests PUBLIC Catch2::Catch2)
1734

1835
target_link_libraries(unit_tests PUBLIC jse::jse)
36+
target_link_libraries(unit_tests PUBLIC jse::test_embedded_specs)
1937

2038
include(jse_warnings)
2139
target_link_libraries(unit_tests PRIVATE jse::warnings)

0 commit comments

Comments
 (0)