wip
This commit is contained in:
parent
a0d72f177e
commit
583c07ad29
401
utils.cmake
401
utils.cmake
|
@ -106,7 +106,7 @@ function(compute_install_dir)
|
|||
endfunction()
|
||||
|
||||
function(setup_project)
|
||||
cmake_parse_arguments(PROJ_ARGS "QT_PROJECT;ENABLE_LTO;WARNINGS_ARE_ERRORS;MAX_WARNING;NO_WARNING;RTTI;EXCEPTIONS" "QML_IMPORT_DIR" "LANGUAGES" ${ARGN})
|
||||
cmake_parse_arguments(PROJ_ARGS "QT_PROJECT;ENABLE_LTO;WARNINGS_ARE_ERRORS;MAX_WARNING;NO_WARNING;RTTI;EXCEPTIONS" "QML_IMPORT_DIR;LICENSE_HEADER;MODIFY_LOST_WARNING;CONFIG_PREFIX;VERSION_PREFIX" "LANGUAGES" ${ARGN})
|
||||
if(NOT PROJ_ARGS_LANGUAGES)
|
||||
message(AUTHOR_WARNING "setup_project: You need to specify at least one language for this function!")
|
||||
return()
|
||||
|
@ -524,6 +524,22 @@ function(setup_project)
|
|||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
set(__meta_target ${PROJECT_NAME}_METADATA)
|
||||
if(NOT TARGET ${__meta_target})
|
||||
add_custom_target(${__meta_target})
|
||||
endif()
|
||||
if(arg_LICENSE_HEADER)
|
||||
set_target_properties(${__meta_target} PROPERTIES __PROJECT_LICENSE_HEADER "${arg_LICENSE_HEADER}")
|
||||
endif()
|
||||
if(arg_MODIFY_LOST_WARNING)
|
||||
set_target_properties(${__meta_target} PROPERTIES __PROJECT_MODIFY_LOST_WARNING "${arg_MODIFY_LOST_WARNING}")
|
||||
endif()
|
||||
if(arg_CONFIG_PREFIX)
|
||||
set_target_properties(${__meta_target} PROPERTIES __PROJECT_CONFIG_PREFIX "${arg_CONFIG_PREFIX}")
|
||||
endif()
|
||||
if(arg_VERSION_PREFIX)
|
||||
set_target_properties(${__meta_target} PROPERTIES __PROJECT_VERSION_PREFIX "${arg_VERSION_PREFIX}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(get_commit_hash)
|
||||
|
@ -1913,3 +1929,386 @@ function(setup_target_rpaths)
|
|||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(add_project_config)
|
||||
set(__target ${PROJECT_NAME}_METADATA)
|
||||
if(NOT TARGET ${__target})
|
||||
message(AUTHOR_WARNING "add_project_config: You need to call setup_project() first!")
|
||||
return()
|
||||
endif()
|
||||
cmake_parse_arguments(arg "" "KEY;VALUE" "" ${ARGN})
|
||||
if(NOT arg_KEY)
|
||||
message(AUTHOR_WARNING "add_project_config: You need to specify the KEY name!")
|
||||
return()
|
||||
endif()
|
||||
if(NOT arg_VALUE)
|
||||
message(AUTHOR_WARNING "add_project_config: You need to specify the VALUE value!")
|
||||
return()
|
||||
endif()
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "add_project_config: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
set(__prefix "")
|
||||
get_target_property(__prefix ${__target} __PROJECT_CONFIG_PREFIX)
|
||||
if("x${__prefix}" STREQUAL "x")
|
||||
set(__prefix "${PROJECT_NAME}_FEATURE")
|
||||
message(AUTHOR_WARNING "add_project_config: config prefix is not set, using ${__prefix} as default.")
|
||||
endif()
|
||||
set_property(TARGET ${__target} APPEND PROPERTY __PROJECT_CONFIG_OPTIONS "${__prefix}_${arg_KEY}=${arg_VALUE}")
|
||||
endfunction()
|
||||
|
||||
function(enable_project_config)
|
||||
cmake_parse_arguments(arg "" "KEY" "" ${ARGN})
|
||||
if(NOT arg_KEY)
|
||||
message(AUTHOR_WARNING "enable_project_config: You need to specify the KEY name!")
|
||||
return()
|
||||
endif()
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "enable_project_config: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
add_project_config(KEY "${arg_KEY}" TRUE)
|
||||
endfunction()
|
||||
|
||||
function(disable_project_config)
|
||||
cmake_parse_arguments(arg "" "KEY" "" ${ARGN})
|
||||
if(NOT arg_KEY)
|
||||
message(AUTHOR_WARNING "disable_project_config: You need to specify the KEY name!")
|
||||
return()
|
||||
endif()
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "disable_project_config: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
add_project_config(KEY "${arg_KEY}" FALSE)
|
||||
endfunction()
|
||||
|
||||
function(generate_project_config)
|
||||
set(__target ${PROJECT_NAME}_METADATA)
|
||||
if(NOT TARGET ${__target})
|
||||
message(AUTHOR_WARNING "generate_project_config: You need to call setup_project() first!")
|
||||
return()
|
||||
endif()
|
||||
cmake_parse_arguments(arg "OVERWRITE" "PATH" "" ${ARGN})
|
||||
if(NOT arg_PATH)
|
||||
message(AUTHOR_WARNING "generate_project_config: You need to specify the file path!")
|
||||
return()
|
||||
endif()
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "generate_project_config: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
if(EXISTS "${arg_PATH}" AND NOT arg_OVERWRITE)
|
||||
return()
|
||||
endif()
|
||||
set(__raw_options "")
|
||||
get_target_property(__raw_options ${__target} __PROJECT_CONFIG_OPTIONS)
|
||||
if("x${__raw_options}" STREQUAL "x")
|
||||
message(AUTHOR_WARNING "generate_project_config: You need to specify at least one configuration option!")
|
||||
return()
|
||||
endif()
|
||||
set(__license_header "")
|
||||
get_target_property(__license_header ${__target} __PROJECT_LICENSE_HEADER)
|
||||
if("x${__license_header}" STREQUAL "x")
|
||||
message(AUTHOR_WARNING "generate_project_config: The license header is not set.")
|
||||
endif()
|
||||
set(__modify_lost_warning "")
|
||||
get_target_property(__modify_lost_warning ${__target} __PROJECT_MODIFY_LOST_WARNING)
|
||||
if("x${__modify_lost_warning}" STREQUAL "x")
|
||||
message(AUTHOR_WARNING "generate_project_config: The modification lost warning is not set.")
|
||||
endif()
|
||||
set(__inc_guard "_${PROJECT_NAME}_CONFIG_INCLUDE_GUARD_")
|
||||
set(__options "")
|
||||
foreach(__raw_option ${__raw_options})
|
||||
set(__length -1)
|
||||
string(LENGTH "${__raw_option}" __length)
|
||||
if(__length LESS_EQUAL 0)
|
||||
continue()
|
||||
endif()
|
||||
set(__index -1)
|
||||
string(FIND "${__raw_option}" "=" __index)
|
||||
if(__index LESS_EQUAL 0)
|
||||
continue()
|
||||
endif()
|
||||
set(__option "")
|
||||
string(SUBSTRING "${__raw_option}" 0 ${__index} __option)
|
||||
if("x${__option}" STREQUAL "x")
|
||||
continue()
|
||||
endif()
|
||||
math(EXPR __value_index "${__index} + 1")
|
||||
math(EXPR __value_length "${__length} - ${__value_index}")
|
||||
set(__value "")
|
||||
string(SUBSTRING "${__raw_option}" ${__value_index} ${__value_length} __value)
|
||||
if("x${__value}" STREQUAL "TRUE" OR "x${__value}" STREQUAL "ENABLE" OR "x${__value}" STREQUAL "ON" OR "x${__value}" STREQUAL "YES" OR "x${__value}" STREQUAL "WANT" OR "x${__value}" STREQUAL "1")
|
||||
set(__value 1)
|
||||
else()
|
||||
set(__value -1)
|
||||
endif()
|
||||
string(APPEND __options "#define ${__option} ${__value}\n")
|
||||
endforeach()
|
||||
set(__config_content "${__license_header}
|
||||
|
||||
${__modify_lost_warning}
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ${__inc_guard}
|
||||
#define ${__inc_guard}
|
||||
|
||||
${__options}
|
||||
#endif // ${__inc_guard}
|
||||
")
|
||||
file(WRITE "${arg_PATH}" "${__config_content}")
|
||||
endfunction()
|
||||
|
||||
function(query_git_information)
|
||||
cmake_parse_arguments(arg "" "HASH;SUBJECT;AUTHOR;DATETIME;BRANCH" "" ${ARGN})
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "query_git_information: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
if(NOT Git_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
if(NOT Git_FOUND)
|
||||
message(AUTHOR_WARNING "query_git_information: Can't find the Git package!")
|
||||
return()
|
||||
endif()
|
||||
#message(STATUS "Git found: ${GIT_EXECUTABLE} (version ${GIT_VERSION_STRING})")
|
||||
if(arg_HASH)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" log -1 --pretty=format:%H
|
||||
OUTPUT_VARIABLE ${arg_HASH}
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE __result_code
|
||||
)
|
||||
endif()
|
||||
if(arg_SUBJECT)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" log -1 --pretty=format:%s
|
||||
OUTPUT_VARIABLE ${arg_SUBJECT}
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE __result_code
|
||||
)
|
||||
endif()
|
||||
if(arg_AUTHOR)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" log -1 --pretty=format:"%aN (%aE)"
|
||||
OUTPUT_VARIABLE ${arg_AUTHOR}
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE __result_code
|
||||
)
|
||||
endif()
|
||||
if(arg_DATETIME)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" log -1 --pretty=format:%aI
|
||||
OUTPUT_VARIABLE ${arg_DATETIME}
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE __result_code
|
||||
)
|
||||
endif()
|
||||
if(arg_BRANCH)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" symbolic-ref --quiet --short HEAD
|
||||
OUTPUT_VARIABLE ${arg_BRANCH}
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE __result_code
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(query_compiler_information)
|
||||
cmake_parse_arguments(arg "" "NAME;VENDOR;VERSION;HOMEPAGE" "" ${ARGN})
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "query_compiler_information: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
set(__lang "C")
|
||||
set(__id "${CMAKE_${__lang}_COMPILER_ID}")
|
||||
if("x${__id}" STREQUAL "x")
|
||||
set(__lang "CXX")
|
||||
set(__id "${CMAKE_${__lang}_COMPILER_ID}")
|
||||
endif()
|
||||
if("x${__id}" STREQUAL "x")
|
||||
message(AUTHOR_WARNING "query_compiler_information: Can't find a valid C/C++ toolchain!")
|
||||
return()
|
||||
endif()
|
||||
if(arg_VERSION)
|
||||
set(${arg_VERSION} "${CMAKE_${__lang}_COMPILER_VERSION}" PARENT_SCOPE)
|
||||
endif()
|
||||
if(__id MATCHES "Clang")
|
||||
if(arg_NAME)
|
||||
set(${arg_NAME} "Clang" PARENT_SCOPE)
|
||||
endif()
|
||||
if(__id STREQUAL "AppleClang")
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "Apple" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "https://opensource.apple.com/source/clang/" PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(__id STREQUAL "ARMClang")
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "Arm" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "https://developer.arm.com/documentation/100748/latest/" PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(__id STREQUAL "IBMClang" OR __id STREQUAL "XLClang")
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "IBM" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "https://www.ibm.com/docs/en/openxl-c-and-cpp-aix" PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(__id STREQUAL "Clang")
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "LLVM" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "https://clang.llvm.org/" PARENT_SCOPE)
|
||||
endif()
|
||||
else()
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "UNKNOWN" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "UNKNOWN" PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
elseif(__id STREQUAL "GNU")
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "GNU Compiler Collection" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "https://gcc.gnu.org/" PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(__id STREQUAL "MSVC")
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "Microsoft" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "https://visualstudio.microsoft.com/" PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(__id MATCHES "Intel")
|
||||
else()
|
||||
if(arg_VENDOR)
|
||||
set(${arg_VENDOR} "UNKNOWN" PARENT_SCOPE)
|
||||
endif()
|
||||
if(arg_HOMEPAGE)
|
||||
set(${arg_HOMEPAGE} "UNKNOWN" PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(generate_project_version)
|
||||
set(__target ${PROJECT_NAME}_METADATA)
|
||||
if(NOT TARGET ${__target})
|
||||
message(AUTHOR_WARNING "generate_project_version: You need to call setup_project() first!")
|
||||
return()
|
||||
endif()
|
||||
cmake_parse_arguments(arg "OVERWRITE;COMMIT_HASH;COMMIT_SUBJECT;COMMIT_AUTHOR;COMMIT_DATETIME;COMMIT_BRANCH;COMPILER_NAME;COMPILER_VENDOR;COMPILER_VERSION;COMPILER_HOMEPAGE" "PATH" "" ${ARGN})
|
||||
if(NOT arg_PATH)
|
||||
message(AUTHOR_WARNING "generate_project_version: You need to specify the file path!")
|
||||
return()
|
||||
endif()
|
||||
if(arg_UNPARSED_ARGUMENTS)
|
||||
message(AUTHOR_WARNING "generate_project_version: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
if(EXISTS "${arg_PATH}" AND NOT arg_OVERWRITE)
|
||||
return()
|
||||
endif()
|
||||
set(__license_header "")
|
||||
get_target_property(__license_header ${__target} __PROJECT_LICENSE_HEADER)
|
||||
if("x${__license_header}" STREQUAL "x")
|
||||
message(AUTHOR_WARNING "generate_project_version: The license header is not set.")
|
||||
endif()
|
||||
set(__modify_lost_warning "")
|
||||
get_target_property(__modify_lost_warning ${__target} __PROJECT_MODIFY_LOST_WARNING)
|
||||
if("x${__modify_lost_warning}" STREQUAL "x")
|
||||
message(AUTHOR_WARNING "generate_project_version: The modification lost warning is not set.")
|
||||
endif()
|
||||
set(__prefix "")
|
||||
get_target_property(__prefix ${__target} __PROJECT_VERSION_PREFIX)
|
||||
if("x${__prefix}" STREQUAL "x")
|
||||
set(__prefix "${PROJECT_NAME}_FEATURE")
|
||||
message(AUTHOR_WARNING "generate_project_version: config prefix is not set, using ${__prefix} as default.")
|
||||
endif()
|
||||
set(__inc_guard "_${PROJECT_NAME}_VERSION_INCLUDE_GUARD_")
|
||||
set(__versions "")
|
||||
set(__common_num "[[maybe_unused]] inline constexpr const unsigned long")
|
||||
set(__common_str "[[maybe_unused]] inline constexpr const char")
|
||||
string(APPEND __versions "${__common_num} ${__prefix}_VERSION_MAJOR = ${PROJECT_VERSION_MAJOR};\n")
|
||||
string(APPEND __versions "${__common_num} ${__prefix}_VERSION_MINOR = ${PROJECT_VERSION_MINOR};\n")
|
||||
string(APPEND __versions "${__common_num} ${__prefix}_VERSION_PATCH = ${PROJECT_VERSION_PATCH};\n")
|
||||
string(APPEND __versions "${__common_num} ${__prefix}_VERSION_TWEAK = ${PROJECT_VERSION_TWEAK};\n")
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_VERSION_STR[] = \"${PROJECT_VERSION}\";\n")
|
||||
string(APPEND __versions "#define __${__prefix}_VERSION_MAJOR__ ${PROJECT_VERSION_MAJOR}\n")
|
||||
string(APPEND __versions "#define __${__prefix}_VERSION_MINOR__ ${PROJECT_VERSION_MINOR}\n")
|
||||
string(APPEND __versions "#define __${__prefix}_VERSION_PATCH__ ${PROJECT_VERSION_PATCH}\n")
|
||||
string(APPEND __versions "#define __${__prefix}_VERSION_TWEAK__ ${PROJECT_VERSION_TWEAK}\n")
|
||||
string(APPEND __versions "#define __${__prefix}_VERSION__ ${PROJECT_VERSION_MINOR}\n")
|
||||
if(arg_COMMIT_HASH)
|
||||
set(__hash "")
|
||||
query_git_information(HASH __hash)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMMIT_HASH_STR[] = \"${__hash}\";\n")
|
||||
endif()
|
||||
if(arg_COMMIT_SUBJECT)
|
||||
set(__subject "")
|
||||
query_git_information(SUBJECT __subject)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMMIT_SUBJECT_STR[] = \"${__subject}\";\n")
|
||||
endif()
|
||||
if(arg_COMMIT_AUTHOR)
|
||||
set(__author "")
|
||||
query_git_information(AUTHOR __author)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMMIT_AUTHOR_STR[] = \"${__author}\";\n")
|
||||
endif()
|
||||
if(arg_COMMIT_DATETIME)
|
||||
set(__datetime "")
|
||||
query_git_information(DATETIME __datetime)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMMIT_DATETIME_STR[] = \"${__datetime}\";\n")
|
||||
endif()
|
||||
if(arg_COMMIT_BRANCH)
|
||||
set(__branch "")
|
||||
query_git_information(BRANCH __branch)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMMIT_BRANCH_STR[] = \"${__branch}\";\n")
|
||||
endif()
|
||||
if(arg_COMPILER_NAME)
|
||||
set(__name "")
|
||||
query_compiler_information(NAME __name)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMPILER_NAME_STR[] = \"${__name}\";\n")
|
||||
endif()
|
||||
if(arg_COMPILER_VERSION)
|
||||
set(__version "")
|
||||
query_compiler_information(VERSION __version)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMPILER_VERSION_STR[] = \"${__version}\";\n")
|
||||
endif()
|
||||
if(arg_COMPILER_VENDOR)
|
||||
set(__vendor "")
|
||||
query_compiler_information(VENDOR __vendor)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMPILER_VENDOR_STR[] = \"${__vendor}\";\n")
|
||||
endif()
|
||||
if(arg_COMPILER_HOMEPAGE)
|
||||
set(__homepage "")
|
||||
query_compiler_information(NAME __homepage)
|
||||
string(APPEND __versions "${__common_str} ${__prefix}_COMPILER_HOMEPAGE_STR[] = \"${__homepage}\";\n")
|
||||
endif()
|
||||
set(__version_content "${__license_header}
|
||||
|
||||
${__modify_lost_warning}
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ${__inc_guard}
|
||||
#define ${__inc_guard}
|
||||
|
||||
${__versions}
|
||||
#endif // ${__inc_guard}
|
||||
")
|
||||
file(WRITE "${arg_PATH}" "${__version_content}")
|
||||
endfunction()
|
||||
|
|
Loading…
Reference in New Issue