# Copyright (c) 2014, 2024, Oracle and/or its affiliates. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, # as published by the Free Software Foundation. # # This program is designed to work with certain software (including # but not limited to OpenSSL) that is licensed under separate terms, # as designated in a particular file or component or in included license # documentation. The authors of MySQL hereby grant you an additional # permission to link the program and your derivative works with the # separately licensed software that they have either included with # the program or referenced in the documentation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See # the GNU General Public License, version 2.0, for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cmake_minimum_required (VERSION 3.2) SET(ROOT_PROJECT_DIR "${CMAKE_SOURCE_DIR}/../../") IF(NOT MYSH_VERSION) INCLUDE(${CMAKE_SOURCE_DIR}/../../version.cmake) ENDIF() project (mysqlsh) function (to_snake_case input output) string(REGEX REPLACE "([a-z0-9])([A-Z])" "\\1_\\2" value "${input}") string(TOLOWER "${value}" value) set(${output} "${value}" PARENT_SCOPE) endfunction() function (remove_member_indicators input output) string(LENGTH ${input}, len) math(EXPR new_len "${len} - 6 - 1") string(SUBSTRING "${input}" 3 ${new_len} value) set(${output} "${value}" PARENT_SCOPE) endfunction() function (remove_keyword_indicators input output) string(LENGTH ${input}, len) math(EXPR new_len "${len} - 7 - 1") string(SUBSTRING "${input}" 4 ${new_len} value) set(${output} "${value}" PARENT_SCOPE) endfunction() function(resolve_keywords input output variable) set(${output} "${input}" PARENT_SCOPE) string(REGEX MATCHALL "<<<:[A-Z|a-z|0-9|_]+>>>" keywords "${input}") foreach(keyword ${keywords}) remove_keyword_indicators("${keyword}" keyword_name) string(REGEX REPLACE "([A-Z]*)_.*" "\\1" class_name "${variable}") set(full_keyword_name "${class_name}_${keyword_name}") if("${${full_keyword_name}}" STREQUAL "") else() string(REPLACE "${keyword}" "${${full_keyword_name}}" input "${input}") endif() set(${output} "${input}" PARENT_SCOPE) endforeach() endfunction() function(resolve_member_names input output mode) set(${output} "${input}" PARENT_SCOPE) string(REGEX MATCHALL "<<<[A-Z|a-z|0-9|_]+>>>" members "${input}") foreach(member ${members}) remove_member_indicators("${member}" final_member) if ("PY" STREQUAL "${mode}") to_snake_case("${final_member}" final_member) endif() # STRING(REGEX REPLACE "${member}" "${final_member}" input "${input}") string(REPLACE "${member}" "${final_member}" input "${input}") set(${output} "${input}" PARENT_SCOPE) endforeach() endfunction() function(scan_help_keywords file) FILE(READ "${file}" contents) string(FIND "${contents}" "REGISTER_HELP_CLASS_KW(" class_kw_start) IF(class_kw_start GREATER 0) MATH(EXPR class_kw_start "${class_kw_start}+23") string(SUBSTRING "${contents}" ${class_kw_start} -1 contents) string(REGEX REPLACE "([A-Za-z_]*),.*" "\\1" class_name "${contents}") string(STRIP "${class_name}" class_name) string(TOUPPER "${class_name}" class_name) string(FIND "${contents}" "{{" kwlist_start) MATH(EXPR kwlist_start "${kwlist_start}+1") string(FIND "${contents}" "}}" kwlist_end) MATH(EXPR kwlist_end "${kwlist_end}+1") MATH(EXPR kwlist_length "${kwlist_end}-${kwlist_start}") string(SUBSTRING "${contents}" ${kwlist_start} ${kwlist_length} keyword_map) string(REGEX MATCHALL "{([^}]*)}" keywords "${keyword_map}") foreach(keyword_pair ${keywords}) string(REGEX REPLACE "{\"([^\"]*)\",.*}" "\\1" keyword_name "${keyword_pair}") string(REGEX REPLACE "{[^,]*, \"([^\"]*)\"}" "\\1" keyword_value "${keyword_pair}") set(full_keyword_name "${class_name}_${keyword_name}") set(${full_keyword_name} "${keyword_value}" PARENT_SCOPE) endforeach() ENDIF() endfunction() function(scan_shared_help_text file) SET(continued "0") SET(ready_line "") SET(varname "") # process only headers IF(NOT file MATCHES ".*.h$") RETURN() ENDIF() FILE(READ "${file}" contents) # To iterate the lines of the file, we must turn it into a ; separated list # - escape pre-existing ; STRING(REGEX REPLACE ";" "\\\\;" contents "${contents}") # - turn empty lines into something non-empty, otherwise cmake skips them STRING(REGEX REPLACE "\n\n" "\n \n" contents "${contents}") STRING(REGEX REPLACE "\n\n" "\n \n" contents "${contents}") # - turn newlines into ; STRING(REGEX REPLACE "\n" ";" contents "${contents}") # - dunno... STRING(REGEX REPLACE "\"\\\\;[ ]*\"" "" contents "${contents}") foreach(line ${contents}) IF ("${continued}" STREQUAL "0") # format must be REGISTER_HELP_SHARED_TEXT(VARNAME_HELP_TEXT, # (R"*( # contents in multiple lines)*")); if("${line}" MATCHES "^REGISTER_HELP_SHARED_TEXT\\(") string(REGEX REPLACE "^REGISTER_HELP_SHARED_TEXT\\(([^,]+),.*" "\\1" varname "${line}") string(FIND "${line}" "\"));" register_end REVERSE) string(REGEX REPLACE ".*R\"\\*\\((.*)" "\\1" ready_line "${line}") IF ("${register_end}" STREQUAL "-1") SET(continued "1") ELSE() SET(process "1") ENDIF() endif() ELSE() IF("${continued}" STREQUAL "1") string(FIND "${line}" "\"));" register_end REVERSE) IF("${register_end}" STREQUAL "-1") string(STRIP "${line}" stripped_line) SET(ready_line "${ready_line}\n${line}") ELSE() string(STRIP "${line}" stripped_line) SET(ready_line "${ready_line}\n${stripped_line}") STRING(REGEX REPLACE "\"\"" "" ready_line "${ready_line}") SET(continued "0") string(FIND "${ready_line}" ")*\"" string_end REVERSE) string(SUBSTRING "${ready_line}" 0 ${string_end} ready_line) SET(${varname} "${ready_line}" PARENT_SCOPE) ENDIF() ENDIF() ENDIF() endforeach() endfunction() function (generate_docs type) file(GLOB docs_SRC "${CMAKE_SOURCE_DIR}/../../modules/devapi/*" "${CMAKE_SOURCE_DIR}/../../modules/adminapi/*" "${CMAKE_SOURCE_DIR}/../../modules/util/*" "${CMAKE_SOURCE_DIR}/../../modules/*" ) foreach(file ${docs_SRC}) scan_help_keywords("${file}") scan_shared_help_text("${file}") endforeach() foreach(file ${docs_SRC}) FILE(READ "${file}" contents) # To iterate the lines of the file, we must turn it into a ; separated list # - escape pre-existing ; STRING(REGEX REPLACE ";" "\\\\;" contents "${contents}") # - turn empty lines into something non-empty, otherwise cmake skips them STRING(REGEX REPLACE "\n\n" "\n \n" contents "${contents}") STRING(REGEX REPLACE "\n\n" "\n \n" contents "${contents}") # - turn newlines into ; STRING(REGEX REPLACE "\n" ";" contents "${contents}") # - dunno... STRING(REGEX REPLACE "\"\\\\;[ ]*\"" "" contents "${contents}") #MESSAGE("----> ${file}") SET(number "0") SET(continued "0") SET(process "0") SET(ready_line "") set(ifdef 0) foreach(line ${contents}) IF("${line}" STREQUAL "#ifdef DOXYGEN") SET(ifdef 1) ELSEIF("${line}" STREQUAL "#ifndef DOXYGEN") SET(ifdef -1) ELSEIF("${line}" STREQUAL "#else") MATH(EXPR ifdef "${ifdef} * -1") ELSEIF("${line}" STREQUAL "#endif") SET(ifdef 0) ELSEIF(${ifdef} EQUAL -1) CONTINUE() ENDIF() IF ("${continued}" STREQUAL "0") if("${line}" MATCHES "^REGISTER_HELP\\(") string(FIND "${line}" "\");" register_end REVERSE) SET(ready_line "${line}") IF ("${register_end}" STREQUAL "-1") SET(continued "1") ELSE() SET(process "1") ENDIF() elseif("${line}" MATCHES "^REGISTER_HELP_[A-Z_]*TEXT\\(") string(FIND "${line}" ")*\");" register_end REVERSE) IF("${line}" MATCHES "^REGISTER_HELP_(FUNCTION|PROPERTY|CLASS)_TEXT\\(" OR "${line}" MATCHES "^REGISTER_HELP_TOPIC_WITH_BRIEF_TEXT\\(") SET(tmp "2") ELSE() SET(tmp "3") ENDIF() SET(ready_line "${line}") IF ("${register_end}" STREQUAL "-1") string(FIND "${line}" "_HELP_TEXT);" register_end_var REVERSE) IF("${register_end_var}" STREQUAL "-1") SET(continued "${tmp}") ELSE() # handle REGISTER_HELP_TEXT(HELP_NAME, XXX_HELP_TEXT) string(REGEX REPLACE ".* ([A-Z]*_HELP_TEXT)\\).*" "\\1" varname "${line}") string(REGEX REPLACE "${varname}" "R\"*(${${varname}})*\"" ready_line "${ready_line}") SET(process "${tmp}") ENDIF() ELSE() SET(process "${tmp}") ENDIF() endif() ELSE() IF("${continued}" STREQUAL "1") string(FIND "${line}" "\");" register_end REVERSE) IF("${register_end}" STREQUAL "-1") string(STRIP "${line}" stripped_line) SET(ready_line "${ready_line}${stripped_line}") ELSE() string(STRIP "${line}" stripped_line) SET(ready_line "${ready_line}${stripped_line}") STRING(REGEX REPLACE "\"\"" "" ready_line "${ready_line}") SET(continued "0") SET(process "1") ENDIF() ELSE() SET(ready_line "${ready_line}\n${line}") string(FIND "${line}" ")*\");" register_end REVERSE) IF("${register_end}" STREQUAL "-1") string(FIND "${line}" "_HELP_TEXT);" register_end_var REVERSE) IF("${register_end_var}" STREQUAL "-1") ELSE() # handle REGISTER_HELP_TEXT(HELP_NAME, SHARED_HELP_TEXT) string(REGEX REPLACE ".* ([A-Z]*_HELP_TEXT)\\).*" "\\1" varname "${line}") string(REGEX REPLACE "${varname}" "R\"*(${${varname}})*\"" ready_line "${ready_line}") #message("RESOLVED ${varname} = ${ready_line}") SET(process "${continued}") SET(continued "0") ENDIF() ELSE() SET(process "${continued}") SET(continued "0") ENDIF() ENDIF() ENDIF() MATH(EXPR number "${number}+1") # Now process a line considered complete IF (NOT "${process}" STREQUAL "0") # Retrieves the variable name to be defined #MESSAGE("----> ${number} : ${ready_line}") string(FIND "${ready_line}" "(" start) string(FIND "${ready_line}" "," end) MATH(EXPR start "${start}+1") SET(original "${ready_line}") IF ("${end}" STREQUAL "-1") MESSAGE(">> ${file}") MESSAGE("${ready_line}") ENDIF() MATH(EXPR length "${end}-${start}") string(SUBSTRING "${ready_line}" ${start} ${length} variable) # Now retrieves the value to be assigned IF("${process}" STREQUAL "1") string(FIND "${ready_line}" "\"" start) string(FIND "${ready_line}" "\");" end REVERSE) MATH(EXPR start "${start}+1") ELSE() string(FIND "${ready_line}" "R\"*(\n" start) string(FIND "${ready_line}" ")*\");" end REVERSE) MATH(EXPR start "${start}+5") ENDIF() IF ("${end}" STREQUAL "-1") MESSAGE(">>> ${file}") MESSAGE("${ready_line}") ENDIF() MATH(EXPR length "${end}-${start}") string(SUBSTRING "${ready_line}" ${start} ${length} value) string(REGEX REPLACE "\\$\\{([^}]*)\\}" "$(\\1)" value "${value}") SET(value_brief "") IF("${process}" STREQUAL "2") # doxygen won't handle auto-brief unless the brief part appears separately in the comment string(FIND "${value}" "\n \n" brief_end) IF(NOT "${brief_end}" STREQUAL "-1") string(SUBSTRING "${value}" 0 ${brief_end} value_brief) string(SUBSTRING "${value}" ${brief_end} -1 value) ENDIF() ENDIF() # Creates the variable with the assigned value STRING(STRIP "${variable}" stripped_variable) resolve_keywords("${value}" value "${stripped_variable}") resolve_member_names("${value}" final_value "${type}") #MESSAGE ("VAR: '${stripped_variable}' : '${final_value}'") SET(ENV{${stripped_variable}} "${final_value}") list(APPEND all_variables ${stripped_variable}) IF(NOT "${value_brief}" STREQUAL "") SET(variable_brief "${stripped_variable}_BRIEF") resolve_keywords("${value_brief}" value_brief "${stripped_variable}") resolve_member_names("${value_brief}" final_value_brief "${type}") #MESSAGE ("VAR: '${variable_brief}' : '${final_value_brief}'") SET(ENV{${variable_brief}} "${final_value_brief}") list(APPEND all_variables ${variable_brief}) ENDIF() SET(process "0") ENDIF() endforeach() endforeach() SET(DOX_INPUT "${CMAKE_SOURCE_DIR}/../../modules/adminapi ${CMAKE_SOURCE_DIR}/../../modules/ ${CMAKE_SOURCE_DIR}/../../modules/devapi ${CMAKE_SOURCE_DIR}/../../modules/util ${CMAKE_SOURCE_DIR}") SET(DOX_EXAMPLE_PATH "${CMAKE_SOURCE_DIR}/../../unittest/scripts/py_devapi/scripts/") SET(DOX_EXAMPLE_PATH "${DOX_EXAMPLE_PATH} ${CMAKE_SOURCE_DIR}/../../unittest/scripts/js_devapi/scripts/") SET(DOX_EXAMPLE_PATH "${DOX_EXAMPLE_PATH} ${CMAKE_SOURCE_DIR}/../../unittest/scripts/js_dev_api_examples/") SET(DOX_EXAMPLE_PATH "${DOX_EXAMPLE_PATH} ${CMAKE_SOURCE_DIR}/../../unittest/scripts/py_dev_api_examples/") SET(DOX_EXAMPLE_PATH "${DOX_EXAMPLE_PATH} ${CMAKE_SOURCE_DIR}/../../unittest/scripts/auto/js_devapi/scripts/") SET(DOX_EXAMPLE_PATH "${DOX_EXAMPLE_PATH} ${CMAKE_SOURCE_DIR}/../../unittest/scripts/auto/py_devapi/scripts/") SET(DOX_EXCLUDE_PATTERNS "*my_aes* *mod_dba_replicaset* *mod_dba_instan* mod_extensible_object* interactive_object_wrapper*") SET(DOX_LAYOUT_FILE "${CMAKE_SOURCE_DIR}/DoxygenLayout.scripting.xml") # JS Documentation Generation SET(DOX_PREDEFINED "DOXYGEN_${type}") SET(DOX_ENABLED_SECTIONS "DOXYGEN_${type}") if(SHELL_DOCS_PATH) SET(DOX_OUTDIR "${SHELL_DOCS_PATH}/${type}") else() SET(DOX_OUTDIR "${type}") endif() string(TIMESTAMP TODAY "%b %d, %Y") string(TIMESTAMP YEAR "%Y") # Creates the target footer file configure_file("${CMAKE_SOURCE_DIR}/footer.html.in" "footer.html") # Creates the target file containing the code ready for processing configure_file("${CMAKE_SOURCE_DIR}/doxygen.cfg.in" "doxygen_${type}.cfg") execute_process(COMMAND doxygen "doxygen_${type}.cfg" RESULT_VARIABLE doxygen_result) if(doxygen_result) message(FATAL_ERROR "Error running \"doxygen doxygen_${type}.cfg\": ${doxygen_result}") endif() # search for all unparsed variables find_program(OS_HAS_GREP "grep") if(OS_HAS_GREP) # check just the files which contain suspicious strings # grep recursively the output directory, look for HTML files which contain a series of upper-case characters followed by underscore execute_process(COMMAND "grep" "--include=*.html" "-Rl" "-e" "[[:upper:]]\\+_" "${CMAKE_BINARY_DIR}/${DOX_OUTDIR}" OUTPUT_VARIABLE html_files) string(REPLACE "\n" ";" html_files ${html_files}) else() # check all the files file(GLOB_RECURSE html_files "${CMAKE_BINARY_DIR}/${DOX_OUTDIR}/*.html") endif() # remove variables which appear in the docs and are false-positives list(REMOVE_ITEM all_variables "ROW") foreach(file ${html_files}) file(READ "${file}" contents) foreach(variable ${all_variables}) string(FIND "${contents}" "${variable}" variable_found) if(NOT ${variable_found} EQUAL -1) message(WARNING "File ${file} contains unparsed variable ${variable}.") endif() endforeach() endforeach() endfunction() generate_docs("JS") generate_docs("PY")