From c9e3ae8c9a507883318909e1ef4c8398fc521ecd Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 5 Jun 2026 15:52:48 +0000 Subject: [PATCH] CMake: improve and vendor upstream `FindBISON` module. This change uses the `find_program(VALIDATOR)` functionality available since CMake 3.25 to find the acceptable Bison executable instead of failing if the first one found is too old. (macOS ships with Bison 2.6.) This change also explicitly finds a usable M4 and ensures the Bison command will use it. (macOS ships with an m4 which can fail if XCode Tools aren't installed.) --- cmake/FindBISON.cmake | 346 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 cmake/FindBISON.cmake diff --git a/cmake/FindBISON.cmake b/cmake/FindBISON.cmake new file mode 100644 index 000000000..11851fd05 --- /dev/null +++ b/cmake/FindBISON.cmake @@ -0,0 +1,346 @@ +# Based on CMake v3.31.0 `Modules/FindBISON.cmake`, changed as follows: +# - Continue searching for Bison if an executable does not match the requested +# version constraint. +# - Search for a usable M4 and run Bison with the `M4` environment variable. + +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindBISON +--------- + +Find ``bison`` executable and provide a macro to generate custom build rules. + +The module defines the following variables: + +``BISON_EXECUTABLE`` + path to the ``bison`` program + +``BISON_VERSION`` + version of ``bison`` + +``BISON_FOUND`` + "True" if the program was found + +The minimum required version of ``bison`` can be specified using the +standard CMake syntax, e.g. :command:`find_package(BISON 2.1.3)`. + +If ``bison`` is found, the module defines the macro:: + + BISON_TARGET( + [COMPILE_FLAGS ] + [DEFINES_FILE ] + [VERBOSE []] + [REPORT_FILE ] + ) + +which will create a custom rule to generate a parser. ```` is +the path to a yacc file. ```` is the name of the source file +generated by bison. A header file is also be generated, and contains +the token list. + +.. versionchanged:: 3.14 + When :policy:`CMP0088` is set to ``NEW``, ``bison`` runs in the + :variable:`CMAKE_CURRENT_BINARY_DIR` directory. + +The options are: + +``COMPILE_FLAGS `` + Specify flags to be added to the ``bison`` command line. + +``DEFINES_FILE `` + .. versionadded:: 3.4 + + Specify a non-default header ```` to be generated by ``bison``. + +``VERBOSE []`` + Tell ``bison`` to write a report file of the grammar and parser. + + .. deprecated:: 3.7 + If ```` is given, it specifies path the report file is copied to. + ``[]`` is left for backward compatibility of this module. + Use ``VERBOSE REPORT_FILE ``. + +``REPORT_FILE `` + .. versionadded:: 3.7 + + Specify a non-default report ````, if generated. + +The macro defines the following variables: + +``BISON__DEFINED`` + ``True`` is the macro ran successfully + +``BISON__INPUT`` + The input source file, an alias for + +``BISON__OUTPUT_SOURCE`` + The source file generated by bison + +``BISON__OUTPUT_HEADER`` + The header file generated by bison + +``BISON__OUTPUTS`` + All files generated by bison including the source, the header and the report + +``BISON__COMPILE_FLAGS`` + Options used in the ``bison`` command line + +Example usage: + +.. code-block:: cmake + + find_package(BISON) + BISON_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp + DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/parser.h) + add_executable(Foo main.cpp ${BISON_MyParser_OUTPUTS}) +#]=======================================================================] + +function(BISON_m4_validator result_var executable) + execute_process(COMMAND ${executable} --version + OUTPUT_QUIET + ERROR_QUIET + RESULT_VARIABLE M4_version_result + ) + if (NOT M4_version_result EQUAL 0) + set(${result_var} FALSE PARENT_SCOPE) + endif() +endfunction() + +find_program(BISON_M4_EXECUTABLE + NAMES m4 + VALIDATOR BISON_m4_validator + DOC "path to the m4 executable (used by bison)" +) + +function(BISON_get_version result_var executable) + # the bison commands should be executed with the C locale, otherwise + # the message (which are parsed) may be translated + set(_Bison_SAVED_LC_ALL "$ENV{LC_ALL}") + set(ENV{LC_ALL} C) + + execute_process(COMMAND ${executable} --version + OUTPUT_VARIABLE BISON_version_output + ERROR_VARIABLE BISON_version_error + RESULT_VARIABLE BISON_version_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(ENV{LC_ALL} ${_Bison_SAVED_LC_ALL}) + + if(NOT ${BISON_version_result} EQUAL 0) + message(SEND_ERROR "Command \"${executable} --version\" failed with output:\n${BISON_version_error}") + else() + # Bison++ + if("${BISON_version_output}" MATCHES "^bison\\+\\+ Version ([^,]+)") + set(${result_var} "${CMAKE_MATCH_1}") + # GNU Bison + elseif("${BISON_version_output}" MATCHES "^bison \\(GNU Bison\\) ([^\n]+)\n") + set(${result_var} "${CMAKE_MATCH_1}") + elseif("${BISON_version_output}" MATCHES "^GNU Bison (version )?([^\n]+)") + set(${result_var} "${CMAKE_MATCH_2}") + endif() + endif() + + return(PROPAGATE ${result_var}) +endfunction() + +function(BISON_validator result_var executable) + BISON_get_version(bison_version ${executable}) + find_package_check_version("${bison_version}" ${result_var}) + return(PROPAGATE ${result_var}) +endfunction() + +find_program(BISON_EXECUTABLE + NAMES bison win-bison win_bison + VALIDATOR BISON_validator + DOC "path to the bison executable" +) +mark_as_advanced(BISON_EXECUTABLE) + +if(BISON_EXECUTABLE) + BISON_get_version(BISON_VERSION ${BISON_EXECUTABLE}) + + # internal macro + # sets BISON_TARGET_cmdopt + macro(BISON_TARGET_option_extraopts Options) + set(BISON_TARGET_cmdopt "") + set(BISON_TARGET_extraopts "${Options}") + separate_arguments(BISON_TARGET_extraopts) + list(APPEND BISON_TARGET_cmdopt ${BISON_TARGET_extraopts}) + endmacro() + + # internal macro + # sets BISON_TARGET_output_header and BISON_TARGET_cmdopt + macro(BISON_TARGET_option_defines BisonOutput Header) + if("${Header}" STREQUAL "") + # default header path generated by bison (see option -d) + string(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\2" _fileext "${BisonOutput}") + string(REPLACE "c" "h" _fileext ${_fileext}) + string(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\1${_fileext}" + BISON_TARGET_output_header "${BisonOutput}") + list(APPEND BISON_TARGET_cmdopt "-d") + else() + set(BISON_TARGET_output_header "${Header}") + list(APPEND BISON_TARGET_cmdopt "--defines=${BISON_TARGET_output_header}") + endif() + endmacro() + + # internal macro + # sets BISON_TARGET_verbose_file and BISON_TARGET_cmdopt + macro(BISON_TARGET_option_report_file BisonOutput ReportFile) + if("${ReportFile}" STREQUAL "") + get_filename_component(BISON_TARGET_output_path "${BisonOutput}" PATH) + get_filename_component(BISON_TARGET_output_name "${BisonOutput}" NAME_WE) + set(BISON_TARGET_verbose_file + "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output") + else() + set(BISON_TARGET_verbose_file "${ReportFile}") + list(APPEND BISON_TARGET_cmdopt "--report-file=${BISON_TARGET_verbose_file}") + endif() + if(NOT IS_ABSOLUTE "${BISON_TARGET_verbose_file}") + cmake_policy(GET CMP0088 _BISON_CMP0088 + PARENT_SCOPE # undocumented, do not use outside of CMake + ) + if("x${_BISON_CMP0088}x" STREQUAL "xNEWx") + set(BISON_TARGET_verbose_file "${CMAKE_CURRENT_BINARY_DIR}/${BISON_TARGET_verbose_file}") + else() + set(BISON_TARGET_verbose_file "${CMAKE_CURRENT_SOURCE_DIR}/${BISON_TARGET_verbose_file}") + endif() + unset(_BISON_CMP0088) + endif() + endmacro() + + # internal macro + # adds a custom command and sets + # BISON_TARGET_cmdopt, BISON_TARGET_extraoutputs + macro(BISON_TARGET_option_verbose Name BisonOutput filename) + cmake_policy(GET CMP0088 _BISON_CMP0088 + PARENT_SCOPE # undocumented, do not use outside of CMake + ) + set(_BISON_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + if("x${_BISON_CMP0088}x" STREQUAL "xNEWx") + set(_BISON_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + unset(_BISON_CMP0088) + + list(APPEND BISON_TARGET_cmdopt "--verbose") + list(APPEND BISON_TARGET_outputs + "${BISON_TARGET_verbose_file}") + if (NOT "${filename}" STREQUAL "") + if(IS_ABSOLUTE "${filename}") + set(BISON_TARGET_verbose_extra_file "${filename}") + else() + set(BISON_TARGET_verbose_extra_file "${_BISON_WORKING_DIRECTORY}/${filename}") + endif() + + add_custom_command(OUTPUT ${BISON_TARGET_verbose_extra_file} + COMMAND ${CMAKE_COMMAND} -E copy + "${BISON_TARGET_verbose_file}" + "${filename}" + VERBATIM + DEPENDS + "${BISON_TARGET_verbose_file}" + COMMENT "[BISON][${Name}] Copying bison verbose table to ${filename}" + WORKING_DIRECTORY ${_BISON_WORKING_DIRECTORY}) + list(APPEND BISON_TARGET_extraoutputs + "${BISON_TARGET_verbose_extra_file}") + unset(BISON_TARGET_verbose_extra_file) + unset(_BISON_WORKING_DIRECTORY) + endif() + endmacro() + + #============================================================ + # BISON_TARGET (public macro) + #============================================================ + # + macro(BISON_TARGET Name BisonInput BisonOutput) + set(BISON_TARGET_outputs "${BisonOutput}") + set(BISON_TARGET_extraoutputs "") + + # Parsing parameters + set(BISON_TARGET_PARAM_OPTIONS + ) + set(BISON_TARGET_PARAM_ONE_VALUE_KEYWORDS + COMPILE_FLAGS + DEFINES_FILE + REPORT_FILE + ) + set(BISON_TARGET_PARAM_MULTI_VALUE_KEYWORDS + VERBOSE + ) + cmake_parse_arguments( + BISON_TARGET_ARG + "${BISON_TARGET_PARAM_OPTIONS}" + "${BISON_TARGET_PARAM_ONE_VALUE_KEYWORDS}" + "${BISON_TARGET_PARAM_MULTI_VALUE_KEYWORDS}" + ${ARGN} + ) + + if(NOT "${BISON_TARGET_ARG_UNPARSED_ARGUMENTS}" STREQUAL "") + message(SEND_ERROR "Usage") + elseif("${BISON_TARGET_ARG_VERBOSE}" MATCHES ";") + # [VERBOSE [] hack: is non-multi value by usage + message(SEND_ERROR "Usage") + else() + + BISON_TARGET_option_extraopts("${BISON_TARGET_ARG_COMPILE_FLAGS}") + BISON_TARGET_option_defines("${BisonOutput}" "${BISON_TARGET_ARG_DEFINES_FILE}") + BISON_TARGET_option_report_file("${BisonOutput}" "${BISON_TARGET_ARG_REPORT_FILE}") + if(NOT "${BISON_TARGET_ARG_VERBOSE}" STREQUAL "") + BISON_TARGET_option_verbose(${Name} ${BisonOutput} "${BISON_TARGET_ARG_VERBOSE}") + else() + # [VERBOSE []] is used with no argument or is not used + set(BISON_TARGET_args "${ARGN}") + list(FIND BISON_TARGET_args "VERBOSE" BISON_TARGET_args_indexof_verbose) + if(${BISON_TARGET_args_indexof_verbose} GREATER -1) + # VERBOSE is used without + BISON_TARGET_option_verbose(${Name} ${BisonOutput} "") + endif() + endif() + + list(APPEND BISON_TARGET_outputs "${BISON_TARGET_output_header}") + + cmake_policy(GET CMP0088 _BISON_CMP0088 + PARENT_SCOPE # undocumented, do not use outside of CMake + ) + set(_BISON_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + set(_BisonInput "${BisonInput}") + if("x${_BISON_CMP0088}x" STREQUAL "xNEWx") + set(_BISON_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(NOT IS_ABSOLUTE "${_BisonInput}") + set(_BisonInput "${CMAKE_CURRENT_SOURCE_DIR}/${_BisonInput}") + endif() + endif() + unset(_BISON_CMP0088) + + add_custom_command(OUTPUT ${BISON_TARGET_outputs} + COMMAND ${CMAKE_COMMAND} -E env "M4=${BISON_M4_EXECUTABLE}" + ${BISON_EXECUTABLE} ${BISON_TARGET_cmdopt} -o ${BisonOutput} ${_BisonInput} + VERBATIM + DEPENDS ${_BisonInput} + COMMENT "[BISON][${Name}] Building parser with bison ${BISON_VERSION}" + WORKING_DIRECTORY ${_BISON_WORKING_DIRECTORY}) + + unset(_BISON_WORKING_DIRECTORY) + + # define target variables + set(BISON_${Name}_DEFINED TRUE) + set(BISON_${Name}_INPUT ${_BisonInput}) + set(BISON_${Name}_OUTPUTS ${BISON_TARGET_outputs} ${BISON_TARGET_extraoutputs}) + set(BISON_${Name}_COMPILE_FLAGS ${BISON_TARGET_cmdopt}) + set(BISON_${Name}_OUTPUT_SOURCE "${BisonOutput}") + set(BISON_${Name}_OUTPUT_HEADER "${BISON_TARGET_output_header}") + + unset(_BisonInput) + + endif() + endmacro() + # + #============================================================ + +endif() + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(BISON REQUIRED_VARS BISON_EXECUTABLE + VERSION_VAR BISON_VERSION)