include_guard(DIRECTORY) function(_update_client_decode_utf8 codepoints_variable widths_variable input_value) string(HEX "${input_value}" _hex) string(LENGTH "${_hex}" _hex_length) set(_codepoints) set(_widths) set(_offset 0) while(_offset LESS _hex_length) string(SUBSTRING "${_hex}" ${_offset} 2 _first_hex) math(EXPR _first "0x${_first_hex}") if(_first LESS 128) set(_width 1) set(_codepoint ${_first}) elseif(_first LESS 224) math(EXPR _second_offset "${_offset} + 2") string(SUBSTRING "${_hex}" ${_second_offset} 2 _second_hex) math(EXPR _second "0x${_second_hex}") math(EXPR _codepoint "((${_first} & 31) << 6) | (${_second} & 63)") set(_width 2) elseif(_first LESS 240) math(EXPR _second_offset "${_offset} + 2") math(EXPR _third_offset "${_offset} + 4") string(SUBSTRING "${_hex}" ${_second_offset} 2 _second_hex) string(SUBSTRING "${_hex}" ${_third_offset} 2 _third_hex) math(EXPR _second "0x${_second_hex}") math(EXPR _third "0x${_third_hex}") math(EXPR _codepoint "((${_first} & 15) << 12) | ((${_second} & 63) << 6) | (${_third} & 63)" ) set(_width 3) else() math(EXPR _second_offset "${_offset} + 2") math(EXPR _third_offset "${_offset} + 4") math(EXPR _fourth_offset "${_offset} + 6") string(SUBSTRING "${_hex}" ${_second_offset} 2 _second_hex) string(SUBSTRING "${_hex}" ${_third_offset} 2 _third_hex) string(SUBSTRING "${_hex}" ${_fourth_offset} 2 _fourth_hex) math(EXPR _second "0x${_second_hex}") math(EXPR _third "0x${_third_hex}") math(EXPR _fourth "0x${_fourth_hex}") string(CONCAT _expression "((${_first} & 7) << 18) | ((${_second} & 63) << 12) | " "((${_third} & 63) << 6) | (${_fourth} & 63)" ) math(EXPR _codepoint "${_expression}") set(_width 4) endif() list(APPEND _codepoints "${_codepoint}") list(APPEND _widths "${_width}") math(EXPR _offset "${_offset} + (${_width} * 2)") endwhile() set(${codepoints_variable} "${_codepoints}" PARENT_SCOPE) set(${widths_variable} "${_widths}" PARENT_SCOPE) endfunction() function(_update_client_codepoint_is_qt_space output_variable codepoint) if((codepoint GREATER_EQUAL 9 AND codepoint LESS_EQUAL 13) OR codepoint EQUAL 32 OR codepoint EQUAL 160 OR codepoint EQUAL 5760 OR (codepoint GREATER_EQUAL 8192 AND codepoint LESS_EQUAL 8202) OR codepoint EQUAL 8232 OR codepoint EQUAL 8233 OR codepoint EQUAL 8239 OR codepoint EQUAL 8287 OR codepoint EQUAL 12288) set(${output_variable} TRUE PARENT_SCOPE) else() set(${output_variable} FALSE PARENT_SCOPE) endif() endfunction() function(_update_client_qt_trim output_variable input_value) _update_client_decode_utf8(_codepoints _widths "${input_value}") list(LENGTH _codepoints _codepoint_count) set(_first_non_space_byte -1) set(_last_non_space_end_byte -1) set(_byte_offset 0) if(_codepoint_count GREATER 0) math(EXPR _last_index "${_codepoint_count} - 1") foreach(_index RANGE 0 ${_last_index}) list(GET _codepoints ${_index} _codepoint) list(GET _widths ${_index} _width) _update_client_codepoint_is_qt_space(_is_space "${_codepoint}") if(NOT _is_space) if(_first_non_space_byte EQUAL -1) set(_first_non_space_byte ${_byte_offset}) endif() math(EXPR _last_non_space_end_byte "${_byte_offset} + ${_width}") endif() math(EXPR _byte_offset "${_byte_offset} + ${_width}") endforeach() endif() if(_first_non_space_byte EQUAL -1) set(_trimmed "") else() math(EXPR _trimmed_length "${_last_non_space_end_byte} - ${_first_non_space_byte}" ) string(SUBSTRING "${input_value}" ${_first_non_space_byte} ${_trimmed_length} _trimmed) endif() set(${output_variable} "${_trimmed}" PARENT_SCOPE) endfunction() function(_update_client_string_properties control_variable space_variable utf16_length_variable input_value) _update_client_decode_utf8(_codepoints _widths "${input_value}") set(_has_control FALSE) set(_has_space FALSE) set(_utf16_length 0) foreach(_codepoint IN LISTS _codepoints) if(_codepoint LESS_EQUAL 31 OR (_codepoint GREATER_EQUAL 127 AND _codepoint LESS_EQUAL 159)) set(_has_control TRUE) endif() _update_client_codepoint_is_qt_space(_is_space "${_codepoint}") if(_is_space) set(_has_space TRUE) endif() if(_codepoint GREATER 65535) math(EXPR _utf16_length "${_utf16_length} + 2") else() math(EXPR _utf16_length "${_utf16_length} + 1") endif() endforeach() set(${control_variable} ${_has_control} PARENT_SCOPE) set(${space_variable} ${_has_space} PARENT_SCOPE) set(${utf16_length_variable} ${_utf16_length} PARENT_SCOPE) endfunction() function(_update_client_require_json_string json_text field_name) string(JSON _field_type ERROR_VARIABLE _field_error TYPE "${json_text}" "${field_name}") if(NOT _field_error STREQUAL "NOTFOUND") message(FATAL_ERROR "Product config is missing required field '${field_name}': ${_field_error}" ) endif() if(NOT _field_type STREQUAL "STRING") message(FATAL_ERROR "Product config field '${field_name}' must be a string, got ${_field_type}." ) endif() string(JSON _field_value GET "${json_text}" "${field_name}") _update_client_qt_trim(_trimmed_field_value "${_field_value}") if(_trimmed_field_value STREQUAL "") message(FATAL_ERROR "Product config field '${field_name}' must not be empty.") endif() set("UPDATE_CLIENT_CONFIG_${field_name}" "${_field_value}" PARENT_SCOPE) endfunction() function(_update_client_require_positive_integer json_text field_name) string(JSON _field_type ERROR_VARIABLE _field_error TYPE "${json_text}" "${field_name}") if(NOT _field_error STREQUAL "NOTFOUND") message(FATAL_ERROR "Product config is missing required field '${field_name}': ${_field_error}" ) endif() if(NOT _field_type STREQUAL "STRING" AND NOT _field_type STREQUAL "NUMBER") message(FATAL_ERROR "Product config field '${field_name}' must be a string or number, got ${_field_type}." ) endif() string(JSON _field_value GET "${json_text}" "${field_name}") if(NOT _field_value MATCHES "^[1-9][0-9]*$") message(FATAL_ERROR "Product config field '${field_name}' must contain a positive integer." ) endif() string(LENGTH "${_field_value}" _field_length) if(_field_length GREATER 10 OR (_field_length EQUAL 10 AND _field_value STRGREATER "2147483647")) message(FATAL_ERROR "Product config field '${field_name}' exceeds the supported integer range." ) endif() set("UPDATE_CLIENT_CONFIG_${field_name}" "${_field_value}" PARENT_SCOPE) endfunction() function(_update_client_prepare_qdir_path relative_variable clean_variable absolute_variable input_value) _update_client_qt_trim(_relative "${input_value}") string(REPLACE "\\" "/" _relative "${_relative}") set(_path_for_cmake "${_relative}") cmake_path(IS_ABSOLUTE _path_for_cmake _is_absolute) set(_clean "${_relative}") cmake_path(NORMAL_PATH _clean) if(NOT _clean STREQUAL "/") string(REGEX REPLACE "/+$" "" _clean "${_clean}") endif() if(_clean STREQUAL "") set(_clean ".") endif() set(${relative_variable} "${_relative}" PARENT_SCOPE) set(${clean_variable} "${_clean}" PARENT_SCOPE) set(${absolute_variable} ${_is_absolute} PARENT_SCOPE) endfunction() function(_update_client_validate_install_root output_variable input_value validation_context) _update_client_prepare_qdir_path(_relative _clean _is_absolute "${input_value}") if(_is_absolute OR _relative MATCHES ":" OR NOT _clean STREQUAL _relative) message(FATAL_ERROR "Invalid ${validation_context}: field 'install_root' must be a normalized relative path." ) endif() set(${output_variable} "${_relative}" PARENT_SCOPE) endfunction() function(_update_client_validate_strict_relative_path field_name input_value validation_context) _update_client_prepare_qdir_path(_relative _clean _is_absolute "${input_value}") if(_relative STREQUAL "" OR _is_absolute OR _relative MATCHES ":" OR NOT _clean STREQUAL _relative OR _relative MATCHES "(^|/)\\.\\.?(/|$)") message(FATAL_ERROR "Invalid ${validation_context}: field '${field_name}' must be a safe relative path." ) endif() endfunction() function(_update_client_split_leading_parents count_variable remainder_variable input_value) set(_remaining "${input_value}") set(_count 0) if(_remaining STREQUAL ".") set(_remaining "") else() while(_remaining MATCHES "^\\.\\./") math(EXPR _count "${_count} + 1") string(SUBSTRING "${_remaining}" 3 -1 _remaining) endwhile() if(_remaining STREQUAL "..") math(EXPR _count "${_count} + 1") set(_remaining "") endif() endif() set(${count_variable} ${_count} PARENT_SCOPE) set(${remainder_variable} "${_remaining}" PARENT_SCOPE) endfunction() function(_update_client_validate_main_executable input_value install_root validation_context) _update_client_prepare_qdir_path(_relative _clean _is_absolute "${input_value}") if(_relative STREQUAL "" OR _is_absolute OR _relative MATCHES ":" OR NOT _clean STREQUAL _relative OR _relative STREQUAL "." OR _relative STREQUAL "..") message(FATAL_ERROR "Invalid ${validation_context}: field 'main_executable' must stay within install_root." ) endif() _update_client_split_leading_parents( _install_parent_count _install_remainder "${install_root}" ) _update_client_split_leading_parents( _main_parent_count _main_remainder "${_relative}" ) set(_is_within_root FALSE) if(_install_remainder STREQUAL "") if(_main_parent_count LESS_EQUAL _install_parent_count) set(_is_within_root TRUE) endif() elseif(_main_parent_count EQUAL _install_parent_count) if(_main_remainder STREQUAL _install_remainder) set(_is_within_root TRUE) else() string(FIND "${_main_remainder}" "${_install_remainder}/" _root_prefix_index) if(_root_prefix_index EQUAL 0) set(_is_within_root TRUE) endif() endif() endif() if(NOT _is_within_root) message(FATAL_ERROR "Invalid ${validation_context}: field 'main_executable' must stay within install_root." ) endif() endfunction() function(_update_client_count_ipv6_groups count_variable valid_variable input_value) set(_remaining "${input_value}") set(_count 0) set(_valid TRUE) while(NOT _remaining STREQUAL "") string(FIND "${_remaining}" ":" _separator_index) if(_separator_index EQUAL -1) set(_group "${_remaining}") set(_remaining "") else() string(SUBSTRING "${_remaining}" 0 ${_separator_index} _group) math(EXPR _next_index "${_separator_index} + 1") string(SUBSTRING "${_remaining}" ${_next_index} -1 _remaining) endif() string(LENGTH "${_group}" _group_length) if(_group STREQUAL "" OR _group_length GREATER 4 OR NOT _group MATCHES "^[0-9A-Fa-f]+$") set(_valid FALSE) break() endif() math(EXPR _count "${_count} + 1") endwhile() set(${count_variable} ${_count} PARENT_SCOPE) set(${valid_variable} ${_valid} PARENT_SCOPE) endfunction() function(_update_client_is_valid_ipv6 output_variable input_value) set(_valid FALSE) if(input_value MATCHES "^[0-9A-Fa-f:]+$") string(FIND "${input_value}" "::" _compression_index) if(_compression_index EQUAL -1) _update_client_count_ipv6_groups(_group_count _groups_valid "${input_value}") if(_groups_valid AND _group_count EQUAL 8) set(_valid TRUE) endif() else() string(SUBSTRING "${input_value}" 0 ${_compression_index} _left) math(EXPR _right_index "${_compression_index} + 2") string(SUBSTRING "${input_value}" ${_right_index} -1 _right) string(FIND "${_right}" "::" _second_compression_index) if(NOT _left MATCHES ":$" AND NOT _right MATCHES "^:" AND _second_compression_index EQUAL -1) _update_client_count_ipv6_groups(_left_count _left_valid "${_left}") _update_client_count_ipv6_groups(_right_count _right_valid "${_right}") math(EXPR _explicit_group_count "${_left_count} + ${_right_count}") if(_left_valid AND _right_valid AND _explicit_group_count LESS 8) set(_valid TRUE) endif() endif() endif() endif() set(${output_variable} ${_valid} PARENT_SCOPE) endfunction() function(_update_client_is_valid_url_port output_variable input_value) set(_valid FALSE) if(input_value MATCHES "^[0-9]+$") string(REGEX REPLACE "^0+" "" _significant "${input_value}") if(_significant STREQUAL "") set(_significant 0) endif() string(LENGTH "${_significant}" _length) if(_length LESS 6 AND NOT (_length EQUAL 5 AND _significant STRGREATER "65535")) set(_valid TRUE) endif() endif() set(${output_variable} ${_valid} PARENT_SCOPE) endfunction() function(_update_client_validate_http_url input_value validation_context) set(_valid TRUE) if(NOT input_value MATCHES "^[Hh][Tt][Tt][Pp]([Ss])?://") set(_valid FALSE) else() string(REGEX REPLACE "^[Hh][Tt][Tt][Pp]([Ss])?://" "" _url_remainder "${input_value}" ) string(REGEX MATCH "^[^/?#]*" _authority "${_url_remainder}") if(_authority STREQUAL "") set(_valid FALSE) else() _update_client_string_properties( _url_has_control _unused_space _unused_length "${input_value}" ) _update_client_string_properties( _unused_control _authority_has_space _unused_length "${_authority}" ) if(_url_has_control OR _authority_has_space) set(_valid FALSE) endif() endif() endif() if(_valid) string(REGEX REPLACE "^.*@" "" _host_port "${_authority}") if(_host_port MATCHES "^\\[") if(NOT _host_port MATCHES "^\\[([^]]+)\\](.*)$") set(_valid FALSE) else() set(_host "${CMAKE_MATCH_1}") set(_port_suffix "${CMAKE_MATCH_2}") _update_client_is_valid_ipv6(_host_valid "${_host}") if(NOT _host_valid) set(_valid FALSE) endif() endif() else() if(_host_port MATCHES "[\\[\\]]") set(_valid FALSE) else() string(REGEX REPLACE "[^:]" "" _colons "${_host_port}") string(LENGTH "${_colons}" _colon_count) if(_colon_count GREATER 1) set(_valid FALSE) elseif(_colon_count EQUAL 1) string(FIND "${_host_port}" ":" _colon_index) string(SUBSTRING "${_host_port}" 0 ${_colon_index} _host) math(EXPR _port_index "${_colon_index} + 1") string(SUBSTRING "${_host_port}" ${_port_index} -1 _port) set(_port_suffix ":${_port}") else() set(_host "${_host_port}") set(_port_suffix "") endif() endif() endif() endif() if(_valid AND _host STREQUAL "") set(_valid FALSE) endif() if(_valid AND NOT _port_suffix STREQUAL "") if(NOT _port_suffix MATCHES "^:(.+)$") set(_valid FALSE) else() _update_client_is_valid_url_port(_port_valid "${CMAKE_MATCH_1}") if(NOT _port_valid) set(_valid FALSE) endif() endif() endif() if(_valid) _update_client_decode_utf8(_host_codepoints _unused_widths "${_host}") foreach(_codepoint IN LISTS _host_codepoints) if(_codepoint EQUAL 34 OR _codepoint EQUAL 35 OR _codepoint EQUAL 47 OR _codepoint EQUAL 58 OR _codepoint EQUAL 60 OR _codepoint EQUAL 62 OR _codepoint EQUAL 63 OR _codepoint EQUAL 64 OR _codepoint EQUAL 91 OR _codepoint EQUAL 92 OR _codepoint EQUAL 93 OR _codepoint EQUAL 94 OR _codepoint EQUAL 96 OR _codepoint EQUAL 123 OR _codepoint EQUAL 124 OR _codepoint EQUAL 125) set(_valid FALSE) break() endif() endforeach() endif() if(NOT _valid) message(FATAL_ERROR "Invalid ${validation_context}: field 'api_base_url' must be a valid HTTP or HTTPS URL with a non-empty host." ) endif() endfunction() function(_update_client_is_valid_uuid output_variable input_value) set(_uuid "${input_value}") string(TOLOWER "${_uuid}" _uuid_lower) if(_uuid_lower MATCHES "^urn:uuid:") string(SUBSTRING "${_uuid}" 9 -1 _uuid) endif() if(_uuid MATCHES "^\\{.*\\}$") string(LENGTH "${_uuid}" _uuid_length) if(_uuid_length GREATER_EQUAL 2) math(EXPR _inner_length "${_uuid_length} - 2") string(SUBSTRING "${_uuid}" 1 ${_inner_length} _uuid) endif() endif() set(_valid FALSE) if(_uuid STREQUAL "") set(_valid TRUE) else() string(LENGTH "${_uuid}" _uuid_length) if(_uuid_length EQUAL 36 AND _uuid MATCHES "^[0-9A-Fa-f-]+$") string(SUBSTRING "${_uuid}" 8 1 _hyphen_1) string(SUBSTRING "${_uuid}" 13 1 _hyphen_2) string(SUBSTRING "${_uuid}" 18 1 _hyphen_3) string(SUBSTRING "${_uuid}" 23 1 _hyphen_4) string(REPLACE "-" "" _uuid_digits "${_uuid}") string(LENGTH "${_uuid_digits}" _digits_length) string(TOLOWER "${_uuid_digits}" _uuid_digits) if(_hyphen_1 STREQUAL "-" AND _hyphen_2 STREQUAL "-" AND _hyphen_3 STREQUAL "-" AND _hyphen_4 STREQUAL "-" AND _digits_length EQUAL 32 AND NOT _uuid_digits STREQUAL "00000000000000000000000000000000") set(_valid TRUE) endif() endif() endif() set(${output_variable} ${_valid} PARENT_SCOPE) endfunction() function(_update_client_validate_product_config json_text validation_context) string(JSON _root_type ERROR_VARIABLE _json_error TYPE "${json_text}") if(NOT _json_error STREQUAL "NOTFOUND") message(FATAL_ERROR "Invalid JSON in ${validation_context}: ${_json_error}") endif() if(NOT _root_type STREQUAL "OBJECT") message(FATAL_ERROR "Invalid ${validation_context}: root must be a JSON object.") endif() string(JSON _schema_type ERROR_VARIABLE _schema_error TYPE "${json_text}" schema_version) if(NOT _schema_error STREQUAL "NOTFOUND" OR NOT _schema_type STREQUAL "NUMBER") message(FATAL_ERROR "Invalid ${validation_context}: schema_version must be the number 1.") endif() string(JSON _schema_version GET "${json_text}" schema_version) if(NOT _schema_version EQUAL 1) message(FATAL_ERROR "Invalid ${validation_context}: unsupported schema_version ${_schema_version}." ) endif() foreach(_required_field IN ITEMS app_id app_name channel launch_token api_base_url client_token temp_folder install_root main_executable launcher_executable updater_executable bootstrap_executable platform arch) _update_client_require_json_string("${json_text}" "${_required_field}") endforeach() foreach(_numeric_field IN ITEMS client_protocol request_timeout_ms health_check_timeout_ms) _update_client_require_positive_integer("${json_text}" "${_numeric_field}") endforeach() foreach(_identifier_field IN ITEMS app_id channel platform arch) set(_identifier_value "${UPDATE_CLIENT_CONFIG_${_identifier_field}}") if(NOT _identifier_value MATCHES "^[A-Za-z0-9._-]+$") message(FATAL_ERROR "Invalid ${validation_context}: field '${_identifier_field}' contains unsupported characters." ) endif() endforeach() _update_client_validate_http_url( "${UPDATE_CLIENT_CONFIG_api_base_url}" "${validation_context}" ) _update_client_validate_install_root( _install_root "${UPDATE_CLIENT_CONFIG_install_root}" "${validation_context}" ) _update_client_validate_main_executable( "${UPDATE_CLIENT_CONFIG_main_executable}" "${_install_root}" "${validation_context}" ) foreach(_path_field IN ITEMS temp_folder launcher_executable updater_executable bootstrap_executable) _update_client_validate_strict_relative_path( "${_path_field}" "${UPDATE_CLIENT_CONFIG_${_path_field}}" "${validation_context}" ) endforeach() foreach(_mutable_field IN ITEMS current_version license_key device_id installation_id) string(JSON _root_mutable_type ERROR_VARIABLE _root_mutable_error TYPE "${json_text}" "${_mutable_field}") if(_root_mutable_error STREQUAL "NOTFOUND") message(FATAL_ERROR "Invalid ${validation_context}: mutable field '${_mutable_field}' must be under initial_state." ) endif() endforeach() string(JSON _initial_state_type ERROR_VARIABLE _initial_state_error TYPE "${json_text}" initial_state) if(NOT _initial_state_error STREQUAL "NOTFOUND" OR NOT _initial_state_type STREQUAL "OBJECT") message(FATAL_ERROR "Invalid ${validation_context}: initial_state must be an object.") endif() set(_allowed_initial_state current_version license_key device_id installation_id) set(_has_current_version FALSE) string(JSON _initial_state_length LENGTH "${json_text}" initial_state) if(_initial_state_length GREATER 0) math(EXPR _initial_state_last "${_initial_state_length} - 1") foreach(_index RANGE 0 ${_initial_state_last}) string(JSON _initial_key MEMBER "${json_text}" initial_state ${_index}) list(FIND _allowed_initial_state "${_initial_key}" _allowed_index) string(JSON _initial_type TYPE "${json_text}" initial_state "${_initial_key}") if(_allowed_index EQUAL -1 OR NOT _initial_type STREQUAL "STRING") message(FATAL_ERROR "Invalid ${validation_context}: initial_state.${_initial_key} is unsupported or is not a string." ) endif() string(JSON _initial_value GET "${json_text}" initial_state "${_initial_key}") _update_client_qt_trim(_initial_value "${_initial_value}") if(_initial_key STREQUAL "current_version") set(_has_current_version TRUE) string(LENGTH "${_initial_value}" _version_length) if(_version_length GREATER 128 OR NOT _initial_value MATCHES "^[0-9A-Za-z][0-9A-Za-z._+-]*$") message(FATAL_ERROR "Invalid ${validation_context}: initial_state.current_version has an invalid value." ) endif() elseif(_initial_key STREQUAL "installation_id") _update_client_is_valid_uuid(_uuid_valid "${_initial_value}") if(NOT _uuid_valid) message(FATAL_ERROR "Invalid ${validation_context}: initial_state.installation_id must be empty or a non-null UUID." ) endif() else() _update_client_string_properties( _has_control _unused_space _value_length "${_initial_value}" ) if(_initial_key STREQUAL "device_id") set(_maximum_length 256) else() set(_maximum_length 4096) endif() if(_has_control OR _value_length GREATER _maximum_length) message(FATAL_ERROR "Invalid ${validation_context}: initial_state.${_initial_key} contains a control character or exceeds ${_maximum_length} UTF-16 code units." ) endif() endif() endforeach() endif() if(NOT _has_current_version) message(FATAL_ERROR "Invalid ${validation_context}: initial_state.current_version is required." ) endif() endfunction() function(_update_client_xml_escape output_variable input_value) set(_escaped "${input_value}") string(REPLACE "&" "&" _escaped "${_escaped}") string(REPLACE "<" "<" _escaped "${_escaped}") string(REPLACE ">" ">" _escaped "${_escaped}") set(${output_variable} "${_escaped}" PARENT_SCOPE) endfunction() function(_update_client_json_quote output_variable input_value) set(_escaped "${input_value}") string(REPLACE "\\" "\\\\" _escaped "${_escaped}") string(REPLACE "\"" "\\\"" _escaped "${_escaped}") string(REPLACE "\r" "\\r" _escaped "${_escaped}") string(REPLACE "\n" "\\n" _escaped "${_escaped}") string(REPLACE "\t" "\\t" _escaped "${_escaped}") set(${output_variable} "\"${_escaped}\"" PARENT_SCOPE) endfunction() function(update_client_select_product_config output_variable) set(_primary_config "${UPDATE_CLIENT_SOURCE_DIR}/config/config.json") set(_fallback_config "${UPDATE_CLIENT_SOURCE_DIR}/config/app_config.example.json") file(GLOB _product_config_candidates CONFIGURE_DEPENDS "${UPDATE_CLIENT_SOURCE_DIR}/config/config.json" "${UPDATE_CLIENT_SOURCE_DIR}/config/app_config.example.json") set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_primary_config}" "${_fallback_config}" ) if(UPDATE_CLIENT_PRODUCT_CONFIG_FILE) cmake_path( ABSOLUTE_PATH UPDATE_CLIENT_PRODUCT_CONFIG_FILE BASE_DIRECTORY "${UPDATE_CLIENT_SOURCE_DIR}" NORMALIZE OUTPUT_VARIABLE _selected_config ) if(NOT EXISTS "${_selected_config}" OR IS_DIRECTORY "${_selected_config}") message(FATAL_ERROR "UPDATE_CLIENT_PRODUCT_CONFIG_FILE does not exist or is not a file: " "${_selected_config}" ) endif() set(_selection_reason "caller-provided UPDATE_CLIENT_PRODUCT_CONFIG_FILE") elseif(EXISTS "${_primary_config}") set(_selected_config "${_primary_config}") set(_selection_reason "project config/config.json") elseif(EXISTS "${_fallback_config}") set(_selected_config "${_fallback_config}") set(_selection_reason "fallback config/app_config.example.json") else() message(FATAL_ERROR "No update-client product config was found. Create " "${_primary_config} or restore ${_fallback_config}." ) endif() set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_selected_config}") set(UPDATE_CLIENT_SELECTED_PRODUCT_CONFIG_FILE "${_selected_config}" CACHE INTERNAL "Selected update-client product config" FORCE) file(READ "${_selected_config}" _config_json) _update_client_validate_product_config( "${_config_json}" "selected product config ${_selected_config}" ) message(STATUS "update-client product config source: ${_selected_config}") message(STATUS "update-client product config selection: ${_selection_reason}") set(${output_variable} "${_selected_config}" PARENT_SCOPE) endfunction() function(update_client_generate_normalized_product_config output_variable source_file) file(READ "${source_file}" _source_json) string(JSON _normalized_json GET "${_source_json}") string(JSON _source_launch_token GET "${_source_json}" launch_token) set(_effective_launch_token "${UPDATE_CLIENT_LAUNCH_TOKEN}") if(_effective_launch_token STREQUAL "" AND _source_launch_token MATCHES "(ChangeMe|CHANGE_ME|YOUR_[A-Z_]+)") if(NOT UPDATE_CLIENT_GENERATED_LAUNCH_TOKEN) string(RANDOM LENGTH 64 ALPHABET "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" _generated_launch_token) set(UPDATE_CLIENT_GENERATED_LAUNCH_TOKEN "${_generated_launch_token}" CACHE INTERNAL "Generated update-client launch token" FORCE) endif() set(_effective_launch_token "${UPDATE_CLIENT_GENERATED_LAUNCH_TOKEN}") message(STATUS "Generated a build-local launch token for the example product config") endif() if(NOT _effective_launch_token STREQUAL "") _update_client_json_quote(_launch_token_json "${_effective_launch_token}") string(JSON _normalized_json SET "${_normalized_json}" launch_token "${_launch_token_json}") endif() foreach(_override_variable IN ITEMS UPDATE_CLIENT_MAIN_EXECUTABLE UPDATE_CLIENT_LAUNCHER_EXECUTABLE UPDATE_CLIENT_UPDATER_EXECUTABLE UPDATE_CLIENT_BOOTSTRAP_EXECUTABLE UPDATE_CLIENT_PLATFORM UPDATE_CLIENT_ARCH) if("${${_override_variable}}" STREQUAL "") continue() endif() string(REGEX REPLACE "^UPDATE_CLIENT_" "" _json_field "${_override_variable}") string(TOLOWER "${_json_field}" _json_field) _update_client_json_quote(_json_value "${${_override_variable}}") string(JSON _normalized_json SET "${_normalized_json}" "${_json_field}" "${_json_value}") message(STATUS "update-client product config override: ${_json_field}=${${_override_variable}}" ) endforeach() _update_client_validate_product_config( "${_normalized_json}" "normalized product config after build overrides" ) set(_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") file(MAKE_DIRECTORY "${_generated_dir}") set(_normalized_file "${_generated_dir}/product-config.json") file(WRITE "${_normalized_file}" "${_normalized_json}\n") message(STATUS "update-client normalized product config: ${_normalized_file}") set(${output_variable} "${_normalized_file}" PARENT_SCOPE) endfunction() function(update_client_create_embedded_resources target_name product_config_file) if(TARGET "${target_name}") message(FATAL_ERROR "Embedded resource target already exists: ${target_name}") endif() set(_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") file(MAKE_DIRECTORY "${_generated_dir}") cmake_path(CONVERT "${product_config_file}" TO_CMAKE_PATH_LIST _config_qrc_path NORMALIZE) _update_client_xml_escape(_config_qrc_path "${_config_qrc_path}") set(_resource_entries " ${_config_qrc_path}\n" ) set(_public_key "${UPDATE_CLIENT_SOURCE_DIR}/config/manifest_public_key.pem") if(NOT EXISTS "${_public_key}") message(FATAL_ERROR "The manifest verification public key is missing: ${_public_key}" ) endif() cmake_path(CONVERT "${_public_key}" TO_CMAKE_PATH_LIST _public_key_qrc_path NORMALIZE) _update_client_xml_escape(_public_key_qrc_path "${_public_key_qrc_path}") string(APPEND _resource_entries " ${_public_key_qrc_path}\n" ) set(_translation_catalog "${UPDATE_CLIENT_SOURCE_DIR}/translations/update-client_zh_CN.ts" ) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_translation_catalog}") set(_generated_inputs) if(EXISTS "${_translation_catalog}") find_package(Qt5 5.15 CONFIG REQUIRED COMPONENTS LinguistTools) if(NOT TARGET Qt5::lrelease) message(FATAL_ERROR "Qt5::lrelease is required to compile update-client translations." ) endif() set(_translation_qm "${_generated_dir}/update-client_zh_CN.qm") add_custom_command( OUTPUT "${_translation_qm}" COMMAND Qt5::lrelease "${_translation_catalog}" -qm "${_translation_qm}" DEPENDS "${_translation_catalog}" Qt5::lrelease COMMENT "Compiling update-client Chinese translation catalog" VERBATIM ) list(APPEND _generated_inputs "${_translation_qm}") cmake_path(CONVERT "${_translation_qm}" TO_CMAKE_PATH_LIST _translation_qrc_path NORMALIZE) _update_client_xml_escape(_translation_qrc_path "${_translation_qrc_path}") string(APPEND _resource_entries " ${_translation_qrc_path}\n" ) elseif(UPDATE_CLIENT_REQUIRE_TRANSLATIONS) message(FATAL_ERROR "Required translation catalog is missing: ${_translation_catalog}" ) else() message(STATUS "Translation catalog is not present yet; embedded i18n will be enabled when " "translations/update-client_zh_CN.ts is added." ) endif() set(_resource_file "${_generated_dir}/update_client_embedded.qrc") string(CONCAT _resource_content "\n" " \n" "${_resource_entries}" " \n" "\n" ) file(CONFIGURE OUTPUT "${_resource_file}" CONTENT "${_resource_content}" @ONLY ) if(NOT TARGET Qt5::rcc) message(FATAL_ERROR "Qt5::rcc is required to compile update-client resources.") endif() set(_resource_source "${_generated_dir}/qrc_update_client_embedded.cpp") add_custom_command( OUTPUT "${_resource_source}" COMMAND Qt5::rcc --name update_client_embedded --output "${_resource_source}" "${_resource_file}" DEPENDS "${_resource_file}" "${product_config_file}" "${_public_key}" ${_generated_inputs} Qt5::rcc COMMENT "Compiling update-client embedded resources" VERBATIM ) add_library("${target_name}" OBJECT "${_resource_source}" ${_generated_inputs}) target_link_libraries("${target_name}" PRIVATE Qt5::Core) set_target_properties("${target_name}" PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF ) set(UPDATE_CLIENT_PRODUCT_CONFIG_RESOURCE ":/simcae/update-client/product-config.json" PARENT_SCOPE ) set(UPDATE_CLIENT_TRANSLATION_RESOURCE ":/simcae/update-client/i18n/update-client_zh_CN.qm" PARENT_SCOPE ) endfunction() function(update_client_link_embedded_resources target_name) if(NOT TARGET "${target_name}") message(FATAL_ERROR "Unknown update-client target: ${target_name}") endif() if(NOT TARGET UpdateClientEmbeddedResources) message(FATAL_ERROR "UpdateClientEmbeddedResources has not been created.") endif() target_link_libraries("${target_name}" PRIVATE UpdateClientEmbeddedResources) endfunction()