# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause

# This function creates a CMake target for a generic console or GUI binary.
# Please consider to use a more specific version target like the one created
# by qt_add_test or qt_add_tool below.
# One-value Arguments:
#     CORE_LIBRARY
#         The argument accepts 'Bootstrap' or 'None' values. If the argument value is set to
#         'Bootstrap' the Qt::Bootstrap library is linked to the executable instead of Qt::Core.
#         The 'None' value points that core library is not necessary and avoids linking neither
#         Qt::Core or Qt::Bootstrap libraries. Otherwise the Qt::Core library will be publicly
#         linked to the executable target by default.
function(qt_internal_add_executable name)
    cmake_parse_arguments(PARSE_ARGV 1 arg
        "${__qt_internal_add_executable_optional_args}"
        "${__qt_internal_add_executable_single_args}"
        "${__qt_internal_add_executable_multi_args}")
    _qt_internal_validate_all_args_are_parsed(arg)

    if ("x${arg_OUTPUT_DIRECTORY}" STREQUAL "x")
        set(arg_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_BINDIR}")
    endif()

    get_filename_component(arg_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
        ABSOLUTE BASE_DIR "${QT_BUILD_DIR}")

    if ("x${arg_INSTALL_DIRECTORY}" STREQUAL "x")
        set(arg_INSTALL_DIRECTORY "${INSTALL_BINDIR}")
    endif()

    _qt_internal_create_executable(${name})
    qt_internal_mark_as_internal_target(${name})

    set_target_properties(${name} PROPERTIES
        _qt_is_test_executable ${arg_QT_TEST}
        _qt_is_manual_test ${arg_QT_MANUAL_TEST}
        _qt_is_benchmark_test ${arg_QT_BENCHMARK_TEST}
    )

    # Opt out to skip the new way of running test finalizers, and instead use the old way for
    # specific platforms.
    # TODO: Remove once we confirm that the new way of running test finalizers for all platforms
    # doesn't cause any issues.
    if(NOT QT_INTERNAL_SKIP_TEST_FINALIZERS_V2)
        # We don't run finalizers for all executables on all platforms, because there are still
        # some unsolved issues there. One of them is trying to run finalizers for the
        # qmlimportscanner executable would create a circular depenendecy trying to run
        # qmlimportscanner on itself.
        #
        # For now, we only run finalizers for test-like executables on all platforms, and all
        # android and wasm internal executables.
        # For android and wasm all executables, to be behavior compatible with the old way of
        # running finalizers.
        if(ANDROID
            OR WASM
            OR arg_QT_TEST
            OR arg_QT_MANUAL_TEST
            OR arg_QT_BENCHMARK_TEST)
            set(QT_INTERNAL_USE_POOR_MANS_SCOPE_FINALIZER TRUE)
            _qt_internal_finalize_target_defer("${name}")
        endif()
    else()
        if(ANDROID)
            # This direct calls the finalizer, which in the v2 way is deferred.
            _qt_internal_android_executable_finalizer(${name})
        endif()
        if(WASM)
            # This defer calls the finalizer.
            qt_internal_wasm_add_finalizers(${name})
        endif()
    endif()

    if(arg_QT_APP AND QT_FEATURE_debug_and_release AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.19.0")
        set_property(TARGET "${name}"
            PROPERTY EXCLUDE_FROM_ALL "$<NOT:$<CONFIG:${QT_MULTI_CONFIG_FIRST_CONFIG}>>")
    endif()

    if (arg_VERSION)
        if(arg_VERSION MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")
            # nothing to do
        elseif(arg_VERSION MATCHES "[0-9]+\\.[0-9]+\\.[0-9]+")
            set(arg_VERSION "${arg_VERSION}.0")
        elseif(arg_VERSION MATCHES "[0-9]+\\.[0-9]+")
            set(arg_VERSION "${arg_VERSION}.0.0")
        elseif (arg_VERSION MATCHES "[0-9]+")
            set(arg_VERSION "${arg_VERSION}.0.0.0")
        else()
            message(FATAL_ERROR "Invalid version format")
        endif()
    endif()

    if(arg_DELAY_TARGET_INFO)
        # Delay the setting of target info properties if requested. Needed for scope finalization
        # of Qt apps.
        set_target_properties("${name}" PROPERTIES
            QT_DELAYED_TARGET_VERSION "${arg_VERSION}"
            QT_DELAYED_TARGET_PRODUCT "${arg_TARGET_PRODUCT}"
            QT_DELAYED_TARGET_DESCRIPTION "${arg_TARGET_DESCRIPTION}"
            QT_DELAYED_TARGET_COMPANY "${arg_TARGET_COMPANY}"
            QT_DELAYED_TARGET_COPYRIGHT "${arg_TARGET_COPYRIGHT}"
            )
    else()
        if(NOT arg_TARGET_DESCRIPTION)
            set(arg_TARGET_DESCRIPTION "Qt ${name}")
        endif()
        qt_set_target_info_properties(${name} ${ARGN}
            TARGET_DESCRIPTION ${arg_TARGET_DESCRIPTION}
            TARGET_VERSION ${arg_VERSION})
    endif()

    if (WIN32 AND NOT arg_DELAY_RC)
        _qt_internal_generate_win32_rc_file(${name})
    endif()

    qt_set_common_target_properties(${name})

    qt_internal_add_repo_local_defines(${name})

    if(ANDROID)
        # The above call to qt_set_common_target_properties() sets the symbol
        # visibility to hidden, but for Android, we need main() to not be hidden
        # because it has to be loadable at runtime using dlopen().
        set_property(TARGET ${name} PROPERTY C_VISIBILITY_PRESET default)
        set_property(TARGET ${name} PROPERTY CXX_VISIBILITY_PRESET default)
    endif()

    qt_autogen_tools_initial_setup(${name})
    qt_skip_warnings_are_errors_when_repo_unclean("${name}")

    set(extra_libraries "")
    if(arg_CORE_LIBRARY STREQUAL "Bootstrap")
        list(APPEND extra_libraries ${QT_CMAKE_EXPORT_NAMESPACE}::Bootstrap)
    elseif(NOT arg_CORE_LIBRARY STREQUAL "None")
        list(APPEND extra_libraries ${QT_CMAKE_EXPORT_NAMESPACE}::Core)
    endif()

    set(private_includes
        "${CMAKE_CURRENT_SOURCE_DIR}"
        "${CMAKE_CURRENT_BINARY_DIR}"
         ${arg_INCLUDE_DIRECTORIES}
    )

    if(arg_PUBLIC_LIBRARIES)
        message(WARNING
            "qt_internal_add_executable's PUBLIC_LIBRARIES option is deprecated, and will be "
            "removed in a future Qt version. Use the LIBRARIES option instead.")
    endif()

    if(arg_NO_UNITY_BUILD)
        set(arg_NO_UNITY_BUILD "NO_UNITY_BUILD")
    else()
        set(arg_NO_UNITY_BUILD "")
    endif()

    qt_internal_extend_target("${name}"
        ${arg_NO_UNITY_BUILD}
        SOURCES ${arg_SOURCES}
        NO_PCH_SOURCES ${arg_NO_PCH_SOURCES}
        NO_UNITY_BUILD_SOURCES ${arg_NO_UNITY_BUILD_SOURCES}
        INCLUDE_DIRECTORIES ${private_includes}
        DEFINES ${arg_DEFINES}
        LIBRARIES
            ${arg_LIBRARIES}
            ${arg_PUBLIC_LIBRARIES}
            Qt::PlatformCommonInternal
            ${extra_libraries}
        DBUS_ADAPTOR_SOURCES ${arg_DBUS_ADAPTOR_SOURCES}
        DBUS_ADAPTOR_FLAGS ${arg_DBUS_ADAPTOR_FLAGS}
        DBUS_INTERFACE_SOURCES ${arg_DBUS_INTERFACE_SOURCES}
        DBUS_INTERFACE_FLAGS ${arg_DBUS_INTERFACE_FLAGS}
        COMPILE_OPTIONS ${arg_COMPILE_OPTIONS}
        LINK_OPTIONS ${arg_LINK_OPTIONS}
        MOC_OPTIONS ${arg_MOC_OPTIONS}
        ENABLE_AUTOGEN_TOOLS ${arg_ENABLE_AUTOGEN_TOOLS}
        DISABLE_AUTOGEN_TOOLS ${arg_DISABLE_AUTOGEN_TOOLS}
    )
    set_target_properties("${name}" PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
        LIBRARY_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
        WIN32_EXECUTABLE "${arg_GUI}"
        MACOSX_BUNDLE "${arg_GUI}"
    )

    qt_internal_set_exceptions_flags("${name}" ${arg_EXCEPTIONS})

    # Check if target needs to be excluded from all target. Also affects qt_install.
    # Set by qt_exclude_tool_directories_from_default_target.
    set(exclude_from_all FALSE)
    if(__qt_exclude_tool_directories)
        foreach(absolute_dir ${__qt_exclude_tool_directories})
            string(FIND "${CMAKE_CURRENT_SOURCE_DIR}" "${absolute_dir}" dir_starting_pos)
            if(dir_starting_pos EQUAL 0)
                set(exclude_from_all TRUE)
                set_target_properties("${name}" PROPERTIES
                    EXCLUDE_FROM_ALL TRUE
                    _qt_internal_excluded_from_default_target TRUE
                )
                break()
            endif()
        endforeach()
    endif()

    if(NOT arg_NO_INSTALL)
        set(additional_install_args "")
        if(exclude_from_all)
            list(APPEND additional_install_args EXCLUDE_FROM_ALL COMPONENT "ExcludedExecutables")
        endif()

        qt_get_cmake_configurations(cmake_configs)
        foreach(cmake_config ${cmake_configs})
            qt_get_install_target_default_args(
                OUT_VAR install_targets_default_args
                CMAKE_CONFIG "${cmake_config}"
                ALL_CMAKE_CONFIGS ${cmake_configs}
                RUNTIME "${arg_INSTALL_DIRECTORY}"
                LIBRARY "${arg_INSTALL_DIRECTORY}"
                BUNDLE "${arg_INSTALL_DIRECTORY}")

            # Make installation optional for targets that are not built by default in this config
            if(NOT exclude_from_all AND arg_QT_APP AND QT_FEATURE_debug_and_release
                    AND NOT (cmake_config STREQUAL QT_MULTI_CONFIG_FIRST_CONFIG))
                set(install_optional_arg "OPTIONAL")
            else()
                unset(install_optional_arg)
            endif()

            qt_install(TARGETS "${name}"
                       ${additional_install_args} # Needs to be before the DESTINATIONS.
                       ${install_optional_arg}
                       CONFIGURATIONS ${cmake_config}
                       ${install_targets_default_args})
        endforeach()

        if(NOT exclude_from_all AND arg_QT_APP AND QT_FEATURE_debug_and_release)
            set(separate_debug_info_executable_arg "QT_EXECUTABLE")
        else()
            unset(separate_debug_info_executable_arg)
        endif()

        qt_internal_defer_separate_debug_info("${name}"
            SEPARATE_DEBUG_INFO_ARGS
                "${arg_INSTALL_DIRECTORY}"
                ${separate_debug_info_executable_arg}
                ADDITIONAL_INSTALL_ARGS ${additional_install_args}
        )
        qt_internal_install_pdb_files(${name} "${arg_INSTALL_DIRECTORY}")
    endif()

    if(QT_GENERATE_SBOM)
        set(sbom_args "")
        _qt_internal_forward_function_args(
            FORWARD_APPEND
            FORWARD_PREFIX arg
            FORWARD_OUT_VAR sbom_args
            FORWARD_OPTIONS
                ${__qt_internal_sbom_optional_args}
            FORWARD_SINGLE
                ${__qt_internal_sbom_single_args}
            FORWARD_MULTI
                ${__qt_internal_sbom_multi_args}
        )

        _qt_internal_extend_sbom(${name} ${sbom_args})
    endif()

    qt_add_list_file_finalizer(qt_internal_finalize_executable "${name}")
endfunction()

# Finalizer for all generic internal executables.
function(qt_internal_finalize_executable target)
    qt_internal_finalize_executable_separate_debug_info("${target}")
endfunction()

# This function compiles the target at configure time the very first time and creates the custom
# ${target}_build that re-runs compilation at build time if necessary. The resulting executable is
# imported under the provided target name. This function should only be used to compile tiny
# executables with system dependencies only.
# One-value Arguments:
#     CMAKELISTS_TEMPLATE
#         The CMakeLists.txt templated that is used to configure the project
#         for an executable. By default the predefined template from the Qt installation is used.
#     INSTALL_DIRECTORY
#         installation directory of the executable. Ignored if NO_INSTALL is set.
#     OUTPUT_NAME
#         the output name of an executable
#     CONFIG
#         the name of configuration that tool needs to be build with.
# Multi-value Arguments:
#     PACKAGES
#         list of system packages are required to successfully build the project.
#     INCLUDES
#         list of include directories are required to successfully build the project.
#     DEFINES
#         list of definitions are required to successfully build the project.
#     COMPILE_OPTIONS
#         list of compiler options are required to successfully build the project.
#     LINK_OPTIONS
#         list of linker options are required to successfully build the project.
#     SOURCES
#         list of project sources.
#     CMAKE_FLAGS
#         specify flags of the form -DVAR:TYPE=VALUE to be passed to the cmake command-line used to
#         drive the test build.
# Options:
#     WIN32
#         reflects the corresponding add_executable argument.
#     MACOSX_BUNDLE
#         reflects the corresponding add_executable argument.
#     NO_INSTALL
#         avoids installing the tool.
function(qt_internal_add_configure_time_executable target)
    set(one_value_args
        CMAKELISTS_TEMPLATE
        INSTALL_DIRECTORY
        OUTPUT_NAME
        CONFIG
    )
    set(multi_value_args
        PACKAGES
        INCLUDES
        DEFINES
        COMPILE_OPTIONS
        LINK_OPTIONS
        SOURCES
        CMAKE_FLAGS
    )
    set(option_args WIN32 MACOSX_BUNDLE NO_INSTALL)
    cmake_parse_arguments(PARSE_ARGV 1 arg
        "${option_args}" "${one_value_args}" "${multi_value_args}")

    set(target_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/configure_time_bins")
    if(arg_CONFIG)
        set(CMAKE_TRY_COMPILE_CONFIGURATION "${arg_CONFIG}")
        string(TOUPPER "_${arg_CONFIG}" config_suffix)
    endif()

    get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
    if(is_multi_config AND CMAKE_TRY_COMPILE_CONFIGURATION)
        set(configuration_path "${CMAKE_TRY_COMPILE_CONFIGURATION}/")
        set(config_build_arg "--config" "${CMAKE_TRY_COMPILE_CONFIGURATION}")
    endif()

    set(configure_time_target "${target}")
    if(arg_OUTPUT_NAME)
        set(configure_time_target "${arg_OUTPUT_NAME}")
    endif()
    set(target_binary "${configure_time_target}${CMAKE_EXECUTABLE_SUFFIX}")

    set(install_dir "${INSTALL_BINDIR}")
    if(arg_INSTALL_DIRECTORY)
        set(install_dir "${arg_INSTALL_DIRECTORY}")
    endif()

    set(output_directory_relative "${install_dir}")
    set(output_directory "${QT_BUILD_DIR}/${install_dir}")

    set(target_binary_path_relative
        "${output_directory_relative}/${configuration_path}${target_binary}")
    set(target_binary_path
        "${output_directory}/${configuration_path}${target_binary}")

    get_filename_component(target_binary_path "${target_binary_path}" ABSOLUTE)

    if(NOT DEFINED arg_SOURCES)
        message(FATAL_ERROR "No SOURCES given to target: ${target}")
    endif()
    set(sources "${arg_SOURCES}")

    # Timestamp file is required because CMake ignores 'add_custom_command' if we use only the
    # binary file as the OUTPUT.
    set(timestamp_file "${target_binary_dir}/${target_binary}_timestamp")
    add_custom_command(OUTPUT "${target_binary_path}" "${timestamp_file}"
        COMMAND
            ${CMAKE_COMMAND} --build "${target_binary_dir}" --clean-first ${config_build_arg}
        COMMAND
            ${CMAKE_COMMAND} -E touch "${timestamp_file}"
        DEPENDS
            ${sources}
        COMMENT
            "Compiling ${target}"
        VERBATIM
    )

    add_custom_target(${target}_build ALL
        DEPENDS
            "${target_binary_path}"
            "${timestamp_file}"
    )

    set(should_build_at_configure_time TRUE)
    if(QT_INTERNAL_HAVE_CONFIGURE_TIME_${target} AND
        EXISTS "${target_binary_path}" AND EXISTS "${timestamp_file}")
        set(last_ts 0)
        foreach(source IN LISTS sources)
            file(TIMESTAMP "${source}" ts "%s")
            if(${ts} GREATER ${last_ts})
                set(last_ts ${ts})
            endif()
        endforeach()

        file(TIMESTAMP "${target_binary_path}" ts "%s")
        if(${ts} GREATER_EQUAL ${last_ts})
            set(should_build_at_configure_time FALSE)
        endif()
    endif()

    set(cmake_flags_arg "")
    if(arg_CMAKE_FLAGS)
        set(cmake_flags_arg CMAKE_FLAGS "${arg_CMAKE_FLAGS}")
    endif()

    qt_internal_get_enabled_languages_for_flag_manipulation(enabled_languages)
    foreach(lang IN LISTS enabled_languages)
        set(compiler_flags_var "CMAKE_${lang}_FLAGS")
        list(APPEND cmake_flags_arg "-D${compiler_flags_var}:STRING=${${compiler_flags_var}}")
        if(arg_CONFIG)
            set(compiler_flags_var_config "${compiler_flags_var}${config_suffix}")
            list(APPEND cmake_flags_arg
                "-D${compiler_flags_var_config}:STRING=${${compiler_flags_var_config}}")
        endif()
    endforeach()

    qt_internal_get_target_link_types_for_flag_manipulation(target_link_types)
    foreach(linker_type IN LISTS target_link_types)
        set(linker_flags_var "CMAKE_${linker_type}_LINKER_FLAGS")
        list(APPEND cmake_flags_arg "-D${linker_flags_var}:STRING=${${linker_flags_var}}")
        if(arg_CONFIG)
            set(linker_flags_var_config "${linker_flags_var}${config_suffix}")
            list(APPEND cmake_flags_arg
                "-D${linker_flags_var_config}:STRING=${${linker_flags_var_config}}")
        endif()
    endforeach()

    if(NOT "${QT_INTERNAL_CMAKE_FLAGS_CONFIGURE_TIME_TOOL_${target}}" STREQUAL "${cmake_flags_arg}")
        set(should_build_at_configure_time TRUE)
    endif()

    if(should_build_at_configure_time)
        foreach(arg IN LISTS multi_value_args)
            string(TOLOWER "${arg}" template_arg_name)
            set(${template_arg_name} "")
            if(DEFINED arg_${arg})
                set(${template_arg_name} "${arg_${arg}}")
            endif()
        endforeach()

        foreach(arg IN LISTS option_args)
            string(TOLOWER "${arg}" template_arg_name)
            set(${template_arg_name} "")
            if(arg_${arg})
                set(${template_arg_name} "${arg}")
            endif()
        endforeach()

        file(MAKE_DIRECTORY "${target_binary_dir}")
        set(template "${QT_CMAKE_DIR}/QtConfigureTimeExecutableCMakeLists.txt.in")
        if(DEFINED arg_CMAKELISTS_TEMPLATE)
            set(template "${arg_CMAKELISTS_TEMPLATE}")
        endif()

        configure_file("${template}" "${target_binary_dir}/CMakeLists.txt" @ONLY)

        if(EXISTS "${target_binary_dir}/CMakeCache.txt")
            file(REMOVE "${target_binary_dir}/CMakeCache.txt")
        endif()

        try_compile(result
            "${target_binary_dir}"
            "${target_binary_dir}"
            ${target}
            ${cmake_flags_arg}
            OUTPUT_VARIABLE try_compile_output
        )

        set(QT_INTERNAL_CMAKE_FLAGS_CONFIGURE_TIME_TOOL_${target}
            "${cmake_flags_arg}" CACHE INTERNAL "")

        file(WRITE "${timestamp_file}" "")
        set(QT_INTERNAL_HAVE_CONFIGURE_TIME_${target} ${result} CACHE INTERNAL
            "Indicates that the configure-time target ${target} was built")
        if(NOT result)
            message(FATAL_ERROR "Unable to build ${target}: ${try_compile_output}")
        endif()
    endif()

    add_executable(${target} IMPORTED GLOBAL)
    add_executable(${QT_CMAKE_EXPORT_NAMESPACE}::${target} ALIAS ${target})
    set_target_properties(${target} PROPERTIES
        _qt_internal_configure_time_target TRUE
        _qt_internal_configure_time_target_build_location "${target_binary_path_relative}"
        IMPORTED_LOCATION "${target_binary_path}"
    )

    if(NOT arg_NO_INSTALL)
        set_target_properties(${target} PROPERTIES
            _qt_internal_configure_time_target_install_location
                "${install_dir}/${target_binary}"
        )
        qt_path_join(target_install_dir ${QT_INSTALL_DIR} ${install_dir})
        qt_install(PROGRAMS "${target_binary_path}" DESTINATION "${target_install_dir}")
    endif()
endfunction()
