|
| 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() |
0 commit comments