diff options
65 files changed, 2669 insertions, 1044 deletions
diff --git a/bin/qt-configure-module.bat.in b/bin/qt-configure-module.bat.in index aacb4e6aa32..4c2798c0fbd 100644 --- a/bin/qt-configure-module.bat.in +++ b/bin/qt-configure-module.bat.in @@ -53,12 +53,15 @@ set REDO_FILE_PATH=%TOPQTDIR%\config.redo.last set REDO_TMP_FILE_PATH=%TOPQTDIR%\config.redo.in set FRESH_REQUESTED_ARG= if not defined redoing ( + copy "%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\@side_car_file_name@" "%OPT_TMP_FILE_PATH%" + rem "The '.' in 'echo.%*' ensures we don't print "echo is off" when no arguments are passed" rem "https://devblogs.microsoft.com/oldnewthing/20170802-00/?p=96735" rem "The space before the '>' makes sure that when we have a digit at the end of the args, we" rem "don't accidentally concatenate it with the '>' resulting in '0>' or '2>' which redirects" rem "into the file from a stream different than stdout, leading to broken or empty content." - echo.%* >"%OPT_TMP_FILE_PATH%" + echo.%* >>"%OPT_TMP_FILE_PATH%" + echo.@__extra_configure_args@ >>"%OPT_TMP_FILE_PATH%" rem "The SKIP_ARGS option makes sure not to write the repo path into the config.opt file" call "%script_dir_path%\qt-cmake-private.bat" -DSKIP_ARGS=1 -DIN_FILE="%OPT_TMP_FILE_PATH%" ^ diff --git a/bin/qt-configure-module.in b/bin/qt-configure-module.in index 690fc014782..0c4e4f77a67 100755 --- a/bin/qt-configure-module.in +++ b/bin/qt-configure-module.in @@ -73,7 +73,7 @@ qt_cmake_private_path="$script_dir_path/../@INSTALL_LIBEXECDIR@" fresh_requested_arg= if [ -z "$optfile" ]; then # only write optfile if not currently redoing - > "$opttmpfilepath" + cp "$script_dir_path/@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@/@side_car_file_name@" "$opttmpfilepath" > "$redotmpfilepath" for arg in "$@"; do echo \"$arg\" >> "$opttmpfilepath"; done diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake index 84ab04db43d..91983636712 100644 --- a/cmake/QtBuildHelpers.cmake +++ b/cmake/QtBuildHelpers.cmake @@ -264,6 +264,7 @@ function(qt_internal_get_qt_build_private_files_to_install out_var) QtSeparateDebugInfo.Info.plist.in QtSetup.cmake QtStandaloneTestsConfig.cmake.in + QtVcpkgManifestHelpers.cmake QtVersionlessAliasTargets.cmake.in QtVersionlessTargets.cmake.in QtWriteArgsFile.cmake diff --git a/cmake/QtFeature.cmake b/cmake/QtFeature.cmake index 9bbf6e700b4..d8f031f5b27 100644 --- a/cmake/QtFeature.cmake +++ b/cmake/QtFeature.cmake @@ -59,6 +59,10 @@ function(qt_feature_module_begin) set(__QtFeature_define_definitions "" PARENT_SCOPE) endfunction() +# Define a Qt feature. +# +# The vcpkg-related arguments are relevant for configure only and are documented +# at the top of the qt_feature implementation in QtProcessConfigureArgs.cmake. function(qt_feature feature) set(original_name "${feature}") qt_feature_normalize_name("${feature}" feature) @@ -68,11 +72,14 @@ function(qt_feature feature) PRIVATE PUBLIC SYSTEM_LIBRARY + VCPKG_DEFAULT + VCPKG_OPTIONAL ) set(single_value_options LABEL PURPOSE SECTION + VCPKG_DESCRIPTION ) set(multi_value_options AUTODETECT @@ -80,6 +87,7 @@ function(qt_feature feature) ENABLE DISABLE EMIT_IF + VCPKG_DEPENDENT_FEATURES ) cmake_parse_arguments(PARSE_ARGV 1 arg "${no_value_options}" "${single_value_options}" "${multi_value_options}" @@ -389,6 +397,10 @@ function(qt_feature_deprecated feature) endif() endfunction() +function(qt_feature_vcpkg_scope name) + # This is just a stub. The real implementation is called at configure script time. +endfunction() + function(qt_evaluate_to_boolean expressionVar) if(${${expressionVar}}) set(${expressionVar} ON PARENT_SCOPE) @@ -816,9 +828,9 @@ endmacro() macro(_qt_internal_parse_feature_definition feature) cmake_parse_arguments(arg - "PRIVATE;PUBLIC;ALIAS_NEGATE" - "LABEL;PURPOSE;SECTION;ALIAS_OF_FEATURE;ALIAS_OF_CACHE" - "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" + "PRIVATE;PUBLIC;ALIAS_NEGATE;VCPKG_DEFAULT;VCPKG_OPTIONAL" + "LABEL;PURPOSE;SECTION;ALIAS_OF_FEATURE;ALIAS_OF_CACHE;VCPKG_DESCRIPTION" + "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF;VCPKG_DEPENDENT_FEATURES" ${_QT_FEATURE_DEFINITION_${feature}}) endmacro() diff --git a/cmake/QtFindPackageHelpers.cmake b/cmake/QtFindPackageHelpers.cmake index 7b15bfa4596..82538ab6ffe 100644 --- a/cmake/QtFindPackageHelpers.cmake +++ b/cmake/QtFindPackageHelpers.cmake @@ -10,12 +10,39 @@ # - Or remove the <builddir>/CMakeCache.txt file and configure from scratch # - Or remove the QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES cache variable (by # editing CMakeCache.txt) and reconfigure. +# +# It's possible to annotate the qt_find_package with information on how to +# generate a vcpkg manifest to satisfy this qt_find_package call. +# +# VCPKG_PORT <name> +# Name of the vcpkg port (package) to install. +# +# VCPKG_PLATFORM <expression> +# vcpkg platform expression, e.g. "!windows". +# See https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json#platform-expression +# +# VCPKG_VERSION <version> +# The minimum version of the vcpkg port to install +# See https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json#version +# +# VCPKG_ADD_TO_FEATURE <name> +# Add this port to the vcpkg feature with the given name. Create the feature if non-existent. +# +# VCPKG_DEFAULT_FEATURES <ON/OFF> +# ON by default. Set this to OFF to avoid that the vcpkg feature is added to +# vcpkg.json's default features. Only useful if VCPKG_ADD_TO_FEATURE is set. macro(qt_find_package) # Get the target names we expect to be provided by the package. set(find_package_options CONFIG NO_MODULE MODULE REQUIRED) set(options ${find_package_options} MARK_OPTIONAL) - set(oneValueArgs MODULE_NAME QMAKE_LIB) - set(multiValueArgs PROVIDED_TARGETS COMPONENTS OPTIONAL_COMPONENTS) + set(oneValueArgs MODULE_NAME QMAKE_LIB + VCPKG_ADD_TO_FEATURE + VCPKG_DEFAULT_FEATURES + VCPKG_PLATFORM + VCPKG_PORT + VCPKG_VERSION + ) + set(multiValueArgs PROVIDED_TARGETS COMPONENTS OPTIONAL_COMPONENTS VCPKG_FEATURES) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # If some Qt internal project calls qt_find_package(WrapFreeType), but WrapFreeType was already diff --git a/cmake/QtProcessConfigureArgs.cmake b/cmake/QtProcessConfigureArgs.cmake index bdedfb0b734..833daceba27 100644 --- a/cmake/QtProcessConfigureArgs.cmake +++ b/cmake/QtProcessConfigureArgs.cmake @@ -11,11 +11,13 @@ # If empty, qtbase/top-level is assumed. # TOP_LEVEL: TRUE, if this is a top-level build. +# The CMake version required for running the configure script. +# This must be less than or equal to the lowest QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT_*. +cmake_minimum_required(VERSION 3.19) + include(${CMAKE_CURRENT_LIST_DIR}/QtFeatureCommon.cmake) include(${CMAKE_CURRENT_LIST_DIR}/QtBuildInformation.cmake) - -cmake_policy(SET CMP0007 NEW) -cmake_policy(SET CMP0057 NEW) +include(${CMAKE_CURRENT_LIST_DIR}/QtVcpkgManifestHelpers.cmake) set(cmake_args "") macro(push) @@ -85,8 +87,11 @@ list(TRANSFORM configure_args STRIP) unset(generator) set(auto_detect_compiler TRUE) set(auto_detect_generator ${qtbase_or_top_level_build}) +set(dry_run FALSE) set(no_prefix_option FALSE) set(skipped_qtrepos "") +set(use_vcpkg FALSE) +set(generate_vcpkg_manifest "unknown") unset(device_options) unset(options_json_file) set_property(GLOBAL PROPERTY UNHANDLED_ARGS "") @@ -100,6 +105,20 @@ while(NOT "${configure_args}" STREQUAL "") list(POP_FRONT configure_args generator) elseif(arg STREQUAL "-cmake-use-default-generator") set(auto_detect_generator FALSE) + elseif(arg STREQUAL "-dry-run") + set(dry_run TRUE) + elseif(arg MATCHES "^-(no-)?vcpkg$") + if(CMAKE_MATCH_1 STREQUAL "no-") + set(use_vcpkg FALSE) + else() + set(use_vcpkg TRUE) + endif() + elseif(arg MATCHES "^-(no-)?generate-vcpkg-manifest$") + if(CMAKE_MATCH_1 STREQUAL "no-") + set(generate_vcpkg_manifest FALSE) + else() + set(generate_vcpkg_manifest TRUE) + endif() elseif(arg STREQUAL "-no-guess-compiler") set(auto_detect_compiler FALSE) elseif(arg STREQUAL "-list-features") @@ -171,6 +190,18 @@ while(NOT "${configure_args}" STREQUAL "") endif() endwhile() +# Turn on vcpkg usage if requested. +# Set the manifest directory to where we generate the manifest file. +if(use_vcpkg) + push(-DQT_USE_VCPKG=ON) + push("-DVCPKG_MANIFEST_DIR=${CMAKE_CURRENT_BINARY_DIR}") +endif() + +# By default, generate a manifest if using vcpkg. +if(generate_vcpkg_manifest STREQUAL "unknown") + set(generate_vcpkg_manifest ${use_vcpkg}) +endif() + # Read the specified manually generator value from CMake command line. # The '-cmake-generator' argument has higher priority than CMake command line. if(NOT generator) @@ -225,11 +256,108 @@ endif() set_property(GLOBAL PROPERTY COMMANDLINE_KNOWN_FEATURES "") +# Define a Qt feature. +# +# Arguments that start with VCPKG_ affect the creation of vcpkg features. If +# either VCPKG_DEFAULT or VCPGK_OPTIONAL are given, a corresponding vcpkg +# feature will be created. +# +# VCPKG_DEFAULT +# Specifies to create a vcpkg feature that will be added to the manifest's +# default features. +# +# VCPKG_OPTIONAL +# Specifies to create an optional vcpkg feature. +# +# VCPKG_DESCRIPTION <string> +# Optional description of the vcpkg feature. If not given, the description +# is derived from PURPOSE, or LABEL, or the feature name. +# +# VCPKG_DEPENDENT_FEATURES <features> +# List of vcpkg features this feature depends upon. +# For example, the "jpeg" feature would use "VCPKG_DEPENDENT_FEATURES gui". function(qt_feature feature) - cmake_parse_arguments(arg "" "PURPOSE;SECTION;" "" ${ARGN}) + set(no_value_options + VCPKG_DEFAULT + VCPKG_OPTIONAL + ) + set(single_value_options + LABEL + PURPOSE + SECTION + VCPKG_DESCRIPTION + ) + set(multi_value_options + VCPKG_DEPENDENT_FEATURES + ) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + set_property(GLOBAL APPEND PROPERTY COMMANDLINE_KNOWN_FEATURES "${feature}") set_property(GLOBAL PROPERTY COMMANDLINE_FEATURE_PURPOSE_${feature} "${arg_PURPOSE}") set_property(GLOBAL PROPERTY COMMANDLINE_FEATURE_SECTION_${feature} "${arg_SECTION}") + + if(NOT generate_vcpkg_manifest) + return() + endif() + + set(unknown_vcpkg_args "${arg_UNPARSED_ARGUMENTS}") + list(FILTER unknown_vcpkg_args INCLUDE REGEX "^VCPKG_") + if(NOT "${unknown_vcpkg_args}" STREQUAL "") + message(FATAL_ERROR "Unknown arguments passed to qt_feature: ${unknown_vcpkg_args}") + endif() + + set(create_vcpkg_feature FALSE) + if(arg_VCPKG_DEFAULT OR arg_VCPKG_OPTIONAL) + set(create_vcpkg_feature TRUE) + else() + get_cmake_property(vcpkg_features_to_create _QT_VCPKG_FEATURES_TO_CREATE) + if("${feature}" IN_LIST vcpkg_features_to_create) + set(create_vcpkg_feature TRUE) + list(REMOVE_ITEM vcpkg_features_to_create "${feature}") + set_property(GLOBAL PROPERTY + _QT_VCPKG_FEATURES_TO_CREATE ${vcpkg_features_to_create} + ) + endif() + endif() + + if(NOT create_vcpkg_feature) + return() + endif() + + # Determine the description + if(DEFINED arg_VCPKG_DESCRIPTION) + set(description "${arg_VCPKG_DESCRIPTION}") + elseif(DEFINED arg_PURPOSE) + set(description "${arg_PURPOSE}") + elseif(DEFINED arg_LABEL) + set(description "${arg_LABEL}") + else() + set(description "${feature} support") + endif() + + # Determine the dependent features (e.g. gui for freetype) + set(dependent_features "") + get_cmake_property(vcpkg_scope _QT_VCPKG_SCOPE) + if(vcpkg_scope) + list(APPEND dependent_features "${vcpkg_scope}") + endif() + if(DEFINED arg_VCPKG_DEPENDENT_FEATURES) + list(APPEND dependent_features "${arg_VCPKG_DEPENDENT_FEATURES}") + endif() + + # Features that aren't dependencies of other features are added to default-features + # unless VCPKG_DEFAULT or VCPKG_OPTIONAL are specified. + set(additional_args "") + if(NOT arg_VCKG_DEFAULT AND NOT arg_VCPKG_OPTIONAL AND dependent_features STREQUAL "") + list(APPEND additional_args DEFAULT) + endif() + qt_vcpkg_feature("${feature}" "${description}" ${additional_args}) + + foreach(dependent_feature IN LISTS dependent_features) + qt_vcpkg_add_feature_dependencies("${dependent_feature}" "${feature}") + endforeach() endfunction() function(qt_feature_alias feature) @@ -260,11 +388,83 @@ function(qt_feature_deprecated feature) set_property(GLOBAL PROPERTY COMMANDLINE_FEATURE_SECTION_${feature} "${arg_SECTION}") endfunction() +function(qt_feature_vcpkg_scope name) + set_property(GLOBAL PROPERTY _QT_VCPKG_SCOPE ${name}) +endfunction() + function(find_package) message(FATAL_ERROR "find_package must not be used directly in configure.cmake. " "Use qt_find_package or guard the call with an if(NOT QT_CONFIGURE_RUNNING) block.") endfunction() +function(qt_init_vcpkg_manifest_if_needed) + get_property(manifest_initialized GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON SET) + if(manifest_initialized) + return() + endif() + + if(TOP_LEVEL) + set(package_name "qt") + else() + get_filename_component(package_name "${MODULE_ROOT}" NAME) + endif() + qt_vcpkg_manifest_init(NAME "${package_name}") +endfunction() + +function(qt_find_package name) + if(NOT generate_vcpkg_manifest) + return() + endif() + + set(no_value_options "") + set(single_value_options + VCPKG_ADD_TO_FEATURE + VCPKG_DEFAULT_FEATURES + VCPKG_PLATFORM + VCPKG_PORT + VCPKG_VERSION + ) + set(multi_value_options + VCPKG_FEATURES + ) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + set(unknown_vcpkg_args "${arg_UNPARSED_ARGUMENTS}") + list(FILTER unknown_vcpkg_args INCLUDE REGEX "^VCPKG_") + if(NOT "${unknown_vcpkg_args}" STREQUAL "") + message(FATAL_ERROR "Unknown arguments passed to qt_find_package: ${unknown_vcpkg_args}") + endif() + + if(NOT DEFINED arg_VCPKG_PORT) + return() + endif() + + qt_init_vcpkg_manifest_if_needed() + + set(dependency_args "${arg_VCPKG_PORT}") + if(DEFINED arg_VCPKG_VERSION) + list(APPEND dependency_args VERSION "${arg_VCPKG_VERSION}") + endif() + if(DEFINED arg_VCPKG_PLATFORM) + list(APPEND dependency_args PLATFORM "${arg_VCPKG_PLATFORM}") + endif() + if(DEFINED arg_VCPKG_DEFAULT_FEATURES) + list(APPEND dependency_args DEFAULT_FEATURES "${arg_VCPKG_DEFAULT_FEATURES}") + endif() + if(DEFINED arg_VCPKG_ADD_TO_FEATURE) + list(APPEND dependency_args ADD_TO_FEATURE "${arg_VCPKG_ADD_TO_FEATURE}") + set_property(GLOBAL APPEND PROPERTY + _QT_VCPKG_FEATURES_TO_CREATE "${arg_VCPKG_ADD_TO_FEATURE}" + ) + endif() + if(DEFINED arg_VCPKG_FEATURES) + list(APPEND dependency_args FEATURES "${arg_VCPKG_FEATURES}") + endif() + qt_vcpkg_add_dependency(${dependency_args}) +endfunction() + macro(defstub name) function(${name}) endfunction() @@ -289,7 +489,6 @@ defstub(qt_configure_end_summary_section) defstub(qt_extra_definition) defstub(qt_feature_config) defstub(qt_feature_definition) -defstub(qt_find_package) defstub(set_package_properties) defstub(qt_qml_find_python) defstub(qt_set01) @@ -391,6 +590,9 @@ endfunction() # stub functions above. set(QT_CONFIGURE_RUNNING ON) +# Make sure that configure sees all qt_find_package calls. +set(QT_FIND_ALL_PACKAGES_ALWAYS ON) + #################################################################################################### # Load qt_cmdline.cmake files @@ -414,6 +616,7 @@ while(commandline_files) unset(commandline_subconfigs) if(EXISTS "${configure_file}") include("${configure_file}") + set_property(GLOBAL PROPERTY _QT_VCPKG_SCOPE) endif() if(EXISTS "${commandline_file}") include("${commandline_file}") @@ -1188,9 +1391,109 @@ if(INPUT_sysroot) "to pass the sysroot to CMake.\n") endif() +function(qt_generate_vcpkg_manifest) + # Check if manifest was initialized (i.e., any dependencies were found) + get_property(manifest_initialized GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON SET) + if(NOT manifest_initialized) + # For repositories that don't need 3rd-party libs, generate an empty vcpkg.json. + qt_init_vcpkg_manifest_if_needed() + endif() + + # Write the manifest file + set(manifest_file "${CMAKE_CURRENT_BINARY_DIR}/vcpkg.json") + qt_vcpkg_write_manifest("${manifest_file}") +endfunction() + +function(qt_select_vcpkg_features out_var cmake_args) + # Extract enabled/disabled features from cmake_args. + set(normalized_enabled_features "") + set(normalized_disabled_features "") + foreach(arg IN LISTS cmake_args) + if(arg MATCHES "^-DFEATURE_([^=]+)=(.*)") + if(CMAKE_MATCH_2) + list(APPEND normalized_enabled_features "${CMAKE_MATCH_1}") + else() + list(APPEND normalized_disabled_features "${CMAKE_MATCH_1}") + endif() + endif() + endforeach() + + if(normalized_enabled_features STREQUAL "" AND normalized_disabled_features STREQUAL "") + return() + endif() + + # Convert normalized feature names back to original names + set(enabled_features "") + set(disabled_features "") + foreach(original_feature IN LISTS commandline_known_features) + qt_feature_normalize_name("${original_feature}" normalized_feature) + if(normalized_feature IN_LIST normalized_enabled_features) + list(APPEND enabled_features "${original_feature}") + endif() + if(normalized_feature IN_LIST normalized_disabled_features) + list(APPEND disabled_features "${original_feature}") + endif() + endforeach() + + set(manifest_file_path "${CMAKE_CURRENT_BINARY_DIR}/vcpkg.json") + if(NOT EXISTS "${manifest_file_path}") + return() + endif() + + file(READ "${manifest_file_path}" json) + qt_vcpkg_set_internal_manifest_data("${json}") + + # Enable all default vcpkg feature that were not explicitly disabled. + qt_vcpkg_get_default_features(vcpkg_default_features "${json}") + set(vcpkg_features_to_enable ${vcpkg_default_features}) + list(REMOVE_ITEM vcpkg_features_to_enable ${disabled_features}) + + # Enable all vcpkg features that were explicitly enabled. Filter out non-vcpkg features. + qt_vcpkg_get_features(all_vcpkg_features "${json}") + set(enabled_features_without_vcpkg_features "${enabled_features}") + list(REMOVE_ITEM enabled_features_without_vcpkg_features ${all_vcpkg_features}) + list(APPEND vcpkg_features_to_enable ${enabled_features}) + list(REMOVE_ITEM vcpkg_features_to_enable ${enabled_features_without_vcpkg_features}) + list(REMOVE_DUPLICATES vcpkg_features_to_enable) + + # If we're enabling exactly the default features, we don't have to explicitly enable them. + if(vcpkg_features_to_enable STREQUAL vcpkg_default_features) + return() + endif() + + list(APPEND cmake_args "-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON") + list(JOIN vcpkg_features_to_enable "[[;]]" manifest_args) + list(APPEND cmake_args "-DVCPKG_MANIFEST_FEATURES=${manifest_args}") + set("${out_var}" "${cmake_args}" PARENT_SCOPE) +endfunction() + +if(generate_vcpkg_manifest) + qt_generate_vcpkg_manifest() +endif() + +if(use_vcpkg) + qt_select_vcpkg_features(cmake_args "${cmake_args}") +endif() + # Restore the escaped semicolons in arguments that are lists list(TRANSFORM cmake_args REPLACE "\\[\\[;\\]\\]" "\\\\;") +if(dry_run) + if(CMAKE_COMMAND MATCHES " ") + set(pretty_command_line "\"${CMAKE_COMMAND}\"") + else() + set(pretty_command_line "${CMAKE_COMMAND}") + endif() + foreach(arg IN LISTS cmake_args) + if(arg MATCHES "[ ;]") + set(arg "\"${arg}\"") + endif() + string(APPEND pretty_command_line " ${arg}") + endforeach() + message("${pretty_command_line}") + return() +endif() + execute_process(COMMAND "${CMAKE_COMMAND}" ${cmake_args} COMMAND_ECHO STDOUT RESULT_VARIABLE exit_code) diff --git a/cmake/QtVcpkgManifestHelpers.cmake b/cmake/QtVcpkgManifestHelpers.cmake new file mode 100644 index 00000000000..1b556610915 --- /dev/null +++ b/cmake/QtVcpkgManifestHelpers.cmake @@ -0,0 +1,354 @@ +# CMake API for generating vcpkg.json manifest files + +# Initialize the internal vcpkg manifest data with the given JSON string. +# +# This function is called internally by qt_vcpkg_manifest_init and may be used to set the internal +# JSON data to a JSON string read from a file. +function(qt_vcpkg_set_internal_manifest_data json) + set_property(GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON "${json}") +endfunction() + +# Initialize a new vcpkg manifest with basic package information. +# +# This function creates the foundation for a vcpkg.json manifest file by setting up +# the basic structure with package name and version. It must be called before any +# other qt_vcpkg_* functions. +# +# Arguments: +# NAME - Required. The package name for the vcpkg manifest +# VERSION - Optional. The package version string +# BUILTIN_BASELINE - Optional. Baseline for version resolution. +function(qt_vcpkg_manifest_init) + set(no_value_options "") + set(single_value_options + BUILTIN_BASELINE + NAME + VERSION + ) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + if(NOT DEFINED arg_NAME) + message(FATAL_ERROR "qt_vcpkg_manifest_init: NAME is required") + endif() + + string(JSON manifest_json SET "{}" name "\"${arg_NAME}\"") + string(JSON manifest_json SET "${manifest_json}" dependencies "[]") + string(JSON manifest_json SET "${manifest_json}" features "{}") + string(JSON manifest_json SET "${manifest_json}" default-features "[]") + if(DEFINED arg_VERSION) + string(JSON manifest_json SET "${manifest_json}" version "\"${arg_VERSION}\"") + endif() + if(DEFINED arg_BUILTIN_BASELINE) + string(JSON manifest_json SET "${manifest_json}" builtin-baseline + "\"${arg_BUILTIN_BASELINE}\"" + ) + endif() + + qt_vcpkg_set_internal_manifest_data("${manifest_json}") +endfunction() + +# Add a dependency to the vcpkg manifest. +# +# This function adds either a simple string dependency or a complex dependency object +# to the manifest's dependencies array or to a specific feature's dependencies. +# +# Arguments: +# dependency - Required. The name of the package dependency +# VERSION - Optional. Specific version constraint for the dependency +# PLATFORM - Optional. Platform expression to limit when dependency is used +# FEATURES - Optional. List of features to enable for this dependency +# DEFAULT_FEATURES - Optional. Boolean to set default-features. +# ADD_TO_FEATURE - Optional. Name of feature to add this dependency to. If not specified, +# adds to the manifest's root dependencies array. +# +# If VERSION, PLATFORM, FEATURES, or DEFAULT_FEATURES are specified, a complex dependency object is +# created. When FEATURES is specified, "default-features": false is automatically set. Otherwise, a +# simple string dependency is added. +# +# If ADD_TO_FEATURE is specified but the feature doesn't exist, it will be created with a default +# description. +function(qt_vcpkg_add_dependency name) + set(no_value_options "") + set(single_value_options + ADD_TO_FEATURE + DEFAULT_FEATURES + PLATFORM + VERSION + ) + set(multi_value_options + FEATURES + ) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + get_property(manifest_json GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON) + if(NOT manifest_json) + message(FATAL_ERROR "qt_vcpkg_add_dependency: Must call qt_vcpkg_manifest_init first") + endif() + + if(arg_ADD_TO_FEATURE) + # Check if feature exists, create with default description if not + string(JSON feature_obj ERROR_VARIABLE feature_error GET "${manifest_json}" features + ${arg_ADD_TO_FEATURE} + ) + if(feature_error) + string(JSON feature_obj SET "{}" description "\"Use ${arg_ADD_TO_FEATURE}\"") + endif() + + string(JSON deps_exists ERROR_VARIABLE deps_error GET "${feature_obj}" dependencies) + if(deps_error) + string(JSON feature_obj SET "${feature_obj}" dependencies "[]") + endif() + + set(target_obj "${feature_obj}") + set(target_path "features" "${arg_ADD_TO_FEATURE}") + else() + set(target_obj "${manifest_json}") + set(target_path "") + endif() + + string(JSON deps_length LENGTH "${target_obj}" dependencies) + if(DEFINED arg_VERSION OR DEFINED arg_FEATURES OR DEFINED arg_PLATFORM + OR DEFINED arg_DEFAULT_FEATURES) + string(JSON dep_obj SET "{}" name "\"${name}\"") + + if(arg_VERSION) + string(JSON dep_obj SET "${dep_obj}" version>= "\"${arg_VERSION}\"") + endif() + + if(arg_FEATURES) + if(NOT DEFINED arg_DEFAULT_FEATURES) + set(arg_DEFAULT_FEATURES OFF) + endif() + string(JSON dep_obj SET "${dep_obj}" features "[]") + set(feature_index 0) + foreach(feature ${arg_FEATURES}) + string(JSON dep_obj SET "${dep_obj}" features ${feature_index} "\"${feature}\"") + math(EXPR feature_index "${feature_index} + 1") + endforeach() + endif() + + if(DEFINED arg_DEFAULT_FEATURES) + if(arg_DEFAULT_FEATURES) + string(JSON dep_obj SET "${dep_obj}" default-features true) + else() + string(JSON dep_obj SET "${dep_obj}" default-features false) + endif() + endif() + + if(arg_PLATFORM) + string(JSON dep_obj SET "${dep_obj}" platform "\"${arg_PLATFORM}\"") + endif() + + string(JSON target_obj SET "${target_obj}" dependencies ${deps_length} "${dep_obj}") + else() + string(JSON target_obj SET "${target_obj}" dependencies ${deps_length} "\"${name}\"") + endif() + + if(arg_ADD_TO_FEATURE) + string(JSON manifest_json SET "${manifest_json}" features ${arg_ADD_TO_FEATURE} + "${target_obj}" + ) + else() + set(manifest_json "${target_obj}") + endif() + + set_property(GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON "${manifest_json}") +endfunction() + +# Add a feature definition to the vcpkg manifest. +# +# This function creates or updates a basic feature in the manifest with the specified +# description and metadata. It does not modify feature dependencies - use the dedicated +# dependency functions for that purpose. +# +# Arguments: +# name - Required. The feature name +# description - Required. Human-readable description of the feature +# DEFAULT - Optional flag. If set, feature is added to default-features +# SUPPORTS - Optional. Platform expression defining where feature is supported +# +# The function preserves existing dependencies when updating an existing feature. +function(qt_vcpkg_feature name description) + set(no_value_options DEFAULT) + set(single_value_options SUPPORTS) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 2 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + get_property(manifest_json GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON) + if(NOT manifest_json) + message(FATAL_ERROR "qt_vcpkg_feature: Must call qt_vcpkg_manifest_init first") + endif() + + string(JSON feature_obj ERROR_VARIABLE feature_error GET "${manifest_json}" features ${name}) + if(feature_error) + set(feature_obj "{}") + endif() + string(JSON feature_obj SET "${feature_obj}" description "\"${description}\"") + + if(arg_SUPPORTS) + string(JSON feature_obj SET "${feature_obj}" supports "\"${arg_SUPPORTS}\"") + endif() + + string(JSON manifest_json SET "${manifest_json}" features ${name} "${feature_obj}") + + if(arg_DEFAULT) + string(JSON default_features_length LENGTH "${manifest_json}" default-features) + string(JSON manifest_json SET "${manifest_json}" default-features ${default_features_length} + "\"${name}\"" + ) + endif() + + set_property(GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON "${manifest_json}") +endfunction() + +# Add feature dependencies to an existing feature. +# +# This function creates self-referential dependencies where one feature in a package +# depends on other features in the same package. +# +# Arguments: +# feature_name - Required. Name of the feature to modify +# dependency_features - Required. List of features this feature depends on +# +# Creates a dependency object with the package's own name and the specified features, +# with "default-features": false to only enable the specified features. +function(qt_vcpkg_add_feature_dependencies feature_name) + set(dependency_features ${ARGN}) + + get_property(manifest_json GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON) + if(NOT manifest_json) + message(FATAL_ERROR + "qt_vcpkg_add_feature_dependencies: Must call qt_vcpkg_manifest_init first" + ) + endif() + + string(JSON package_name GET "${manifest_json}" name) + qt_vcpkg_add_dependency(${package_name} ADD_TO_FEATURE ${feature_name} + FEATURES ${dependency_features} + ) +endfunction() + +# Write the vcpkg manifest to a file. +# +# This function serializes the current manifest JSON structure to the specified +# output file. +# +# Arguments: +# output_file - Required. Path where the vcpkg.json manifest should be written +# +# The function removes empty top-level elements from the manifest before writing +# to avoid cluttering the output file. +function(qt_vcpkg_write_manifest output_file) + get_property(manifest_json GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON) + if(NOT manifest_json) + message(FATAL_ERROR "qt_vcpkg_write_manifest: Must call qt_vcpkg_manifest_init first") + endif() + + # Remove empty elements + foreach(element_name IN ITEMS dependencies features default-features) + string(JSON element_length LENGTH "${manifest_json}" "${element_name}") + if(element_length EQUAL 0) + string(JSON manifest_json REMOVE "${manifest_json}" "${element_name}") + endif() + endforeach() + + file(CONFIGURE OUTPUT "${output_file}" CONTENT "${manifest_json}") + message(STATUS "Generated vcpkg manifest: ${output_file}") +endfunction() + +# Return the default-features of the current manifest in out_var. +# +# This function reads the default-features from the internal manifest data and +# writes their names to out_var. +function(qt_vcpkg_get_default_features out_var) + get_property(manifest_json GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON) + if(NOT manifest_json) + message(FATAL_ERROR "qt_vcpkg_get_default_features: Must call qt_vcpkg_manifest_init first") + endif() + + set("${out_var}" "" PARENT_SCOPE) + string(JSON default_features ERROR_VARIABLE json_error GET "${manifest_json}" default-features) + if(json_error) + return() + endif() + string(JSON default_features_length ERROR_VARIABLE json_error LENGTH "${default_features}") + if(json_error) + return() + endif() + + set(result "") + math(EXPR max_i "${default_features_length} - 1") + foreach(i RANGE 0 ${max_i}) + string(JSON element ERROR_VARIABLE json_error GET "${default_features}" "${i}") + if(json_error) + continue() + endif() + string(JSON element_type ERROR_VARIABLE json_error TYPE "${default_features}" "${i}") + if(json_error) + continue() + endif() + + set(feature "") + if(element_type STREQUAL "STRING") + set(feature "${element}") + elseif(element_type STREQUAL "OBJECT") + string(JSON feature ERROR_VARIABLE json_error GET "${element}" "name") + if(json_error) + continue() + endif() + endif() + + list(APPEND result "${feature}") + endforeach() + + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + +# Return the names of all features in the current manifest in out_var. +# +# This function reads the features map from the current manifest and returns its keys +# (the feature names) in out_var. +function(qt_vcpkg_get_features out_var) + get_property(manifest_json GLOBAL PROPERTY _QT_VCPKG_MANIFEST_JSON) + if(NOT manifest_json) + message(FATAL_ERROR "qt_vcpkg_get_features: Must call qt_vcpkg_manifest_init first") + endif() + + set("${out_var}" "" PARENT_SCOPE) + string(JSON features_obj ERROR_VARIABLE json_error GET "${manifest_json}" features) + if(json_error) + return() + endif() + string(JSON features_length ERROR_VARIABLE json_error LENGTH "${features_obj}") + if(json_error) + return() + endif() + + set(result "") + math(EXPR max_i "${features_length} - 1") + foreach(i RANGE 0 ${max_i}) + string(JSON feature ERROR_VARIABLE json_error MEMBER "${features_obj}" ${i}) + if(json_error) + continue() + endif() + list(APPEND result "${feature}") + endforeach() + + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() diff --git a/cmake/QtWrapperScriptHelpers.cmake b/cmake/QtWrapperScriptHelpers.cmake index f76d52bca91..95380386300 100644 --- a/cmake/QtWrapperScriptHelpers.cmake +++ b/cmake/QtWrapperScriptHelpers.cmake @@ -98,6 +98,19 @@ export CMAKE_GENERATOR=Xcode endif() file(TO_NATIVE_PATH "${__relative_path_to_cmake_scripts_dir}" __relative_path_to_cmake_scripts_dir) + + # Store arguments we want to forward from configure to qt-configure-module arguments in a + # side-car file. If there's nothing to forward, create an empty file. + set(initial_configure_args "") + if(QT_USE_VCPKG) + list(APPEND initial_configure_args -vcpkg) + endif() + set(side_car_file_name "qt-configure-module-flags.txt") + set(side_car_file_path "${__GlobalConfig_build_dir}/${side_car_file_name}") + string(JOIN "\n" side_car_file_content "${initial_configure_args}") + file(CONFIGURE OUTPUT "${side_car_file_path}" CONTENT "${side_car_file_content}\n") + qt_copy_or_install(FILES "${side_car_file_path}" DESTINATION "${__GlobalConfig_install_dir}") + if(generate_unix) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bin/qt-configure-module.in" "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-configure-module" @ONLY diff --git a/config_help.txt b/config_help.txt index 583c14a7b20..9550f3f2768 100644 --- a/config_help.txt +++ b/config_help.txt @@ -69,6 +69,7 @@ Build options: system generator. -cmake-file-api ...... Let CMake store build metadata for loading the build into an IDE. [no; yes if -developer-build] + -dry-run ............. Only print the CMake command but don't execute it. -no-guess-compiler ... Do not guess the compiler from the target mkspec. -release ............. Build Qt with optimizations and without debug symbols [yes] @@ -193,7 +194,10 @@ Build environment: -pkg-config .......... Use pkg-config [auto] (Unix only) - -vcpkg ............... Use vcpkg [no] + -vcpkg ............... Use vcpkg for downloading and finding 3rd-party + dependencies [no] + -generate-vcpkg-manifest ... Generate a vcpkg.json file in the build directory + [no; yes if -vcpkg] -D <string> .......... Pass additional preprocessor define -I <string> .......... Pass additional include path diff --git a/configure.cmake b/configure.cmake index 02f4d8b8c84..2c01aafa53b 100644 --- a/configure.cmake +++ b/configure.cmake @@ -8,7 +8,9 @@ #### Libraries qt_find_package(WrapSystemZLIB 1.0.8 MODULE - PROVIDED_TARGETS WrapSystemZLIB::WrapSystemZLIB MODULE_NAME global QMAKE_LIB zlib) + PROVIDED_TARGETS WrapSystemZLIB::WrapSystemZLIB MODULE_NAME global QMAKE_LIB zlib + VCPKG_PORT zlib +) # Work around global target promotion failure when WrapZLIB is used on APPLE platforms. # What ends up happening is that the ZLIB::ZLIB target is not promoted to global by qt_find_package, # then qt_find_package(WrapSystemPNG) tries to find its dependency ZLIB::ZLIB, sees it's not global @@ -24,7 +26,10 @@ endif() # directory scope. qt_find_package(Threads MODULE PROVIDED_TARGETS Threads::Threads) qt_find_package(WrapOpenSSLHeaders MODULE - PROVIDED_TARGETS WrapOpenSSLHeaders::WrapOpenSSLHeaders MODULE_NAME core) + PROVIDED_TARGETS WrapOpenSSLHeaders::WrapOpenSSLHeaders MODULE_NAME core + VCPKG_PORT openssl + VCPKG_ADD_TO_FEATURE openssl +) # openssl_headers # OPENSSL_VERSION_MAJOR is not defined for OpenSSL 1.1.1 qt_config_compile_test(opensslv11_headers @@ -1125,6 +1130,7 @@ elseif(QT_COORD_TYPE STREQUAL "float") endif() qt_feature("gui" PRIVATE LABEL "Qt Gui" + VCPKG_DEFAULT ) qt_feature_config("gui" QMAKE_PUBLIC_QT_CONFIG NEGATE) @@ -1132,6 +1138,7 @@ qt_feature("network" PRIVATE LABEL "Qt Network" SECTION "Module" PURPOSE "Provides the Qt Network module." + VCPKG_DEFAULT ) qt_feature("printsupport" PRIVATE LABEL "Qt PrintSupport" @@ -1143,6 +1150,7 @@ qt_feature("sql" PRIVATE LABEL "Qt Sql" SECTION "Module" PURPOSE "Provides the Sql module." + VCPKG_OPTIONAL ) qt_feature("testlib" PRIVATE LABEL "Qt Testlib" @@ -1170,6 +1178,7 @@ qt_feature("openssl" PRIVATE LABEL "OpenSSL" CONDITION QT_FEATURE_openssl_runtime OR QT_FEATURE_openssl_linked ENABLE false + VCPKG_DEFAULT ) qt_feature_definition("openssl" "QT_NO_OPENSSL" NEGATE) qt_feature_config("openssl" QMAKE_PUBLIC_QT_CONFIG) diff --git a/qt_cmdline.cmake b/qt_cmdline.cmake index 0baa675da46..97a225d670d 100644 --- a/qt_cmdline.cmake +++ b/qt_cmdline.cmake @@ -55,7 +55,6 @@ qt_commandline_option(unity-build-batch-size CMAKE_VARIABLE QT_UNITY_BUILD_BATCH_SIZE ) qt_commandline_option(ccache TYPE boolean NAME ccache CMAKE_VARIABLE QT_USE_CCACHE) -qt_commandline_option(vcpkg TYPE boolean CMAKE_VARIABLE QT_USE_VCPKG) qt_commandline_option(commercial TYPE void) qt_commandline_option(confirm-license TYPE void) qt_commandline_option(dbus TYPE optionalString VALUES no yes linked runtime) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index edcfba0f6ce..08908082991 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -31,7 +31,10 @@ qt_find_package_extend_sbom(TARGETS GLIB2::GLIB2 LICENSE_EXPRESSION "LGPL-2.1-or-later" ) qt_find_package(ICU 50.1 COMPONENTS i18n uc data PROVIDED_TARGETS ICU::i18n ICU::uc ICU::data - MODULE_NAME core QMAKE_LIB icu) + MODULE_NAME core QMAKE_LIB icu + VCPKG_PORT icu + VCPKG_PLATFORM !windows +) if(QT_FEATURE_dlopen) qt_add_qmake_lib_dependency(icu libdl) @@ -49,7 +52,9 @@ qt_find_package_extend_sbom(TARGETS Libb2::Libb2 qt_find_package(WrapRt MODULE PROVIDED_TARGETS WrapRt::WrapRt MODULE_NAME core QMAKE_LIB librt) qt_find_package(WrapSystemPCRE2 10.20 MODULE - PROVIDED_TARGETS WrapSystemPCRE2::WrapSystemPCRE2 MODULE_NAME core QMAKE_LIB pcre2) + PROVIDED_TARGETS WrapSystemPCRE2::WrapSystemPCRE2 MODULE_NAME core QMAKE_LIB pcre2 + VCPKG_PORT pcre2 +) set_package_properties(WrapPCRE2 PROPERTIES TYPE REQUIRED) if((QNX) OR QT_FIND_ALL_PACKAGES_ALWAYS) qt_find_package(PPS MODULE PROVIDED_TARGETS PPS::PPS MODULE_NAME core QMAKE_LIB pps) diff --git a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc index 68fe6a8e5cc..8fa6c2bb6d7 100644 --- a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc +++ b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc @@ -14,6 +14,10 @@ \cmakecommandsince 6.9 +\note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present +in the AndroidManifest.xml. For further information on the use of this tag, +see \l {Qt Permissions and Features} + \section1 Synopsis \badcode diff --git a/src/corelib/global/qcompilerdetection.h b/src/corelib/global/qcompilerdetection.h index 7bb2e5b04ef..0b42af7686c 100644 --- a/src/corelib/global/qcompilerdetection.h +++ b/src/corelib/global/qcompilerdetection.h @@ -1446,7 +1446,7 @@ QT_WARNING_DISABLE_MSVC(4355) /* 'this' : used in base member initializer list * QT_WARNING_DISABLE_MSVC(4710) /* function not inlined */ QT_WARNING_DISABLE_MSVC(4530) /* C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc */ # elif defined(Q_CC_CLANG_ONLY) -# if Q_CC_CLANG >= 2100 +# if __has_warning("-Wcharacter-conversion") QT_WARNING_DISABLE_CLANG("-Wcharacter-conversion") /* until https://github.com/llvm/llvm-project/issues/163719 is fixed */ # endif # elif defined(Q_CC_BOR) diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h index 13349688d20..97d0cd37682 100644 --- a/src/corelib/kernel/qjniarray.h +++ b/src/corelib/kernel/qjniarray.h @@ -872,7 +872,7 @@ auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion) const size_type length = size_type(std::size(list)); JNIEnv *env = QJniEnvironment::getJniEnv(); auto localArray = (env->*newArray)(length); - if (QJniEnvironment::checkAndClearExceptions(env)) { + if (env->ExceptionCheck()) { if (localArray) env->DeleteLocalRef(localArray); return QJniArray<ElementType>(); @@ -916,7 +916,7 @@ auto QJniArrayBase::makeObjectArray(List &&list) elementClass = env->GetObjectClass(*std::begin(list)); } auto localArray = env->NewObjectArray(length, elementClass, nullptr); - if (QJniEnvironment::checkAndClearExceptions(env)) { + if (env->ExceptionCheck()) { if (localArray) env->DeleteLocalRef(localArray); return ResultType(); diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp index b4f8497ddda..1ee658fd18d 100644 --- a/src/corelib/kernel/qjnienvironment.cpp +++ b/src/corelib/kernel/qjnienvironment.cpp @@ -559,4 +559,12 @@ bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::Outp return false; } +/*! + Returns the stack trace that resulted in \a exception being thrown. +*/ +QStringList QJniEnvironment::stackTrace(jthrowable exception) +{ + return exceptionMessage(getJniEnv(), exception).split(u'\n'); +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qjnienvironment.h b/src/corelib/kernel/qjnienvironment.h index b473f75bed1..a5f3700b1f0 100644 --- a/src/corelib/kernel/qjnienvironment.h +++ b/src/corelib/kernel/qjnienvironment.h @@ -89,6 +89,7 @@ public: static bool checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose); static JNIEnv *getJniEnv(); + static QStringList stackTrace(jthrowable exception); private: Q_DISABLE_COPY_MOVE(QJniEnvironment) diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp index 8f3da9a8595..59117bd01d4 100644 --- a/src/corelib/kernel/qjniobject.cpp +++ b/src/corelib/kernel/qjniobject.cpp @@ -348,7 +348,10 @@ static inline QByteArray cacheKey(Args &&...args) return (QByteArrayView(":") + ... + QByteArrayView(args)); } -typedef QHash<QByteArray, jclass> JClassHash; +struct JClassHash : QHash<QByteArray, jclass> +{ + jmethodID loadClassMethod = 0; +}; Q_GLOBAL_STATIC(JClassHash, cachedClasses) Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock) @@ -391,29 +394,22 @@ bool QJniObjectPrivate::isJString(JNIEnv *env) const */ static QJniObject getCleanJniObject(jobject object, JNIEnv *env) { - if (QJniEnvironment::checkAndClearExceptions(env) || !object) { - if (object) - env->DeleteLocalRef(object); + if (!object || env->ExceptionCheck()) return QJniObject(); - } - QJniObject res(object); - env->DeleteLocalRef(object); - return res; + return QJniObject::fromLocalRef(object); } -/*! - \internal - \a className must be slash-encoded -*/ -jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +namespace { + +jclass loadClassHelper(const QByteArray &className, JNIEnv *env) { Q_ASSERT(env); QByteArray classNameArray(className); #ifdef QT_DEBUG if (classNameArray.contains('.')) { qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!", - className); + className.constData()); } #endif classNameArray.replace('.', '/'); @@ -442,21 +438,40 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) if (!clazz) { // Wrong class loader, try our own - QJniObject classLoader(QtAndroidPrivate::classLoader()); - if (!classLoader.isValid()) + jobject classLoader = QtAndroidPrivate::classLoader(); + if (!classLoader) return nullptr; + if (!cachedClasses->loadClassMethod) { + jclass classLoaderClass = env->GetObjectClass(classLoader); + if (!classLoaderClass) + return nullptr; + cachedClasses->loadClassMethod = + env->GetMethodID(classLoaderClass, + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + env->DeleteLocalRef(classLoaderClass); + if (!cachedClasses->loadClassMethod) { + qCritical("Couldn't find the 'loadClass' method in the Qt class loader"); + return nullptr; + } + } + // ClassLoader::loadClass on the other hand wants the binary name of the class, // e.g. dot-separated. In testing it works also with /, but better to stick to // the specification. const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.'); - jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()), - binaryClassName.length()); - QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject); + jstring classNameObject = env->NewString(binaryClassName.utf16(), binaryClassName.length()); + jobject classObject = env->CallObjectMethod(classLoader, + cachedClasses->loadClassMethod, + classNameObject); env->DeleteLocalRef(classNameObject); - if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid()) - clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object())); + if (classObject && !env->ExceptionCheck()) { + clazz = static_cast<jclass>(env->NewGlobalRef(classObject)); + env->DeleteLocalRef(classObject); + } + // Clearing the exception is the caller's responsibility (see + // QtAndroidPrivate::findClass()) and QJniObject::loadClass{KeepExceptions} } if (clazz) @@ -465,11 +480,32 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) return clazz; } +} // unnamed namespace + +/*! + \internal + \a className must be slash-encoded +*/ +jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +{ + jclass clazz = loadClassHelper(className, env); + if (!clazz) + QJniEnvironment::checkAndClearExceptions(env); + return clazz; +} + jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env) { return QtAndroidPrivate::findClass(className, env); } +jclass QJniObject::loadClassKeepExceptions(const QByteArray &className, JNIEnv *env) +{ + return loadClassHelper(className, env); +} + + + typedef QHash<QByteArray, jmethodID> JMethodIDHash; Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID) Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock) @@ -483,9 +519,6 @@ jmethodID QJniObject::getMethodID(JNIEnv *env, jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature) : env->GetMethodID(clazz, name, signature); - if (QJniEnvironment::checkAndClearExceptions(env)) - return nullptr; - return id; } @@ -525,7 +558,8 @@ jmethodID QJniObject::getCachedMethodID(JNIEnv *env, jmethodID id = getMethodID(env, clazz, name, signature, isStatic); - cachedMethodID->insert(key, id); + if (id) + cachedMethodID->insert(key, id); return id; } } @@ -549,9 +583,6 @@ jfieldID QJniObject::getFieldID(JNIEnv *env, jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature) : env->GetFieldID(clazz, name, signature); - if (QJniEnvironment::checkAndClearExceptions(env)) - return nullptr; - return id; } @@ -583,7 +614,8 @@ jfieldID QJniObject::getCachedFieldID(JNIEnv *env, jfieldID id = getFieldID(env, clazz, name, signature, isStatic); - cachedFieldID->insert(key, id); + if (id) + cachedFieldID->insert(key, id); return id; } } @@ -1008,15 +1040,15 @@ QJniObject QJniObject::callObjectMethod(const char *methodName, const char *sign { JNIEnv *env = jniEnv(); jmethodID id = getCachedMethodID(env, methodName, signature); - if (id) { - va_list args; - va_start(args, signature); - QJniObject res = getCleanJniObject(env->CallObjectMethodV(d->m_jobject, id, args), env); - va_end(args); - return res; - } + va_list args; + va_start(args, signature); + // can't go back from variadic arguments to variadic templates + jobject object = id ? jniEnv()->CallObjectMethodV(javaObject(), id, args) : nullptr; + QJniObject res = getCleanJniObject(object, env); + va_end(args); - return QJniObject(); + QJniEnvironment::checkAndClearExceptions(env); + return res; } /*! @@ -1150,7 +1182,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, T value); + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, Type value); Sets the static field \a fieldName on the class \a className to \a value using the setter with \a signature. @@ -1158,7 +1190,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, T value); + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value); Sets the static field \a fieldName on the class \a clazz to \a value using the setter with \a signature. @@ -1196,19 +1228,19 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, T value) + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, Type value) Sets the static field \a fieldName of the class \a className to \a value. */ /*! - \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, T value) + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, Type value) Sets the static field \a fieldName of the class \a clazz to \a value. */ /*! - \fn template <typename Klass, typename T> auto QJniObject::setStaticField(const char *fieldName, T value) + \fn template <typename Klass, typename Ret, typename Type> auto QJniObject::setStaticField(const char *fieldName, Type value) Sets the static field \a fieldName of the class \c Klass to \a value. @@ -1263,11 +1295,16 @@ QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName, { JNIEnv *env = QJniEnvironment::getJniEnv(); jfieldID id = getFieldID(env, clazz, fieldName, signature, true); - return getCleanJniObject(env->GetStaticObjectField(clazz, id), env); + + const auto clearExceptions = qScopeGuard([env]{ + QJniEnvironment::checkAndClearExceptions(env); + }); + + return getCleanJniObject(getStaticObjectFieldImpl(env, clazz, id), env); } /*! - \fn template <typename T> void QJniObject::setField(const char *fieldName, const char *signature, T value) + \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, const char *signature, Type value) Sets the value of \a fieldName with \a signature to \a value. @@ -1304,14 +1341,16 @@ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signatu { JNIEnv *env = jniEnv(); jfieldID id = getCachedFieldID(env, fieldName, signature); - if (!id) - return QJniObject(); - return getCleanJniObject(env->GetObjectField(d->m_jobject, id), env); + const auto clearExceptions = qScopeGuard([env]{ + QJniEnvironment::checkAndClearExceptions(env); + }); + + return getCleanJniObject(getObjectFieldImpl(env, id), env); } /*! - \fn template <typename T> void QJniObject::setField(const char *fieldName, T value) + \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, Type value) Sets the value of \a fieldName to \a value. diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 236a49544be..06dfc328b4b 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -27,10 +27,29 @@ struct StoresGlobalRefTest<T, std::void_t<decltype(std::declval<T>().object())>> : std::is_same<decltype(std::declval<T>().object()), jobject> {}; -template <typename ...Args> +// detect if a type is std::expected-like +template <typename R, typename = void> +struct CallerHandlesException : std::false_type { + using value_type = R; +}; +template <typename R> +struct CallerHandlesException<R, std::void_t<typename R::unexpected_type, + typename R::value_type, + typename R::error_type>> : std::true_type +{ + using value_type = typename R::value_type; +}; + +template <typename ReturnType> +static constexpr bool callerHandlesException = CallerHandlesException<ReturnType>::value; + +template <typename Ret, typename ...Args> struct LocalFrame { + using ReturnType = Ret; + mutable JNIEnv *env; bool hasFrame = false; + explicit LocalFrame(JNIEnv *env = nullptr) noexcept : env(env) { @@ -52,9 +71,12 @@ struct LocalFrame { env = QJniEnvironment::getJniEnv(); return env; } - bool checkAndClearExceptions() + bool checkAndClearExceptions() const { - return env ? QJniEnvironment::checkAndClearExceptions(env) : false; + if constexpr (callerHandlesException<ReturnType>) + return false; + else + return QJniEnvironment::checkAndClearExceptions(jniEnv()); } template <typename T> auto convertToJni(T &&value) @@ -79,13 +101,46 @@ struct LocalFrame { using Type = q20::remove_cvref_t<T>; return QtJniTypes::Traits<Type>::convertFromJni(std::move(object)); } + + template <typename T> + auto convertFromJni(jobject object); + + auto makeResult() + { + if constexpr (callerHandlesException<ReturnType>) { + JNIEnv *env = jniEnv(); + if (env->ExceptionCheck()) { + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + return ReturnType(typename ReturnType::unexpected_type(exception)); + } + return ReturnType(); + } else { + checkAndClearExceptions(); + } + } + + template <typename Value> + auto makeResult(Value &&value) + { + if constexpr (callerHandlesException<ReturnType>) { + auto maybeValue = makeResult(); + if (maybeValue) + return ReturnType(std::forward<Value>(value)); + return std::move(maybeValue); + } else { + checkAndClearExceptions(); + return std::forward<Value>(value); + } + } }; } } class Q_CORE_EXPORT QJniObject { - template <typename ...Args> using LocalFrame = QtJniTypes::Detail::LocalFrame<Args...>; + template <typename Ret, typename ...Args> using LocalFrame + = QtJniTypes::Detail::LocalFrame<Ret, Args...>; public: QJniObject(); @@ -97,12 +152,12 @@ public: #endif > explicit QJniObject(const char *className, Args &&...args) - : QJniObject(LocalFrame<Args...>{}, className, std::forward<Args>(args)...) + : QJniObject(LocalFrame<QJniObject, Args...>{}, className, std::forward<Args>(args)...) { } private: template<typename ...Args> - explicit QJniObject(LocalFrame<Args...> localFrame, const char *className, Args &&...args) + explicit QJniObject(LocalFrame<QJniObject, Args...> localFrame, const char *className, Args &&...args) : QJniObject(className, QtJniTypes::constructorSignature<Args...>().data(), localFrame.convertToJni(std::forward<Args>(args))...) { @@ -130,13 +185,23 @@ public: void swap(QJniObject &other) noexcept { d.swap(other.d); } - template<typename Class, typename ...Args> - static inline QJniObject construct(Args &&...args) + template<typename Class, typename ...Args +#ifndef Q_QDOC + , QtJniTypes::IfValidSignatureTypes<Class, Args...> = true +#endif + > + static inline auto construct(Args &&...args) { - LocalFrame<Args...> frame; - return QJniObject(QtJniTypes::Traits<Class>::className().data(), - QtJniTypes::constructorSignature<Args...>().data(), - frame.convertToJni(std::forward<Args>(args))...); + LocalFrame<Class, Args...> frame; + jclass clazz = QJniObject::loadClassKeepExceptions(QtJniTypes::Traits<Class>::className().data(), + frame.jniEnv()); + auto res = clazz + ? QJniObject(clazz, QtJniTypes::constructorSignature<Args...>().data(), + frame.convertToJni(std::forward<Args>(args))...) + : QtJniTypes::Detail::callerHandlesException<Class> + ? QJniObject(Qt::Initialization::Uninitialized) + : QJniObject(); + return frame.makeResult(std::move(res)); } jobject object() const; @@ -149,47 +214,49 @@ public: jclass objectClass() const; QByteArray className() const; - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > auto callMethod(const char *methodName, const char *signature, Args &&...args) const { - LocalFrame<Args...> frame(jniEnv()); + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame(jniEnv()); + jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); + if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callObjectMethodImpl( + id, frame.convertToJni(std::forward<Args>(args))...)) + ); } else { - jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); if (id) { if constexpr (std::is_same_v<Ret, void>) { callVoidMethodV(frame.jniEnv(), id, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callMethodForType<Ret>(frame.jniEnv(), res, object(), id, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > auto callMethod(const char *methodName, Args &&...args) const { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callMethod<Ret>(methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callMethod<ReturnType>(methodName, signature.data(), std::forward<Args>(args)...); } template <typename Ret, typename ...Args @@ -201,9 +268,11 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame(jniEnv()); - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, + LocalFrame<Ret, Args...> frame(jniEnv()); + auto object = frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, frame.convertToJni(std::forward<Args>(args))...)); + frame.checkAndClearExceptions(); + return object; } QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const; @@ -211,90 +280,93 @@ public: template <typename Ret = void, typename ...Args> static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); return callStaticMethod<Ret>(clazz, methodName, signature, std::forward<Args>(args)...); } template <typename Ret = void, typename ...Args> static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jmethodID id = clazz ? getMethodID(env, clazz, methodName, signature, true) + LocalFrame<Ret, Args...> frame; + jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, signature, true) : 0; return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) { - LocalFrame<Args...> frame; + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame; if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodId, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callStaticObjectMethod( + clazz, methodId, + frame.convertToJni(std::forward<Args>(args))...)) + ); } else { if (clazz && methodId) { if constexpr (std::is_same_v<Ret, void>) { callStaticMethodForVoid(frame.jniEnv(), clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callStaticMethodForType<Ret>(frame.jniEnv(), res, clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *className, const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, QtJniTypes::methodSignature<Ret, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callStaticMethod<Ret>(clazz, methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callStaticMethod<ReturnType>(clazz, methodName, signature.data(), std::forward<Args>(args)...); } - template <typename Klass, typename Ret = void, typename ...Args + template <typename Klass, typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); + LocalFrame<ReturnType, Args...> frame; const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits<Klass>::className().data(), - env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, - QtJniTypes::methodSignature<Ret, Args...>().data(), true) + frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, + QtJniTypes::methodSignature<ReturnType, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } static QJniObject callStaticObjectMethod(const char *className, const char *methodName, @@ -315,7 +387,7 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(className, methodName, signature.data(), frame.convertToJni(std::forward<Args>(args))...)); } @@ -329,98 +401,95 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodName, signature.data(), - frame.convertToJni(std::forward<Args>(args))...)); + frame.convertToJni(std::forward<Args>(args))...)); } - template <typename T + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > auto getField(const char *fieldName) const { - LocalFrame<T> frame(jniEnv()); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame(jniEnv()); + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getObjectField<T>(fieldName)); + return frame.makeResult(frame.template convertFromJni<T>(getObjectFieldImpl( + frame.jniEnv(), id)) + ); } else { T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); - if (id) { + if (id) getFieldForType<T>(frame.jniEnv(), res, object(), id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; + return frame.makeResult(res); } } - template <typename T + template <typename Klass, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static auto getStaticField(const char *className, const char *fieldName) + static auto getStaticField(const char *fieldName) { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(className, fieldName)); - } else { - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return T{}; - return getStaticField<T>(clazz, fieldName); - } + return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); } template <typename T #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true #endif > - static auto getStaticField(jclass clazz, const char *fieldName) + QJniObject getObjectField(const char *fieldName) const { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(clazz, fieldName)); - } else { - T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; - } + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + return getObjectField(fieldName, signature); } - template <typename Klass, typename T + QJniObject getObjectField(const char *fieldName, const char *signature) const; + + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static auto getStaticField(const char *fieldName) + static auto getStaticField(const char *className, const char *fieldName) { - return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + return getStaticField<Type>(clazz, fieldName); } - template <typename T + template <typename Type #ifndef Q_QDOC - , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - QJniObject getObjectField(const char *fieldName) const + static auto getStaticField(jclass clazz, const char *fieldName) { + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; constexpr auto signature = QtJniTypes::fieldSignature<T>(); - return getObjectField(fieldName, signature); + jfieldID id = clazz ? getFieldID(frame.jniEnv(), clazz, fieldName, signature, true) + : nullptr; + if constexpr (QtJniTypes::isObjectType<T>()) { + return frame.makeResult(frame.template convertFromJni<T>(getStaticObjectFieldImpl( + frame.jniEnv(), clazz, id)) + ); + } else { + T res{}; + if (id) + getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); + return frame.makeResult(res); + } } - QJniObject getObjectField(const char *fieldName, const char *signature) const; - template <typename T #ifndef Q_QDOC , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true @@ -450,114 +519,122 @@ public: static QJniObject getStaticObjectField(jclass clazz, const char *fieldName, const char *signature); - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, T value) + auto setField(const char *fieldName, Type value) { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); constexpr auto signature = QtJniTypes::fieldSignature<T>(); jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, const char *signature, T value) - { - jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + auto setField(const char *fieldName, const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, T value) - { - LocalFrame<T> frame; - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return; - - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, - signature, true); - if (!id) - return; - - setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); - frame.checkAndClearExceptions(); + static auto setStaticField(const char *className, const char *fieldName, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + constexpr auto signature = QtJniTypes::fieldSignature<q20::remove_cvref_t<T>>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, - const char *signature, T value) - { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - - if (!clazz) - return; - - jfieldID id = getCachedFieldID(env, clazz, className, fieldName, - signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); + static auto setStaticField(const char *className, const char *fieldName, + const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, - const char *signature, T value) + static auto setStaticField(jclass clazz, const char *fieldName, + const char *signature, Type value) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jfieldID id = getFieldID(env, clazz, fieldName, signature, true); + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); - } + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, T value) + static auto setStaticField(jclass clazz, const char *fieldName, Type value) { - setStaticField(clazz, fieldName, QtJniTypes::fieldSignature<T>(), value); + return setStaticField<Ret, Type>(clazz, fieldName, + QtJniTypes::fieldSignature<q20::remove_cvref_t<Type>>(), + value); } - template <typename Klass, typename T + template <typename Klass, typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *fieldName, T value) + static auto setStaticField(const char *fieldName, Type value) { - setStaticField(QtJniTypes::Traits<Klass>::className(), fieldName, value); + return setStaticField<Ret, Type>(QtJniTypes::Traits<Klass>::className(), fieldName, value); } static QJniObject fromString(const QString &string); @@ -583,6 +660,7 @@ protected: private: static jclass loadClass(const QByteArray &className, JNIEnv *env); + static jclass loadClassKeepExceptions(const QByteArray &className, JNIEnv *env); #if QT_CORE_REMOVED_SINCE(6, 7) // these need to stay in the ABI as they were used in inline methods before 6.7 @@ -620,12 +698,25 @@ private: template<typename T> static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...) { + if (!id) + return; + va_list args = {}; va_start(args, id); QtJniTypes::Caller<T>::callMethodForType(env, res, obj, id, args); va_end(args); } + jobject callObjectMethodImpl(jmethodID method, ...) const + { + va_list args; + va_start(args, method); + jobject res = method ? jniEnv()->CallObjectMethodV(javaObject(), method, args) + : nullptr; + va_end(args); + return res; + } + template<typename T> static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz, jmethodID id, ...) @@ -652,6 +743,9 @@ private: template<typename T> static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id) { + if (!id) + return; + QtJniTypes::Caller<T>::getFieldForType(env, res, obj, id); } @@ -661,22 +755,42 @@ private: QtJniTypes::Caller<T>::getStaticFieldForType(env, res, clazz, id); } - template<typename T> - static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, T value) + template<typename Type> + static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, Type value) { + if (!id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetObjectField(obj, id, static_cast<jobject>(frame.convertToJni(value))); } else { - QtJniTypes::Caller<T>::setFieldForType(env, obj, id, value); + using ValueType = typename QtJniTypes::Detail::CallerHandlesException<T>::value_type; + QtJniTypes::Caller<ValueType>::setFieldForType(env, obj, id, value); } } - template<typename T> - static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, T value) + jobject getObjectFieldImpl(JNIEnv *env, jfieldID field) const + { + return field ? env->GetObjectField(javaObject(), field) : nullptr; + } + + static jobject getStaticObjectFieldImpl(JNIEnv *env, jclass clazz, jfieldID field) + { + return clazz && field ? env->GetStaticObjectField(clazz, field) + : nullptr; + } + + template<typename Type> + static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, Type value) { + if (!clazz || !id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetStaticObjectField(clazz, id, static_cast<jobject>(frame.convertToJni(value))); } else { QtJniTypes::Caller<T>::setStaticFieldForType(env, clazz, id, value); @@ -797,14 +911,14 @@ public: { return QJniObject::getStaticField<Class, T>(field); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static void setStaticField(const char *field, T &&value) + static auto setStaticField(const char *field, T &&value) { - QJniObject::setStaticField<Class, T>(field, std::forward<T>(value)); + return QJniObject::setStaticField<Class, Ret, T>(field, std::forward<T>(value)); } // keep only these overloads, the rest is made private @@ -827,14 +941,14 @@ public: return m_object.getField<T>(fieldName); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - void setField(const char *fieldName, T &&value) + auto setField(const char *fieldName, T &&value) { - m_object.setField(fieldName, std::forward<T>(value)); + return m_object.setField<Ret>(fieldName, std::forward<T>(value)); } QByteArray className() const { @@ -911,6 +1025,37 @@ struct Traits<QString> } }; +template <typename T> +struct Traits<T, std::enable_if_t<QtJniTypes::Detail::callerHandlesException<T>>> +{ + static constexpr auto className() + { + return Traits<typename T::value_type>::className(); + } + + static constexpr auto signature() + { + return Traits<typename T::value_type>::signature(); + } +}; + +} + +template <typename ReturnType, typename ...Args> +template <typename T> +auto QtJniTypes::Detail::LocalFrame<ReturnType, Args...>::convertFromJni(jobject object) +{ + // If the caller wants to handle exceptions through a std::expected-like + // type, then we cannot turn the jobject into a QJniObject, as a + // std::expected<jobject, jthrowable> cannot be constructed from a QJniObject. + // The caller will have to take care of this themselves, by asking for a + // std::expected<QJniObject, ...>, or (typically) using a declared JNI class + // or implicitly supported Qt type (QString or array type). + if constexpr (callerHandlesException<ReturnType> && + std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) + return static_cast<T>(object); + else + return convertFromJni<T>(object ? QJniObject::fromLocalRef(object) : QJniObject()); } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index bbcea8338ca..6767917e176 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -124,7 +124,11 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); The relevant permission names are described in the documentation for each permission type. - \sa {Qt Creator: Editing Manifest Files} + \note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present in + the AndroidManifest.xml. For further information on the use of this tag, + see \l {Qt Permissions and Features} + + \sa {Qt Creator: Editing Manifest Files}. \section1 Available Permissions diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 4f3ecc4c6d9..bbaf2c442a0 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -446,6 +446,119 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name, } +size_t qstdweb::Promise::State::s_numInstances = 0; + +// +// When a promise settles, all attached handlers will be called in +// the order they where added. +// +// In particular a finally handler will be called according to its +// position in the call chain. Which is not necessarily at the end, +// +// This makes cleanup difficult. If we cleanup to early, we will remove +// handlers before they have a chance to be called. This would be the +// case if we add a finally handler in the Promise constructor. +// +// For correct cleanup it is necessary that it happens after the +// last handler has been called. +// +// We choose to implement this by making sure the last handler +// is always a finally handler. +// +// In this case we have multiple finally handlers, so any called +// handler checks if it is the last handler to be called. +// If it is, the cleanup is performed, otherwise cleanup +// is delayed to the last handler. +// +// We could try to let the handlers cleanup themselves, but this +// only works for finally handlers. A then or catch handler is not +// necessarily called, and would not cleanup itself. +// +// We could try to let a (then,catch) pair cleanup both handlers, +// under the assumption that one of them will always be called. +// This does not work in the case that we do not have both handlers, +// or if the then handler throws (both should be called in this case). +// +Promise& Promise::addThenFunction(std::function<void(emscripten::val)> thenFunc) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + + m_state->m_handlers.push_back(suspendResume->registerEventHandler(thenFunc)); + m_state->m_promise = + m_state->m_promise.call<emscripten::val>( + "then", + suspendResume->jsEventHandlerAt( + m_state->m_handlers.back())); + + addFinallyFunction([](){}); // Add a potential cleanup handler + return (*this); +} + +Promise& Promise::addCatchFunction(std::function<void(emscripten::val)> catchFunc) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + + m_state->m_handlers.push_back(suspendResume->registerEventHandler(catchFunc)); + m_state->m_promise = + m_state->m_promise.call<emscripten::val>( + "catch", + suspendResume->jsEventHandlerAt( + m_state->m_handlers.back())); + + addFinallyFunction([](){}); // Add a potential cleanup handler + return (*this); +} + +Promise& Promise::addFinallyFunction(std::function<void()> finallyFunc) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + + auto thisHandler = std::make_shared<uint32_t>((uint32_t)(-1)); + auto state = m_state; + + std::function<void(emscripten::val)> func = + [state, thisHandler, finallyFunc](emscripten::val element) { + Q_UNUSED(element); + + finallyFunc(); + + // See comment at top, we can only do the cleanup + // if we are the last handler in the handler chain + if (state->m_handlers.back() == *thisHandler) { + auto guard = state; // removeEventHandler will remove also this function + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + for (int i = 0; i < guard->m_handlers.size(); ++i) { + suspendResume->removeEventHandler(guard->m_handlers[i]); + guard->m_handlers[i] = (uint32_t)(-1); + } + } + }; + + *thisHandler = suspendResume->registerEventHandler(func); + m_state->m_handlers.push_back(*thisHandler); + m_state->m_promise = + m_state->m_promise.call<emscripten::val>( + "finally", + suspendResume->jsEventHandlerAt( + m_state->m_handlers.back())); + + return (*this); +} + +void Promise::suspendExclusive() +{ + Promise::suspendExclusive(m_state->m_handlers); +} + +emscripten::val Promise::getPromise() const +{ + return m_state->m_promise; +} + uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers) { Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc, diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index b14d9e4012f..07df021c444 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -237,11 +237,80 @@ namespace qstdweb { std::function<void()> finallyFunc; }; - namespace Promise { - uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers = nullptr); + // Note: it is ok for the Promise object to go out of scope, + // the resources will be cleaned up in the finally handler. + class Q_CORE_EXPORT Promise { + public: + template<typename... Args> + Promise(emscripten::val target, QString methodName, Args... args) { + m_state = std::make_shared<State>(); + m_state->m_promise = target.call<emscripten::val>( + methodName.toStdString().c_str(), std::forward<Args>(args)...); + if (m_state->m_promise.isUndefined() || m_state->m_promise["constructor"]["name"].as<std::string>() != "Promise") { + qFatal("This function did not return a promise"); + } + addFinallyFunction([](){}); + } + + Promise(emscripten::val promise) { + m_state = std::make_shared<State>(); + m_state->m_promise = promise; + if (m_state->m_promise.isUndefined() || m_state->m_promise["constructor"]["name"].as<std::string>() != "Promise") { + qFatal("This function did not return a promise"); + } + addFinallyFunction([](){}); + } + + Promise(const std::vector<Promise> &promises) { + std::vector<emscripten::val> all; + all.reserve(promises.size()); + for (const auto &p : promises) + all.push_back(p.getPromise()); + + auto arr = emscripten::val::array(all); + m_state = std::make_shared<State>(); + m_state->m_promise = emscripten::val::global("Promise").call<emscripten::val>("all", arr); + addFinallyFunction([](){}); + } + + Promise& addThenFunction(std::function<void(emscripten::val)> thenFunc); + Promise& addCatchFunction(std::function<void(emscripten::val)> catchFunc); + Promise& addFinallyFunction(std::function<void()> finallyFunc); + + void suspendExclusive(); + + emscripten::val getPromise() const; + + public: + class State { + private: + friend class Promise; + + State(const State&) = delete; + State(State&&) = delete; + State& operator=(const State&) = delete; + State& operator=(State&&) = delete; + + public: + State() { ++s_numInstances; } + ~State() { --s_numInstances; } + static size_t numInstances() { return s_numInstances; } + + private: + emscripten::val m_promise = emscripten::val::undefined(); + QList<uint32_t> m_handlers; + static size_t s_numInstances; + }; + + private: + std::shared_ptr<State> m_state; + + public: + // Deprecated: To be backwards compatible + static uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers = nullptr); template<typename... Args> - uint32_t make(emscripten::val target, + static uint32_t make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args) @@ -256,7 +325,7 @@ namespace qstdweb { } template<typename... Args> - void make( + static void make( QList<uint32_t> &handlers, emscripten::val target, QString methodName, @@ -272,8 +341,8 @@ namespace qstdweb { adoptPromise(std::move(promiseObject), std::move(callbacks), &handlers); } - void Q_CORE_EXPORT suspendExclusive(QList<uint32_t> handlerIndices); - void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); + static void Q_CORE_EXPORT suspendExclusive(QList<uint32_t> handlerIndices); + static void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); }; template<class F> diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index deac396061d..34e910fabec 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -3894,10 +3894,12 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime Selects a time on the standard time side of the transition. \value PreferDaylightSaving Selects a time on the daylight-saving-time side of the transition. - \value LegacyBehavior - An alias for RelativeToBefore, which is used as default for - TransitionResolution parameters, as this most closely matches the - behavior prior to Qt 6.7. + \omitvalue LegacyBehavior + + An additional constant, \c LegacyBehavior, is used as a default value for + TransitionResolution parameters in some constructors and setter functions. + This is an alias for \c RelativeToBefore, which implements behavior that + most closely matches the behavior of QDateTime prior to Qt 6.7. For \l addDays(), \l addMonths() or \l addYears(), the behavior is and (mostly) was to use \c RelativeToBefore if adding a positive adjustment and \c @@ -3909,7 +3911,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime where the daylight-saving mechanism is a decrease in offset from UTC in winter (known as "negative DST"), the reverse applies, provided the operating system reports - as it does on most platforms - whether a datetime - is in DST or standard time. For some platforms, where transition times are + is in DST or standard time. For some platforms, where transition details are unavailable even for Qt::TimeZone datetimes, QTimeZone is obliged to presume that the side with lower offset from UTC is standard time, effectively assuming positive DST. diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h index d2c0d45b79d..585eab33ae8 100644 --- a/src/corelib/tools/qflatmap_p.h +++ b/src/corelib/tools/qflatmap_p.h @@ -42,19 +42,6 @@ QT_BEGIN_NAMESPACE QFlatMap<float, int, std::less<float>, std::vector<float>, std::vector<int>> */ -// Qt 6.4: -// - removed QFlatMap API which was incompatible with STL semantics -// - will be released with said API disabled, to catch any out-of-tree users -// - also allows opting in to the new API using QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT -// Qt 6.5 -// - will make QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT the default: - -#ifndef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT -# if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) -# define QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT -# endif -#endif - namespace Qt { struct OrderedUniqueRange_t {}; @@ -415,7 +402,6 @@ private: public: QFlatMap() = default; -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values) : c{keys, values} { @@ -451,7 +437,6 @@ public: initWithRange(first, last); ensureOrderedUnique(); } -#endif explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, const mapped_container_type &values) @@ -493,7 +478,6 @@ public: { } -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values, const Compare &compare) : value_compare(compare), c{keys, values} @@ -534,7 +518,6 @@ public: initWithRange(first, last); ensureOrderedUnique(); } -#endif explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, const mapped_container_type &values, const Compare &compare) @@ -674,7 +657,6 @@ public: return value(key); } -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT std::pair<iterator, bool> insert(const Key &key, const T &value) { return try_emplace(key, value); @@ -694,7 +676,6 @@ public: { return try_emplace(std::move(key), std::move(value)); } -#endif template <typename...Args> std::pair<iterator, bool> try_emplace(const Key &key, Args&&...args) @@ -738,7 +719,6 @@ public: return r; } -#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT template <class InputIt, is_compatible_iterator<InputIt> = nullptr> void insert(InputIt first, InputIt last) { @@ -764,7 +744,6 @@ public: { insertOrderedUniqueRange(first, last); } -#endif iterator begin() { return { &c, 0 }; } const_iterator begin() const { return { &c, 0 }; } diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake index dac2d072e42..b14f7e74ad2 100644 --- a/src/gui/configure.cmake +++ b/src/gui/configure.cmake @@ -30,6 +30,7 @@ set_property(CACHE INPUT_libpng PROPERTY STRINGS undefined no qt system) #### Libraries qt_set01(X11_SUPPORTED LINUX OR HPUX OR FREEBSD OR NETBSD OR OPENBSD OR SOLARIS OR HURD) +qt_feature_vcpkg_scope(gui) qt_find_package(ATSPI2 MODULE PROVIDED_TARGETS PkgConfig::ATSPI2 MODULE_NAME gui QMAKE_LIB atspi) qt_find_package(DirectFB PROVIDED_TARGETS PkgConfig::DirectFB MODULE_NAME gui QMAKE_LIB directfb) qt_find_package(Libdrm MODULE PROVIDED_TARGETS Libdrm::Libdrm MODULE_NAME gui QMAKE_LIB drm) @@ -39,15 +40,26 @@ qt_find_package(PlatformGraphics qt_find_package(EGL MODULE PROVIDED_TARGETS EGL::EGL MODULE_NAME gui QMAKE_LIB egl) qt_find_package(WrapSystemFreetype 2.2.0 MODULE - PROVIDED_TARGETS WrapSystemFreetype::WrapSystemFreetype MODULE_NAME gui QMAKE_LIB freetype) + PROVIDED_TARGETS WrapSystemFreetype::WrapSystemFreetype MODULE_NAME gui QMAKE_LIB freetype + VCPKG_PORT freetype + VCPKG_ADD_TO_FEATURE freetype +) if(QT_FEATURE_system_zlib) qt_add_qmake_lib_dependency(freetype zlib) endif() -qt_find_package(Fontconfig PROVIDED_TARGETS Fontconfig::Fontconfig MODULE_NAME gui QMAKE_LIB fontconfig) +qt_find_package(Fontconfig PROVIDED_TARGETS Fontconfig::Fontconfig MODULE_NAME gui + QMAKE_LIB fontconfig + VCPKG_PORT fontconfig + VCPKG_ADD_TO_FEATURE fontconfig + VCPKG_PLATFORM "linux" +) qt_add_qmake_lib_dependency(fontconfig freetype) qt_find_package(gbm MODULE PROVIDED_TARGETS gbm::gbm MODULE_NAME gui QMAKE_LIB gbm) qt_find_package(WrapSystemHarfbuzz 2.6.0 MODULE - PROVIDED_TARGETS WrapSystemHarfbuzz::WrapSystemHarfbuzz MODULE_NAME gui QMAKE_LIB harfbuzz) + PROVIDED_TARGETS WrapSystemHarfbuzz::WrapSystemHarfbuzz MODULE_NAME gui QMAKE_LIB harfbuzz + VCPKG_PORT harfbuzz + VCPKG_ADD_TO_FEATURE harfbuzz +) qt_find_package(Libinput MODULE PROVIDED_TARGETS Libinput::Libinput MODULE_NAME gui QMAKE_LIB libinput) qt_find_package_extend_sbom(TARGETS Libinput::Libinput @@ -61,11 +73,20 @@ qt_find_package_extend_sbom(TARGETS Libinput::Libinput "Copyright © 2013-2015 Red Hat, Inc." ) qt_find_package(WrapSystemJpeg MODULE - PROVIDED_TARGETS WrapSystemJpeg::WrapSystemJpeg MODULE_NAME gui QMAKE_LIB libjpeg) + PROVIDED_TARGETS WrapSystemJpeg::WrapSystemJpeg MODULE_NAME gui QMAKE_LIB libjpeg + VCPKG_PORT libjpeg-turbo + VCPKG_ADD_TO_FEATURE jpeg +) qt_find_package(WrapSystemMd4c MODULE - PROVIDED_TARGETS WrapSystemMd4c::WrapSystemMd4c MODULE_NAME gui QMAKE_LIB libmd4c) + PROVIDED_TARGETS WrapSystemMd4c::WrapSystemMd4c MODULE_NAME gui QMAKE_LIB libmd4c + VCPKG_PORT md4c + VCPKG_ADD_TO_FEATURE textmarkdownreader +) qt_find_package(WrapSystemPNG MODULE - PROVIDED_TARGETS WrapSystemPNG::WrapSystemPNG MODULE_NAME gui QMAKE_LIB libpng) + PROVIDED_TARGETS WrapSystemPNG::WrapSystemPNG MODULE_NAME gui QMAKE_LIB libpng + VCPKG_PORT libpng + VCPKG_ADD_TO_FEATURE png +) if(QT_FEATURE_system_zlib) qt_add_qmake_lib_dependency(libpng zlib) endif() diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index 7ffb688c035..b60cad34a8d 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -1829,6 +1829,8 @@ bool QFont::operator==(const QFont &f) const */ bool QFont::operator<(const QFont &f) const { + // NB: This operator actually implements greater-than, because it consistently + // swaps LHS (should be *this, but is `f`) and RHS (should be `f`, but is *this) if (f.d == d) return false; // the < operator for fontdefs ignores point sizes. const QFontDef &r1 = f.d->request; diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 9f59d11375b..ffd5d8ff333 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -859,6 +859,10 @@ void QHttpNetworkConnectionChannel::_q_disconnected() state = QHttpNetworkConnectionChannel::ReadingState; _q_receiveReply(); } + } else if (reply && reply->contentLength() == -1 && !reply->d_func()->isChunked()) { + // There was no content-length header and it's not chunked encoding, + // so this is a valid way to have the connection closed by the server + _q_receiveReply(); } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) { // re-sending request because the socket was in ClosingState QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); diff --git a/src/network/access/qnetworkaccesscachebackend_p.h b/src/network/access/qnetworkaccesscachebackend_p.h index 0bf0fdceea8..4014bbcf272 100644 --- a/src/network/access/qnetworkaccesscachebackend_p.h +++ b/src/network/access/qnetworkaccesscachebackend_p.h @@ -18,8 +18,6 @@ #include <QtNetwork/private/qtnetworkglobal_p.h> #include "qnetworkaccessbackend_p.h" -#include "qnetworkrequest.h" -#include "qnetworkreply.h" QT_BEGIN_NAMESPACE diff --git a/src/network/configure.cmake b/src/network/configure.cmake index baadf23dbd7..5dd2af418fa 100644 --- a/src/network/configure.cmake +++ b/src/network/configure.cmake @@ -7,8 +7,12 @@ #### Libraries +qt_feature_vcpkg_scope(network) qt_find_package(WrapBrotli MODULE - PROVIDED_TARGETS WrapBrotli::WrapBrotliDec MODULE_NAME network QMAKE_LIB brotli) + PROVIDED_TARGETS WrapBrotli::WrapBrotliDec MODULE_NAME network QMAKE_LIB brotli + VCPKG_PORT brotli + VCPKG_ADD_TO_FEATURE brotli +) qt_find_package(Libproxy MODULE PROVIDED_TARGETS PkgConfig::Libproxy MODULE_NAME network QMAKE_LIB libproxy) qt_find_package(GSSAPI MODULE PROVIDED_TARGETS GSSAPI::GSSAPI MODULE_NAME network QMAKE_LIB gssapi) diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h index 9a491d4d058..35fb7f24dd5 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h @@ -29,7 +29,7 @@ Q_FORWARD_DECLARE_OBJC_CLASS(NSStatusItem); QT_BEGIN_NAMESPACE -class Q_GUI_EXPORT QCocoaSystemTrayIcon : public QPlatformSystemTrayIcon +class QCocoaSystemTrayIcon : public QPlatformSystemTrayIcon { public: QCocoaSystemTrayIcon() {} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h index ba3d293e054..41946105f08 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h @@ -1,8 +1,7 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef DMABUFSERVERBUFFERINTEGRATION_H -#define DMABUFSERVERBUFFERINTEGRATION_H +#pragma once #include <QtCore/QVariant> #include <QtWaylandClient/private/qwayland-wayland.h> @@ -106,5 +105,3 @@ void DmaBufServerBufferIntegration::glEGLImageTargetTexture2DOES(GLenum target, } QT_END_NAMESPACE - -#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h index 01ac4dbe718..68db25dbfc5 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h @@ -1,8 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef DRMEGLSERVERBUFFERINTEGRATION_H -#define DRMEGLSERVERBUFFERINTEGRATION_H +#pragma once #include <QtWaylandClient/private/qwayland-wayland.h> #include <QtCore/QVariant> @@ -101,5 +100,3 @@ void DrmEglServerBufferIntegration::glEGLImageTargetTexture2DOES (GLenum target, } QT_END_NAMESPACE - -#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h index 4f9e0aeced5..d112fb32385 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h @@ -1,8 +1,7 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef SHMSERVERBUFFERINTEGRATION_H -#define SHMSERVERBUFFERINTEGRATION_H +#pragma once #include <QtWaylandClient/private/qwayland-wayland.h> #include "qwayland-shm-emulation-server-buffer.h" @@ -50,5 +49,3 @@ private: } QT_END_NAMESPACE - -#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h index a1c413be5d9..9d86c61a366 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h @@ -1,8 +1,7 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef VULKANSERVERBUFFERINTEGRATION_H -#define VULKANSERVERBUFFERINTEGRATION_H +#pragma once #include <QtWaylandClient/private/qwayland-wayland.h> #include "qwayland-qt-vulkan-server-buffer-unstable-v1.h" @@ -61,5 +60,3 @@ private: } QT_END_NAMESPACE - -#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h index 9890d14f137..756d139cac1 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef QWAYLANDEGLINTEGRATION_H -#define QWAYLANDEGLINTEGRATION_H +#pragma once #include <QtWaylandClient/private/qwaylandclientbufferintegration_p.h> @@ -59,5 +47,3 @@ private: QT_END_NAMESPACE } - -#endif // QWAYLANDEGLINTEGRATION_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h index aa45d1b1759..b18c01ac267 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h @@ -1,20 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef QWAYLANDEGLINCLUDE_H -#define QWAYLANDEGLINCLUDE_H +#pragma once #include <string.h> #include <wayland-client-core.h> @@ -23,5 +10,3 @@ #define EGL_EGLEXT_PROTOTYPES #include <QtGui/private/qt_egl_p.h> - -#endif // QWAYLANDEGLINCLUDE_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h index dc2e0319519..863bd7f0e53 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef QWAYLANDEGLWINDOW_H -#define QWAYLANDEGLWINDOW_H +#pragma once #include <QtWaylandClient/private/qwaylandwindow_p.h> #include "qwaylandeglinclude_p.h" @@ -72,5 +60,3 @@ private: } QT_END_NAMESPACE - -#endif // QWAYLANDEGLWINDOW_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h index 543c31fd727..7ea7019dfe5 100644 --- a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef QWAYLANDGLCONTEXT_H -#define QWAYLANDGLCONTEXT_H +#pragma once #include "qwaylandeglinclude_p.h" //must be first @@ -71,5 +59,3 @@ private: } QT_END_NAMESPACE - -#endif // QWAYLANDGLCONTEXT_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h index 006f648e4d6..693c179fe29 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h @@ -1,8 +1,7 @@ // Copyright (C) 2018 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDFULLSCREENSHELLV1INTEGRATION_H -#define QWAYLANDFULLSCREENSHELLV1INTEGRATION_H +#pragma once #include <wayland-client-core.h> #include <QtWaylandClient/private/qwayland-wayland.h> @@ -27,5 +26,3 @@ public: } // namespace QtWaylandClient QT_END_NAMESPACE - -#endif // QWAYLANDFULLSCREENSHELLV1INTEGRATION_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h index 0a82e5eee2a..12f551d1031 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h @@ -1,8 +1,7 @@ // Copyright (C) 2018 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDFULLSCREENSHELLV1SURFACE_H -#define QWAYLANDFULLSCREENSHELLV1SURFACE_H +#pragma once #include <QtWaylandClient/qtwaylandclientglobal.h> #include <QtWaylandClient/private/qwaylandshellsurface_p.h> @@ -28,5 +27,3 @@ private: } // namespace QtWaylandClient QT_END_NAMESPACE - -#endif // QWAYLANDFULLSCREENSHELLV1SURFACE_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h index e2c54ef0078..fd107f825e1 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2020 Aleix Pol Gonzalez <aleixpol@kde.org> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDXDGACTIVATIONV1_P_H -#define QWAYLANDXDGACTIVATIONV1_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#pragma once #include <QObject> #include "qwayland-xdg-activation-v1.h" @@ -55,4 +43,3 @@ public: } // namespace QtWaylandClient QT_END_NAMESPACE -#endif // QWAYLANDXDGACTIVATIONV1_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h index 680fcf69b9f..fc2e67b97c4 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDXDGDECORATIONV1_P_H -#define QWAYLANDXDGDECORATIONV1_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#pragma once #include "qwayland-xdg-decoration-unstable-v1.h" @@ -59,4 +47,3 @@ private: } // namespace QtWaylandClient QT_END_NAMESPACE -#endif // QWAYLANDXDGDECORATIONV1_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h index f5465a63e6f..27ebc8d5afa 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h @@ -1,8 +1,7 @@ // Copyright (C) 2022 David Reondo <kde@david-redondo.de> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDDIALOGV1_P_H -#define QWAYLANDDIALOGV1_P_H +#pragma once #include <qwayland-xdg-dialog-v1.h> @@ -28,5 +27,3 @@ public: } // namespace QtWaylandClient QT_END_NAMESPACE - -#endif diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h index c993e390a24..aa48e38c34b 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2022 David Reondo <kde@david-redondo.de> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDXDGEXPORTERV2_H -#define QWAYLANDXDGEXPORTERV2_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#pragma once #include <qwayland-xdg-foreign-unstable-v2.h> @@ -45,5 +33,3 @@ public: } QT_END_NAMESPACE - -#endif // QWAYLANDXDGEXPORTERV2_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h index 43c17672c23..bfecff41758 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h @@ -2,19 +2,7 @@ // Copyright (C) 2017 Eurogiciel, author: <philippe.coval@eurogiciel.fr> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDXDGSHELL_H -#define QWAYLANDXDGSHELL_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#pragma once #include "qwayland-xdg-shell.h" @@ -204,4 +192,3 @@ private: } // namespace QtWaylandClient QT_END_NAMESPACE -#endif // QWAYLANDXDGSHELL_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h index b7627d80462..82b2e005edd 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h @@ -1,19 +1,7 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDXDGSHELLINTEGRATION_P_H -#define QWAYLANDXDGSHELLINTEGRATION_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#pragma once #include "qwayland-xdg-shell.h" @@ -47,5 +35,3 @@ private: } QT_END_NAMESPACE - -#endif // QWAYLANDXDGSHELLINTEGRATION_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h index 63e379c4daf..25e06183f2f 100644 --- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h @@ -2,19 +2,7 @@ // Copyright (C) 2024 David Edmundson <davidedmundson@kde.org> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QWAYLANDTOPLEVELICONV1_P_H -#define QWAYLANDTOPLEVELICONV1_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// +#pragma once #include "wayland-xdg-shell-client-protocol.h" #include <qwayland-xdg-toplevel-icon-v1.h> @@ -45,8 +33,7 @@ private: QList<int> mPreferredSizes; QWaylandDisplay *mDisplay; }; + } // namespace QtWaylandClient QT_END_NAMESPACE - -#endif diff --git a/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp b/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp index e2e46a7b215..abd6f02fb0b 100644 --- a/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp +++ b/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp @@ -11,6 +11,36 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +QString QWindowsWindowClassDescription::classNameSuffix(Qt::WindowFlags type, unsigned int style, bool hasIcon) +{ + QString suffix; + + switch (type) { + case Qt::Popup: + suffix += "Popup"_L1; + break; + case Qt::Tool: + suffix += "Tool"_L1; + break; + case Qt::ToolTip: + suffix += "ToolTip"_L1; + break; + default: + break; + } + + if (style & CS_DROPSHADOW) + suffix += "DropShadow"_L1; + if (style & CS_SAVEBITS) + suffix += "SaveBits"_L1; + if (style & CS_OWNDC) + suffix += "OwnDC"_L1; + if (hasIcon) + suffix += "Icon"_L1; + + return suffix; +} + QWindowsWindowClassDescription QWindowsWindowClassDescription::fromName(QString name, WNDPROC procedure) { return { std::move(name), procedure }; @@ -48,29 +78,7 @@ QWindowsWindowClassDescription QWindowsWindowClassDescription::fromWindow(const description.hasIcon = false; // QTBUG-2027, dialogs without system menu. break; } - // Create a unique name for the flag combination - description.name = "QWindow"_L1; - switch (type) { - case Qt::Tool: - description.name += "Tool"_L1; - break; - case Qt::ToolTip: - description.name += "ToolTip"_L1; - break; - case Qt::Popup: - description.name += "Popup"_L1; - break; - default: - break; - } - if (description.style & CS_DROPSHADOW) - description.name += "DropShadow"_L1; - if (description.style & CS_SAVEBITS) - description.name += "SaveBits"_L1; - if (description.style & CS_OWNDC) - description.name += "OwnDC"_L1; - if (description.hasIcon) - description.name += "Icon"_L1; + description.name = "QWindow"_L1 + classNameSuffix(type, description.style, description.hasIcon); return description; } diff --git a/src/plugins/platforms/windows/qwindowswindowclassdescription.h b/src/plugins/platforms/windows/qwindowswindowclassdescription.h index 3acca65b8a2..692bf18e618 100644 --- a/src/plugins/platforms/windows/qwindowswindowclassdescription.h +++ b/src/plugins/platforms/windows/qwindowswindowclassdescription.h @@ -25,6 +25,8 @@ struct QWindowsWindowClassDescription bool shouldAddPrefix{ true }; private: + static QString classNameSuffix(Qt::WindowFlags type, unsigned int style, bool hasIcon); + friend QDebug operator<<(QDebug dbg, const QWindowsWindowClassDescription &description); }; diff --git a/src/plugins/platforms/windows/qwindowswindowclassregistry.cpp b/src/plugins/platforms/windows/qwindowswindowclassregistry.cpp index c918407a27a..8bf2fb887f1 100644 --- a/src/plugins/platforms/windows/qwindowswindowclassregistry.cpp +++ b/src/plugins/platforms/windows/qwindowswindowclassregistry.cpp @@ -69,25 +69,19 @@ QString QWindowsWindowClassRegistry::registerWindowClass(const QWindowsWindowCla // add a UUID. The check needs to be performed for each name // in case new message windows are added (QTBUG-81347). // Note: GetClassInfo() returns != 0 when a class exists. - const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr)); - WNDCLASS wcinfo; - const bool classExists = GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(className.utf16()), &wcinfo) != FALSE - && wcinfo.lpfnWndProc != description.procedure; - - if (classExists) + if (shouldDecorateWindowClassName(description)) className += QUuid::createUuid().toString(); if (m_registeredWindowClassNames.contains(className)) // already registered in our list return className; - WNDCLASSEX wc; + const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr)); + + WNDCLASSEX wc{}; wc.cbSize = sizeof(WNDCLASSEX); wc.style = description.style; wc.lpfnWndProc = description.procedure; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; wc.hInstance = appInstance; - wc.hCursor = nullptr; wc.hbrBackground = description.brush; if (description.hasIcon) { wc.hIcon = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); @@ -98,15 +92,9 @@ QString QWindowsWindowClassRegistry::registerWindowClass(const QWindowsWindowCla } else { wc.hIcon = static_cast<HICON>(LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED)); - wc.hIconSm = nullptr; } } - else { - wc.hIcon = nullptr; - wc.hIconSm = nullptr; - } - wc.lpszMenuName = nullptr; wc.lpszClassName = reinterpret_cast<LPCWSTR>(className.utf16()); ATOM atom = RegisterClassEx(&wc); if (!atom) @@ -130,6 +118,21 @@ QString QWindowsWindowClassRegistry::registerWindowClass(QString name, WNDPROC p return registerWindowClass(QWindowsWindowClassDescription::fromName(name, procedure)); } +bool QWindowsWindowClassRegistry::shouldDecorateWindowClassName(const QWindowsWindowClassDescription &description) const +{ + return shouldDecorateWindowClassName(description.name, description.procedure); +} + +bool QWindowsWindowClassRegistry::shouldDecorateWindowClassName(const QString &name, WNDPROC procedure) const +{ + const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr)); + + WNDCLASS wc{}; + + return GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(name.utf16()), &wc) != FALSE + && wc.lpfnWndProc != procedure; +} + void QWindowsWindowClassRegistry::unregisterWindowClasses() { const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr)); diff --git a/src/plugins/platforms/windows/qwindowswindowclassregistry.h b/src/plugins/platforms/windows/qwindowswindowclassregistry.h index 44f274de2b2..c4a36b7f3c4 100644 --- a/src/plugins/platforms/windows/qwindowswindowclassregistry.h +++ b/src/plugins/platforms/windows/qwindowswindowclassregistry.h @@ -34,6 +34,8 @@ public: private: static QString classNamePrefix(); + bool shouldDecorateWindowClassName(const QWindowsWindowClassDescription &description) const; + bool shouldDecorateWindowClassName(const QString &name, WNDPROC procedure) const; void unregisterWindowClasses(); static QWindowsWindowClassRegistry *m_instance; diff --git a/src/plugins/sqldrivers/configure.cmake b/src/plugins/sqldrivers/configure.cmake index d96c220d70a..d12917f5631 100644 --- a/src/plugins/sqldrivers/configure.cmake +++ b/src/plugins/sqldrivers/configure.cmake @@ -13,12 +13,22 @@ set_property(CACHE INPUT_sqlite PROPERTY STRINGS undefined qt system) #### Libraries +qt_feature_vcpkg_scope(sql) qt_find_package(DB2 MODULE PROVIDED_TARGETS DB2::DB2 MODULE_NAME sqldrivers QMAKE_LIB db2) -qt_find_package(MySQL MODULE PROVIDED_TARGETS MySQL::MySQL MODULE_NAME sqldrivers QMAKE_LIB mysql) -qt_find_package(PostgreSQL MODULE PROVIDED_TARGETS PostgreSQL::PostgreSQL MODULE_NAME sqldrivers QMAKE_LIB psql) +qt_find_package(MySQL MODULE PROVIDED_TARGETS MySQL::MySQL MODULE_NAME sqldrivers QMAKE_LIB mysql + VCPKG_PORT libmysql + VCPKG_ADD_TO_FEATURE sql-mysql +) +qt_find_package(PostgreSQL MODULE PROVIDED_TARGETS PostgreSQL::PostgreSQL MODULE_NAME sqldrivers QMAKE_LIB psql + VCPKG_PORT libpq + VCPKG_ADD_TO_FEATURE sql-psql +) qt_find_package(Oracle MODULE PROVIDED_TARGETS Oracle::OCI MODULE_NAME sqldrivers QMAKE_LIB oci) qt_find_package(ODBC PROVIDED_TARGETS ODBC::ODBC MODULE_NAME sqldrivers QMAKE_LIB odbc) -qt_find_package(SQLite3 PROVIDED_TARGETS SQLite::SQLite3 MODULE_NAME sqldrivers QMAKE_LIB sqlite3) +qt_find_package(SQLite3 PROVIDED_TARGETS SQLite::SQLite3 MODULE_NAME sqldrivers QMAKE_LIB sqlite3 + VCPKG_PORT sqlite3 + VCPKG_ADD_TO_FEATURE sql-sqlite +) qt_find_package(Interbase MODULE PROVIDED_TARGETS Interbase::Interbase MODULE_NAME sqldrivers QMAKE_LIB ibase) # special case qt_find_package(Mimer MODULE PROVIDED_TARGETS MimerSQL::MimerSQL MODULE_NAME sqldrivers QMAKE_LIB mimer) @@ -60,6 +70,7 @@ qt_feature("sql-psql" PRIVATE qt_feature("sql-sqlite" PRIVATE LABEL "SQLite" CONDITION QT_FEATURE_datestring + VCPKG_DEFAULT ) qt_feature("system-sqlite" PRIVATE SYSTEM_LIBRARY LABEL " Using system provided SQLite" diff --git a/tests/auto/corelib/global/q23/expected/tst_q23_expected.cpp b/tests/auto/corelib/global/q23/expected/tst_q23_expected.cpp index 6de836da9d0..92df31d1a75 100644 --- a/tests/auto/corelib/global/q23/expected/tst_q23_expected.cpp +++ b/tests/auto/corelib/global/q23/expected/tst_q23_expected.cpp @@ -20,7 +20,7 @@ private Q_SLOTS: void tst_q23_expected::value_throw_exception_unreachable_data() { QTest::addColumn<bool>("unexpected"); - QTest::addRow("") << false; + QTest::addRow("expected") << false; } void tst_q23_expected::value_throw_exception_unreachable() diff --git a/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt b/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt index 98509dc0e5f..1d320b49c86 100644 --- a/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt +++ b/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt @@ -14,8 +14,16 @@ endif() qt_internal_add_test(tst_qjniobject SOURCES tst_qjniobject.cpp + LIBRARIES + Qt::CorePrivate ) +# if we can, enable C++23 so that we can use std::expected in the test +# otherwise the test will use our own tl::expected copy from qexpected_p.h +if ("${CMAKE_CXX_COMPILE_FEATURES}" MATCHES "cxx_std_23") + set_property(TARGET tst_qjniobject PROPERTY CXX_STANDARD 23) +endif() + if(ANDROID) set_property(TARGET tst_qjniobject APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/testdata diff --git a/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java b/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java index e0765fcb7e0..bdec173ca97 100644 --- a/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java +++ b/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java @@ -137,6 +137,10 @@ public class QtJniObjectTestClass public Throwable throwableMethod() { return staticThrowableMethod(); } // -------------------------------------------------------------------------------------------- + public static void staticThrowingMethod() throws Throwable { throw new Throwable(A_STRING_OBJECT); } + public void throwingMethod() throws Throwable { throw new Throwable(A_STRING_OBJECT); } + + // -------------------------------------------------------------------------------------------- public static Object[] staticObjectArrayMethod() { Object[] array = { new Object(), new Object(), new Object() }; return array; } public Object[] objectArrayMethod() { return staticObjectArrayMethod(); } diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp index c06b500778b..215b3bf3b78 100644 --- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp +++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp @@ -8,12 +8,20 @@ #include <QtCore/QJniObject> #include <QTest> +#if defined(__cpp_lib_expected) +# include <expected> +#else +# include <QtCore/private/qexpected_p.h> +#endif + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; static constexpr const char testClassName[] = "org/qtproject/qt/android/testdatapackage/QtJniObjectTestClass"; Q_DECLARE_JNI_CLASS(QtJniObjectTestClass, testClassName) +Q_DECLARE_JNI_CLASS(NoSuchClass, "no/such/Class") + using TestClass = QtJniTypes::QtJniObjectTestClass; static const jbyte A_BYTE_VALUE = 127; @@ -125,6 +133,24 @@ private slots: void callback(); void callStaticOverloadResolution(); + void implicitExceptionHandling_construct(); + void implicitExceptionHandling_callMethod(); + void implicitExceptionHandling_callStaticMethod(); + void implicitExceptionHandling_getField(); + void implicitExceptionHandling_setField(); + void implicitExceptionHandling_getStaticField(); + void implicitExceptionHandling_setStaticField(); + + void constructWithException(); + void callMethodWithException(); + void callMethodWithMonadic(); + void callMethodWithTryCatch(); + void callStaticMethodWithException(); + void getFieldWithException(); + void setFieldWithException(); + void getStaticFieldWithException(); + void setStaticFieldWithException(); + void cleanupTestCase(); }; @@ -2303,8 +2329,547 @@ void tst_QJniObject::callStaticOverloadResolution() QCOMPARE(result, value); } -QT_END_NAMESPACE +void tst_QJniObject::implicitExceptionHandling_construct() +{ + QJniEnvironment env; + const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*"); + const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*"); + + // Constructor, including named constructor + { + QTest::ignoreMessage(QtWarningMsg, invalidClass); + QJniObject testObject("NoSuchClass"); + QVERIFY(!env.checkAndClearExceptions()); + } + { + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + QJniObject testObject = QJniObject::construct<TestClass>(u"NoSuchConstructor"_s); + QVERIFY(!env.checkAndClearExceptions()); + } +} + +void tst_QJniObject::implicitExceptionHandling_callMethod() +{ + QJniEnvironment env; + const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*"); + const QRegularExpression throwingMethod("java.lang.Throwable: .*"); + + QJniObject testObject = QJniObject::construct<TestClass>(); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + testObject.callMethod<void>("noSuchMethod1"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + testObject.callMethod<void>("noSuchMethod2", "()V"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, throwingMethod); + testObject.callMethod<void>("throwingMethod"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + (void)testObject.callObjectMethod<jstring>("noSuchMethod"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + testObject.callObjectMethod("noSuchMethod", "()V"); + QVERIFY(!env.checkAndClearExceptions()); +} + +void tst_QJniObject::implicitExceptionHandling_callStaticMethod() +{ + QJniEnvironment env; + const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*"); + const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*"); + const QRegularExpression throwingMethod("java.lang.Throwable: .*"); + + const jclass classId = env.findClass<TestClass>(); + QVERIFY(classId != nullptr); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + TestClass::callStaticMethod<void>("noSuchStaticMethod"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + QJniObject::callStaticMethod<void>(QtJniTypes::Traits<TestClass>::className(), + "noSuchStaticMethod2", "()V"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidClass); + QJniObject::callStaticMethod<void>("noSuchClass", "noSuchStaticMethod2", "()V"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, throwingMethod); + jmethodID methodId = env.findStaticMethod<void>(classId, "staticThrowingMethod"); + QVERIFY(methodId != nullptr); + QJniObject::callStaticMethod<void>(classId, methodId); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidClass); + QJniObject::callStaticObjectMethod("noSuchClass", "noSuchStaticMethod", "()V"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + QJniObject::callStaticObjectMethod(classId, "noSuchStaticMethod", "()V"); + QVERIFY(!env.checkAndClearExceptions()); +} + +void tst_QJniObject::implicitExceptionHandling_getField() +{ + QJniEnvironment env; + const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*"); + + QJniObject testObject = QJniObject::construct<TestClass>(); + QTest::ignoreMessage(QtWarningMsg, invalidField); + (void)testObject.getField<int>("noSuchField"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + (void)testObject.getObjectField<jobject>("noSuchObjectField"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + (void)testObject.getObjectField("noSuchObjectField2", "Ljava/lang/Object;"); + QVERIFY(!env.checkAndClearExceptions()); +} + +void tst_QJniObject::implicitExceptionHandling_setField() +{ + QJniEnvironment env; + const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*"); + + QJniObject testObject = QJniObject::construct<TestClass>(); + QTest::ignoreMessage(QtWarningMsg, invalidField); + testObject.setField("noSuchField", 123); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + testObject.setField("BOOLEAN_VAR", "I", 123); + QVERIFY(!env.checkAndClearExceptions()); + + // make sure that code specifying deducible type explicitly still works + static_assert(std::is_same_v<decltype(testObject.setField<jboolean>("BOOLEAN_VAR", true)), + void>); +} + +void tst_QJniObject::implicitExceptionHandling_getStaticField() +{ + QJniEnvironment env; + const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*"); + const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*"); + + const jclass classId = env.findClass<TestClass>(); + QVERIFY(classId != nullptr); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + (void)TestClass::getStaticField<int>("noSuchStaticField"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + (void)QJniObject::getStaticField<int>(classId, "noSuchStaticField"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidClass); + (void)QJniObject::getStaticObjectField("noSuchClass", "noSuchStaticField", "I"); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + (void)QJniObject::getStaticObjectField(classId, "S_BOOLEAN_VAR", "I"); + QVERIFY(!env.checkAndClearExceptions()); +} + +void tst_QJniObject::implicitExceptionHandling_setStaticField() +{ + QJniEnvironment env; + const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*"); + const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*"); + + const jclass classId = env.findClass<TestClass>(); + QVERIFY(classId != nullptr); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + TestClass::setStaticField("noSuchStaticField", 123); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + QJniObject::setStaticField(classId, "noSuchStaticField", 123); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidClass); + QJniObject::setStaticField("noSuchClass", "noSuchStaticField", 123); + QVERIFY(!env.checkAndClearExceptions()); + + QTest::ignoreMessage(QtWarningMsg, invalidField); + QJniObject::setStaticField(QtJniTypes::Traits<TestClass>::className(), + "S_BOOLEAN_VAR", "I", 123); + QVERIFY(!env.checkAndClearExceptions()); + + // make sure that code specifying deducible type explicitly still works + static_assert(std::is_same_v<decltype(TestClass::setStaticField<jboolean>("S_BOOLEAN_VAR", true)), + void>); +} + +#if __cpp_lib_expected +template <typename T> +using QJniReturnValue = std::expected<T, jthrowable>; +using BadAccessException = std::bad_expected_access<jthrowable>; +// even with __cpp_lib_expected >= 202211L, monadic functions seem to be rather +// broken or not reliably available +#define EXPECTED_HAS_MONADIC false +#elif TL_EXPECTED_VERSION_MAJOR +#define EXPECTED_HAS_MONADIC true +template <typename T> +using QJniReturnValue = tl::expected<T, jthrowable>; +using BadAccessException = tl::bad_expected_access<jthrowable>; +#endif + +static_assert(QtJniTypes::Traits<QJniReturnValue<int>>::signature() == + QtJniTypes::Traits<int>::signature()); +static_assert(QtJniTypes::Traits<QJniReturnValue<QString>>::signature() == + QtJniTypes::Traits<QString>::signature()); + + +void tst_QJniObject::constructWithException() +{ +#if __cpp_lib_expected + qInfo() << "Testing explicit exception handling with std::expected" << __cpp_lib_expected; +#elif defined(TL_EXPECTED_VERSION_MAJOR) + qInfo() << "Testing explicit exception handling with tl::expected"; +#else + qInfo() << "Testing explicit exception handling with QJniReturnValue"; +#endif + + const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*"); + { + QTest::ignoreMessage(QtWarningMsg, invalidClass); + QJniObject invalid = QJniObject::construct<QtJniTypes::NoSuchClass>(); + } + + QVERIFY(!QJniEnvironment().checkAndClearExceptions()); + + { + // can only handle exceptions when using the named constructor + auto result = QJniObject::construct<QJniReturnValue<TestClass>>(); + QVERIFY(result); + result = QJniObject::construct<QJniReturnValue<TestClass>>(u"123"_s); + QVERIFY(!result); + QVERIFY(result.error()); + } + + QVERIFY(!QJniEnvironment().checkAndClearExceptions()); + + { + const auto result = QJniObject::construct<QJniReturnValue<QtJniTypes::NoSuchClass>>(); + QVERIFY(!result); + QVERIFY(result.error()); + } + + QVERIFY(!QJniEnvironment().checkAndClearExceptions()); + + { + // no way to prevent implicit exception here + QTest::ignoreMessage(QtWarningMsg, invalidClass); + QtJniTypes::NoSuchClass noSuchClass; + QVERIFY(!noSuchClass.isValid()); + } + + QVERIFY(!QJniEnvironment().checkAndClearExceptions()); +} + +void tst_QJniObject::callMethodWithException() +{ + TestClass testObject; + { + auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod"); + QVERIFY(result); + result = testObject.callMethod<QJniReturnValue<void>>("voidMethod", 123); + QVERIFY(!result); + QVERIFY(result.error()); + } + + { + auto result = testObject.callMethod<QJniReturnValue<jint>>("intMethod"); + QVERIFY(result); + QCOMPARE(result.value(), A_INT_VALUE); + result = testObject.callMethod<QJniReturnValue<jint>>("intMethod", 123); + QVERIFY(!result); + QVERIFY(result.error()); + QCOMPARE(result.value_or(456), 456); + } + + { + auto result = testObject.callMethod<QJniReturnValue<jstring>>("stringMethod"); + QVERIFY(result); + QVERIFY(result.value()); + result = testObject.callMethod<QJniReturnValue<jstring>>("stringMethod", 123); + QVERIFY(!result); + QVERIFY(result.error()); + } + + { + auto result = testObject.callMethod<QJniReturnValue<QString>>("stringMethod"); + QVERIFY(result); + QVERIFY(!result.value().isEmpty()); + result = testObject.callMethod<QJniReturnValue<QString>>("stringMethod", 123); + QVERIFY(!result); + QVERIFY(result.error()); + QCOMPARE(result.value_or(u"Default"_s), u"Default"_s); + } + + { + QJniArray<jboolean> newArray(QList<jboolean>{true, false, false}); + auto result = testObject.callMethod<QJniReturnValue<QJniArray<jboolean>>>("reverseBooleanArray", newArray); + // this shorthand cannot work with e.g. std::expected + // result = testObject.callMethod<QJniReturnValue<jboolean[]>>("reverseBooleanArray", newArray); + QVERIFY(result); + QCOMPARE(result.value().toContainer(), (QList<jboolean>{false, false, true})); + result = testObject.callMethod<QJniReturnValue<QJniArray<jboolean>>>("reverseBooleanArray", 123); + QVERIFY(!result); + QVERIFY(result.error()); + } + + // throwing method - QJniObject cleans the exception and prints qWarning + { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"java.lang.Throwable: "_s + A_STRING_OBJECT() + u".*"_s)); + testObject.callMethod<void>("throwingMethod"); + + auto result = testObject.callMethod<QJniReturnValue<void>>("throwingMethod"); + QVERIFY(!result); + const QStringList stackTrace = QJniEnvironment::stackTrace(result.error()); + QCOMPARE_GE(stackTrace.size(), 1); + QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT()); + } +} + +void tst_QJniObject::callMethodWithMonadic() +{ +#if !EXPECTED_HAS_MONADIC + QSKIP("Used version of std::expected does not have monadic functions"); +#else + enum Monadic { + AndThen, + OrElse, + Transform, + }; + + TestClass testObject; + { + QList<Monadic> flow; + const auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod") + .and_then([&flow]{ + flow << AndThen; + return QJniReturnValue<void>(); + }) + .or_else([&flow](jthrowable error){ + if (error) + flow << OrElse; + else + qWarning("Invalid call to or_else monadic"); + return QJniReturnValue<void>(); + }) + .transform([&flow](){ + flow << Transform; + return 42; + }); + QCOMPARE(flow, (QList<Monadic>{AndThen, Transform})); + QVERIFY(result); + QCOMPARE(*result, 42); + } + { + QList<Monadic> flow; + const auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod", 123) + .and_then([&flow]{ + flow << AndThen; + return QJniReturnValue<void>(); + }) + .or_else([&flow](jthrowable error){ + if (error) + flow << OrElse; + else + qWarning("Invalid call to or_else monadic"); + return QJniReturnValue<void>(typename QJniReturnValue<void>::unexpected_type(error)); + }) + .transform([&flow](){ + flow << Transform; + return 42; + }); + QCOMPARE(flow, (QList<Monadic>{OrElse})); + QVERIFY(!result); + } + + { + QList<Monadic> flow; + const auto result = testObject.callMethod<QJniReturnValue<jobject>>("objectMethod") + .and_then([&flow](auto &&obj){ + flow << AndThen; + return QJniReturnValue<jobject>(obj); + }) + .or_else([&flow](jthrowable error){ + if (error) + flow << OrElse; + else + qWarning("Invalid call to or_else monadic"); + return QJniReturnValue<jobject>(typename QJniReturnValue<void>::unexpected_type(error)); + }) + .transform([&flow](auto &&obj){ + flow << Transform; + return QJniObject(obj).template getField<int>("INT_FIELD"); + }) + .and_then([&flow](auto value){ + flow << AndThen; + return QJniReturnValue<int>(value * 2); + }); + QCOMPARE(flow, (QList<Monadic>{AndThen, Transform, AndThen})); + QVERIFY(result); + QCOMPARE(*result, 246); + } + { + QList<Monadic> flow; + const auto result = testObject.callMethod<QJniReturnValue<jobject>>("objectMethod", 123) + .and_then([&flow](const QJniObject &obj){ + flow << AndThen; + return QJniReturnValue<jobject>(obj.object()); + }) + .or_else([&flow](jthrowable error){ + if (error) + flow << OrElse; + else + qWarning("Invalid call to or_else monadic"); + return QJniReturnValue<jobject>(typename QJniReturnValue<jobject>::unexpected_type(error)); + }) + .transform([&flow](const QJniObject &obj){ + flow << Transform; + return obj.getField<int>("INT_FIELD"); + }); + QCOMPARE(flow, (QList<Monadic>{OrElse})); + QVERIFY(!result); + } +#endif +} + +void tst_QJniObject::callMethodWithTryCatch() +{ + TestClass testObject; + + const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*"); + QTest::ignoreMessage(QtWarningMsg, invalidMethod); + + try { + const auto result = testObject.callMethod<QJniReturnValue<QJniObject>>("objectMethod", 123); + result.value().getField<int>("INT_FIELD"); + } + catch (BadAccessException &e) { + qWarning().noquote() << QJniEnvironment::stackTrace(e.error()).join('\n'); + } +} + +void tst_QJniObject::callStaticMethodWithException() +{ + { + auto result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod"); + QVERIFY(result); + QCOMPARE(*result, A_INT_VALUE); + result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod", 123); + QVERIFY(!result && result.error()); + } + + { + auto result = TestClass::callStaticMethod<QJniReturnValue<QString>>("staticStringMethod"); + QVERIFY(result); + QCOMPARE(*result, A_STRING_OBJECT()); + result = TestClass::callStaticMethod<QJniReturnValue<QString>>("staticStringMethod", 123); + QVERIFY(!result && result.error()); + } + + // throwing method + { + const auto result = TestClass::callStaticMethod<QJniReturnValue<void>>("staticThrowingMethod"); + QVERIFY(!result); + QStringList stackTrace = QJniEnvironment::stackTrace(result.error()); + QCOMPARE_GE(stackTrace.size(), 1); + QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT()); + } +} + +void tst_QJniObject::getFieldWithException() +{ + TestClass testObject; + { + auto result = testObject.getField<QJniReturnValue<jboolean>>("BOOL_FIELD"); + QVERIFY(result); + result = testObject.getField<QJniReturnValue<jboolean>>("INVALID_BOOL"); + QVERIFY(!result && result.error()); + } + + { + auto result = testObject.getField<QJniReturnValue<QString>>("STRING_OBJECT_VAR"); + QVERIFY(result); + result = testObject.getField<QJniReturnValue<QString>>("INVALID_STRING"); + QVERIFY(!result && result.error()); + } +} + +void tst_QJniObject::setFieldWithException() +{ + TestClass testObject; + { + auto result = testObject.setField<QJniReturnValue<jboolean>>("BOOL_FIELD", true); + QVERIFY(result); + result = testObject.setField<QJniReturnValue<jboolean>>("SET_INVALID_BOOL", true); + QVERIFY(!result); + QVERIFY(result.error()); + } + + { + auto result = testObject.setField<QJniReturnValue<QString>>("STRING_OBJECT_VAR", u"test"_s); + QVERIFY(result); + result = testObject.setField<QJniReturnValue<QString>>("SET_INVALID_STRING", u"test"_s); + QVERIFY(!result); + QVERIFY(result.error()); + } +} + +void tst_QJniObject::getStaticFieldWithException() +{ + { + auto result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_SHORT_VAR"); + QVERIFY(result); + result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_INVALID_SHORT"); + QVERIFY(!result); + QVERIFY(result.error()); + } + + { + auto result = TestClass::getStaticField<QJniReturnValue<QString>>("S_STRING_OBJECT_VAR"); + QVERIFY(result); + result = TestClass::getStaticField<QJniReturnValue<QString>>("S_INVALID_STRING"); + QVERIFY(!result); + QVERIFY(result.error()); + } +} + +void tst_QJniObject::setStaticFieldWithException() +{ + { + auto result = TestClass::setStaticField<QJniReturnValue<jboolean>>("S_BOOLEAN_VAR", true); + QVERIFY(result); + result = TestClass::setStaticField<QJniReturnValue<jboolean>>("SET_S_INVALID_BOOL", true); + QVERIFY(!result); + QVERIFY(result.error()); + } + + { + auto result = TestClass::setStaticField<QJniReturnValue<QString>>("S_STRING_OBJECT_VAR", u"test"_s); + QVERIFY(result); + result = TestClass::setStaticField<QJniReturnValue<QString>>("SET_S_INVALID_STRING", u"test"_s); + QVERIFY(!result); + QVERIFY(result.error()); + } +} QTEST_MAIN(tst_QJniObject) +QT_END_NAMESPACE + #include "tst_qjniobject.moc" diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index db2b18a5c01..8217da843e0 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -9409,7 +9409,7 @@ void tst_QString::rawData() void tst_QString::testUtf16() { { - const char16_t arr[] = {'a', 'b', 'c'}; + constexpr char16_t arr[] = {u'a', u'b', u'c'}; QString s = QString::fromRawData(arr, 3); // doesn't guarantee null-termination QCOMPARE(s.size(), qsizetype(std::size(arr))); // The string points to the raw data diff --git a/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp b/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp index b53362b43e9..f4699f92959 100644 --- a/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp +++ b/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp @@ -20,6 +20,7 @@ private Q_SLOTS: void exceptions(); void earlyExitScope(); void mixedTypes(); + void sharedPtr(); private: void earlyExitScope_helper(int exitpoint, std::atomic<int> &member); }; @@ -221,6 +222,34 @@ void tst_QAtomicScopedValueRollback::mixedTypes() } } +void tst_QAtomicScopedValueRollback::sharedPtr() +{ +#ifdef __cpp_lib_atomic_shared_ptr + std::atomic<std::shared_ptr<int>> a{std::make_shared<int>(42)}; + QCOMPARE_NE(a.load(), nullptr); + QCOMPARE_EQ(*a.load(), 42); + { + QAtomicScopedValueRollback rb(a, nullptr); + QCOMPARE_EQ(a.load(), nullptr); + } + QCOMPARE_NE(a.load(), nullptr); + QCOMPARE_EQ(*a.load(), 42); + { + QAtomicScopedValueRollback rb{a, std::make_shared<int>(123)}; + QCOMPARE_NE(a.load(), nullptr); + QCOMPARE_EQ(*a.load(), 123); + rb.commit(); + a.store(std::make_shared<int>(256)); + QCOMPARE_NE(a.load(), nullptr); + QCOMPARE_EQ(*a.load(), 256); + } + QCOMPARE_NE(a.load(), nullptr); + QCOMPARE_EQ(*a.load(), 123); +#else + QSKIP("This test requires atomic<shared_ptr> support enabled in the C++ standard library."); +#endif +} + static void operator*=(std::atomic<int> &lhs, int rhs) { int expected = lhs.load(); diff --git a/tests/auto/corelib/tools/qflatmap/tst_qflatmap.cpp b/tests/auto/corelib/tools/qflatmap/tst_qflatmap.cpp index 986cf2407b7..fd960581cf9 100644 --- a/tests/auto/corelib/tools/qflatmap/tst_qflatmap.cpp +++ b/tests/auto/corelib/tools/qflatmap/tst_qflatmap.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #define QT_USE_QSTRINGBUILDER -#define QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT #include <QTest> diff --git a/tests/auto/wasm/CMakeLists.txt b/tests/auto/wasm/CMakeLists.txt index 35b4d45e533..ffa2b9ca98c 100644 --- a/tests/auto/wasm/CMakeLists.txt +++ b/tests/auto/wasm/CMakeLists.txt @@ -6,4 +6,5 @@ add_subdirectory(localfileapi) add_subdirectory(qwasmkeytranslator) add_subdirectory(qwasmwindowstack) add_subdirectory(qwasmwindowtreenode) +add_subdirectory(qwasmpromise) add_subdirectory(selenium) diff --git a/tests/auto/wasm/qwasmpromise/CMakeLists.txt b/tests/auto/wasm/qwasmpromise/CMakeLists.txt new file mode 100644 index 00000000000..44e6203ec86 --- /dev/null +++ b/tests/auto/wasm/qwasmpromise/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qwasmpromise Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwasmpromise LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qwasmpromise + SOURCES + tst_qwasmpromise.cpp + DEFINES + QT_NO_FOREACH + LIBRARIES + Qt::Core + Qt::Gui + Qt::GuiPrivate +) diff --git a/tests/auto/wasm/qwasmpromise/tst_qwasmpromise.cpp b/tests/auto/wasm/qwasmpromise/tst_qwasmpromise.cpp new file mode 100644 index 00000000000..0c8582d49a0 --- /dev/null +++ b/tests/auto/wasm/qwasmpromise/tst_qwasmpromise.cpp @@ -0,0 +1,526 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> + +#include <emscripten/val.h> +#include <emscripten.h> + +#include <QtCore/private/qstdweb_p.h> + +namespace { + +emscripten::val g_testSupport; + +void init() { + g_testSupport = emscripten::val::object(); + EM_ASM({ + var testSupport = Emval.toValue($0); + testSupport.resolve = {}; + testSupport.reject = {}; + testSupport.promises = {}; + testSupport.waitConditionPromise = new Promise((resolve, reject) => { + testSupport.finishWaiting = resolve; + }); + + testSupport.makeTestPromise = (param) => { + testSupport.promises[param] = new Promise((resolve, reject) => { + testSupport.resolve[param] = resolve; + testSupport.reject[param] = reject; + }); + + return testSupport.promises[param]; + }; + }, g_testSupport.as_handle()); +} +} + +class tst_QWasmPromise : public QObject +{ + Q_OBJECT + +public: + tst_QWasmPromise() = default; + +private slots: + void init(); + void suspendExclusive(); + void simpleResolve(); + void multipleResolve(); + void simpleReject(); + void multipleReject(); + void throwInThen(); + void bareFinally(); + void finallyWithThen(); + void finallyWithThrow(); + void finallyWithThrowInThen(); + void nested(); + void all(); + void allWithThrow(); + void allWithFinally(); + void allWithFinallyAndThrow(); +}; + +static bool g_Done = false; + +#define QWASMDONE() g_Done = true; + +void tst_QWasmPromise::init() { + g_Done = false; + ::init(); +} + +class BarrierCallback { +public: + BarrierCallback(int number, std::function<void()> onDone) + : m_remaining(number), m_onDone(std::move(onDone)) {} + + void operator()() { + if (!--m_remaining) { + m_onDone(); + } + } + +private: + int m_remaining; + std::function<void()> m_onDone; +}; + +void tst_QWasmPromise::suspendExclusive() +{ + init(); + + { + auto promise = qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("simpleResolve")) + .addThenFunction([](emscripten::val result) { + QVERIFY(result.isString()); + QCOMPARE("Some lovely data", result.as<std::string>()); + QWASMDONE(); + }) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + QFAIL("Unexpected catch"); + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("simpleResolve", std::string("Some lovely data")); + promise.suspendExclusive(); + } + QVERIFY(g_Done); + QVERIFY(qstdweb::Promise::State::numInstances() == 0); +} + +void tst_QWasmPromise::simpleResolve() +{ + init(); + + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("simpleResolve")) + .addThenFunction([](emscripten::val result) { + QVERIFY(result.isString()); + QCOMPARE("Some lovely data", result.as<std::string>()); + QWASMDONE(); + }) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + QFAIL("Unexpected catch"); + QWASMDONE(); + }) + .addFinallyFunction([](){}); + + g_testSupport["resolve"].call<void>("simpleResolve", std::string("Some lovely data")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::multipleResolve() +{ + init(); + static constexpr int promiseCount = 1000; + + auto onThen = std::make_shared<BarrierCallback>(promiseCount, []() { + QWASMDONE(); + }); + + for (int i = 0; i < promiseCount; ++i) { + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + (QStringLiteral("test") + QString::number(i)).toStdString()) + .addThenFunction([=](emscripten::val result) { + QVERIFY(result.isString()); + QCOMPARE(QString::number(i).toStdString(), result.as<std::string>()); + (*onThen)(); + }) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + QFAIL("Unexpected catch"); + QWASMDONE(); + }); + } + + for (int i = 0; i < promiseCount; ++i) + g_testSupport["resolve"].call<void>(("test" + std::to_string(i)).c_str(), std::to_string(i)); + + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::simpleReject() +{ + init(); + + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("simpleReject")) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + QFAIL("Unexpected then"); + QWASMDONE(); + }) + .addCatchFunction([](emscripten::val result) { + QVERIFY(result.isString()); + QCOMPARE("Evil error", result.as<std::string>()); + QWASMDONE(); + }); + + g_testSupport["reject"].call<void>("simpleReject", std::string("Evil error")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::multipleReject() +{ + static constexpr int promiseCount = 1000; + + auto onCatch = std::make_shared<BarrierCallback>(promiseCount, []() { + QWASMDONE(); + }); + + for (int i = 0; i < promiseCount; ++i) { + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + (QStringLiteral("test") + QString::number(i)).toStdString()) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + QFAIL("Unexpected then"); + }) + .addCatchFunction([=](emscripten::val error) { + Q_UNUSED(error); + (*onCatch)(); + }); + } + + for (int i = 0; i < promiseCount; ++i) + g_testSupport["reject"].call<void>(("test" + std::to_string(i)).c_str(), std::to_string(i)); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::throwInThen() +{ + init(); + QSKIP("Throw not supported"); + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("throwInThen")) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + EM_ASM({ + throw "Expected error"; + }); + }) + .addCatchFunction([](emscripten::val error) { + QCOMPARE("Expected error", error.as<std::string>()); + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("throwInThen", std::string("Evil error")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::bareFinally() +{ + init(); + + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("bareFinally")) + .addFinallyFunction([]() { + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("bareFinally", std::string("Evil error")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::finallyWithThen() +{ + init(); + + bool *thenCalled = new bool(false); + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("finallyWithThen")) + .addThenFunction([thenCalled] (emscripten::val result) { + Q_UNUSED(result); + *thenCalled = true; + }) + .addFinallyFunction([thenCalled]() { + QVERIFY(*thenCalled); + delete thenCalled; + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("finallyWithThen", std::string("Evil error")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::finallyWithThrow() +{ + init(); + + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("finallyWithThrow")) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + }) + .addFinallyFunction([]() { + QWASMDONE(); + }); + + g_testSupport["reject"].call<void>("finallyWithThrow", std::string("Evil error")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::finallyWithThrowInThen() +{ + init(); + QSKIP("Throw not supported"); + + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("finallyWithThrowInThen")) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + EM_ASM({ + throw "Expected error"; + }); + }) + .addCatchFunction([](emscripten::val result) { + QVERIFY(result.isString()); + QCOMPARE("Expected error", result.as<std::string>()); + }) + .addFinallyFunction([]() { + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("finallyWithThrowInThen", std::string("Evil error")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::nested() +{ + init(); + + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("outer")) + .addThenFunction([](emscripten::val result) { + QVERIFY(result.isString()); + QCOMPARE("Outer data", result.as<std::string>()); + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("inner")) + .addThenFunction([](emscripten::val innerResult) { + QVERIFY(innerResult.isString()); + QCOMPARE("Inner data", innerResult.as<std::string>()); + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + std::string("innermost")) + .addThenFunction([](emscripten::val innerResult) { + QVERIFY(innerResult.isString()); + QCOMPARE("Innermost data", innerResult.as<std::string>()); + QWASMDONE(); + }) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + QFAIL("Unexpected catch"); + }); + g_testSupport["resolve"].call<void>("innermost", std::string("Innermost data")); + }); + g_testSupport["resolve"].call<void>("inner", std::string("Inner data")); + }) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + QFAIL("Unexpected catch"); + }); + + g_testSupport["resolve"].call<void>("outer", std::string("Outer data")); + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::all() +{ + init(); + + { + static constexpr int promiseCount = 1000; + auto thenCalledOnce = std::make_shared<bool>(true); + + std::vector<qstdweb::Promise> promises; + promises.reserve(promiseCount); + + for (int i = 0; i < promiseCount; ++i) { + promises.push_back( + qstdweb::Promise( + g_testSupport, + "makeTestPromise", + emscripten::val(("all" + QString::number(i)).toStdString()))); + } + + qstdweb::Promise( + promises) + .addThenFunction([thenCalledOnce](emscripten::val result) { + QVERIFY(*thenCalledOnce); + *thenCalledOnce = false; + + QVERIFY(result.isArray()); + QCOMPARE(promiseCount, result["length"].as<int>()); + for (int i = 0; i < promiseCount; ++i) + QCOMPARE(QStringLiteral("Data %1").arg(i).toStdString(), result[i].as<std::string>()); + + QWASMDONE(); + }) + .addCatchFunction([](emscripten::val error) { + Q_UNUSED(error); + QFAIL("Unexpected catch"); + }); + + for (int i = promiseCount - 1; i >= 0; --i) + g_testSupport["resolve"].call<void>(("all" + std::to_string(i)).c_str(), "Data " + std::to_string(i)); + } + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::allWithThrow() +{ + init(); + + { + auto promise1 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise1")); + auto promise2 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise2")); + auto promise3 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise3")); + auto catchCalledOnce = std::make_shared<bool>(true); + + qstdweb::Promise( + {promise1, promise2, promise3}) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + QFAIL("Unexpected then"); + }) + .addCatchFunction([catchCalledOnce](emscripten::val result) { + QVERIFY(*catchCalledOnce); + *catchCalledOnce = false; + QVERIFY(result.isString()); + QCOMPARE("Error 2", result.as<std::string>()); + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("promise3", std::string("Data 3")); + g_testSupport["resolve"].call<void>("promise1", std::string("Data 1")); + g_testSupport["reject"].call<void>("promise2", std::string("Error 2")); + } + + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::allWithFinally() +{ + init(); + { + auto promise1 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise1")); + auto promise2 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise2")); + auto promise3 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise3")); + + auto finallyCalledOnce = std::make_shared<bool>(true); + + qstdweb::Promise( + {promise1, promise2, promise3}) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + }) + .addFinallyFunction([finallyCalledOnce]() { + QVERIFY(*finallyCalledOnce); + *finallyCalledOnce = false; + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("promise3", std::string("Data 3")); + g_testSupport["resolve"].call<void>("promise1", std::string("Data 1")); + g_testSupport["resolve"].call<void>("promise2", std::string("Data 2")); + } + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +void tst_QWasmPromise::allWithFinallyAndThrow() +{ + init(); + QSKIP("Throw not supported"); + { + auto promise1 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise1")); + auto promise2 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise2")); + auto promise3 = qstdweb::Promise(g_testSupport, "makeTestPromise", std::string("promise3")); + auto finallyCalledOnce = std::make_shared<bool>(true); + + qstdweb::Promise( + {promise1, promise2, promise3}) + .addThenFunction([](emscripten::val result) { + Q_UNUSED(result); + EM_ASM({ + throw "This breaks it all!!!"; + }); + }) + .addCatchFunction([](emscripten::val) { ; }) + .addFinallyFunction([finallyCalledOnce]() { + QVERIFY(*finallyCalledOnce); + *finallyCalledOnce = false; + QWASMDONE(); + }); + + g_testSupport["resolve"].call<void>("promise3", std::string("Data 3")); + g_testSupport["resolve"].call<void>("promise1", std::string("Data 1")); + g_testSupport["resolve"].call<void>("promise2", std::string("Data 2")); + } + + QVERIFY(QTest::qWaitFor([]() { return g_Done; })); + QVERIFY(QTest::qWaitFor([]() { return qstdweb::Promise::State::numInstances() == 0; })); +} + +QTEST_MAIN(tst_QWasmPromise) +#include "tst_qwasmpromise.moc" diff --git a/tests/manual/wasm/qstdweb/CMakeLists.txt b/tests/manual/wasm/qstdweb/CMakeLists.txt index 8904abc94b2..46a04a6cdb3 100644 --- a/tests/manual/wasm/qstdweb/CMakeLists.txt +++ b/tests/manual/wasm/qstdweb/CMakeLists.txt @@ -1,28 +1,5 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -qt_internal_add_manual_test(promise_auto - SOURCES - promise_main.cpp - ../qtwasmtestlib/qtwasmtestlib.cpp - LIBRARIES - Qt::Core - Qt::CorePrivate -) - -include_directories(../qtwasmtestlib/) - -add_custom_command( - TARGET promise_auto POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/promise_auto.html - ${CMAKE_CURRENT_BINARY_DIR}/promise_auto.html) - -add_custom_command( - TARGET promise_auto POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/../qtwasmtestlib/qtwasmtestlib.js - ${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js) - qt_internal_add_manual_test(files_auto SOURCES files_main.cpp diff --git a/tests/manual/wasm/qstdweb/promise_auto.html b/tests/manual/wasm/qstdweb/promise_auto.html deleted file mode 100644 index 94a8dbb88a2..00000000000 --- a/tests/manual/wasm/qstdweb/promise_auto.html +++ /dev/null @@ -1,10 +0,0 @@ -<!doctype html> -<script type="text/javascript" src="qtwasmtestlib.js"></script> -<script type="text/javascript" src="promise_auto.js"></script> -<script> - window.onload = () => { - runTestCase(promise_auto_entry, document.getElementById("log")); - }; -</script> -<p>Running promise auto test.</p> -<div id="log"></div> diff --git a/tests/manual/wasm/qstdweb/promise_main.cpp b/tests/manual/wasm/qstdweb/promise_main.cpp deleted file mode 100644 index 395e815cf95..00000000000 --- a/tests/manual/wasm/qstdweb/promise_main.cpp +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -#include <QtCore/QCoreApplication> -#include <QtCore/QEvent> -#include <QtCore/QMutex> -#include <QtCore/QObject> -#include <QtCore/QDebug> -#include <QtCore/private/qstdweb_p.h> - -#include <qtwasmtestlib.h> -#include <emscripten.h> - -using namespace emscripten; - -class WasmPromiseTest : public QObject -{ - Q_OBJECT - -public: - WasmPromiseTest() : m_window(val::global("window")), m_testSupport(val::object()) {} - - ~WasmPromiseTest() noexcept = default; - -private: - void init() { - m_testSupport = val::object(); - m_window.set("testSupport", m_testSupport); - - EM_ASM({ - testSupport.resolve = {}; - testSupport.reject = {}; - testSupport.promises = {}; - testSupport.waitConditionPromise = new Promise((resolve, reject) => { - testSupport.finishWaiting = resolve; - }); - - testSupport.makeTestPromise = (param) => { - testSupport.promises[param] = new Promise((resolve, reject) => { - testSupport.resolve[param] = resolve; - testSupport.reject[param] = reject; - }); - - return testSupport.promises[param]; - }; - }); - } - - val m_window; - val m_testSupport; - -private slots: - void simpleResolve(); - void multipleResolve(); - void simpleReject(); - void multipleReject(); - void throwInThen(); - void bareFinally(); - void finallyWithThen(); - void finallyWithThrow(); - void finallyWithThrowInThen(); - void nested(); - void all(); - void allWithThrow(); - void allWithFinally(); - void allWithFinallyAndThrow(); -}; - -class BarrierCallback { -public: - BarrierCallback(int number, std::function<void()> onDone) - : m_remaining(number), m_onDone(std::move(onDone)) {} - - void operator()() { - if (!--m_remaining) { - m_onDone(); - } - } - -private: - int m_remaining; - std::function<void()> m_onDone; -}; - -// Post event to the main thread and verify that it is processed. -void WasmPromiseTest::simpleResolve() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [](val result) { - QWASMVERIFY(result.isString()); - QWASMCOMPARE("Some lovely data", result.as<std::string>()); - - QWASMSUCCESS(); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - - QWASMFAIL("Unexpected catch"); - } - }, std::string("simpleResolve")); - - EM_ASM({ - testSupport.resolve["simpleResolve"]("Some lovely data"); - }); -} - -void WasmPromiseTest::multipleResolve() -{ - init(); - - static constexpr int promiseCount = 1000; - - auto onThen = std::make_shared<BarrierCallback>(promiseCount, []() { - QWASMSUCCESS(); - }); - - for (int i = 0; i < promiseCount; ++i) { - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [=](val result) { - QWASMVERIFY(result.isString()); - QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>()); - - (*onThen)(); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - QWASMFAIL("Unexpected catch"); - } - }, (QStringLiteral("test") + QString::number(i)).toStdString()); - } - - EM_ASM({ - for (let i = $0 - 1; i >= 0; --i) { - testSupport.resolve['test' + i](`${i}`); - } - }, promiseCount); -} - -void WasmPromiseTest::simpleReject() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [](val result) { - Q_UNUSED(result); - QWASMFAIL("Unexpected then"); - }, - .catchFunc = [](val result) { - QWASMVERIFY(result.isString()); - QWASMCOMPARE("Evil error", result.as<std::string>()); - QWASMSUCCESS(); - } - }, std::string("simpleReject")); - - EM_ASM({ - testSupport.reject["simpleReject"]("Evil error"); - }); -} - -void WasmPromiseTest::multipleReject() -{ - static constexpr int promiseCount = 1000; - - auto onCatch = std::make_shared<BarrierCallback>(promiseCount, []() { - QWASMSUCCESS(); - }); - - for (int i = 0; i < promiseCount; ++i) { - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [=](val result) { - QWASMVERIFY(result.isString()); - QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>()); - - (*onCatch)(); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - QWASMFAIL("Unexpected catch"); - } - }, (QStringLiteral("test") + QString::number(i)).toStdString()); - } - - EM_ASM({ - for (let i = $0 - 1; i >= 0; --i) { - testSupport.resolve['test' + i](`${i}`); - } - }, promiseCount); -} - -void WasmPromiseTest::throwInThen() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [](val result) { - Q_UNUSED(result); - EM_ASM({ - throw "Expected error"; - }); - }, - .catchFunc = [](val error) { - QWASMCOMPARE("Expected error", error.as<std::string>()); - QWASMSUCCESS(); - } - }, std::string("throwInThen")); - - EM_ASM({ - testSupport.resolve["throwInThen"](); - }); -} - -void WasmPromiseTest::bareFinally() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .finallyFunc = []() { - QWASMSUCCESS(); - } - }, std::string("bareFinally")); - - EM_ASM({ - testSupport.resolve["bareFinally"](); - }); -} - -void WasmPromiseTest::finallyWithThen() -{ - init(); - - bool *thenCalled = new bool(false); - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [thenCalled] (val result) { - Q_UNUSED(result); - *thenCalled = true; - }, - .finallyFunc = [thenCalled]() { - QWASMVERIFY(*thenCalled); - delete thenCalled; - QWASMSUCCESS(); - } - }, std::string("finallyWithThen")); - - EM_ASM({ - testSupport.resolve["finallyWithThen"](); - }); -} - -void WasmPromiseTest::finallyWithThrow() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .catchFunc = [](val error) { - Q_UNUSED(error); - }, - .finallyFunc = []() { - QWASMSUCCESS(); - } - }, std::string("finallyWithThrow")); - - EM_ASM({ - testSupport.reject["finallyWithThrow"](); - }); -} - -void WasmPromiseTest::finallyWithThrowInThen() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [](val result) { - Q_UNUSED(result); - EM_ASM({ - throw "Expected error"; - }); - }, - .catchFunc = [](val result) { - QWASMVERIFY(result.isString()); - QWASMCOMPARE("Expected error", result.as<std::string>()); - }, - .finallyFunc = []() { - QWASMSUCCESS(); - } - }, std::string("bareFinallyWithThen")); - - EM_ASM({ - testSupport.resolve["bareFinallyWithThen"](); - }); -} - -void WasmPromiseTest::nested() -{ - init(); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [this](val result) { - QWASMVERIFY(result.isString()); - QWASMCOMPARE("Outer data", result.as<std::string>()); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [this](val innerResult) { - QWASMVERIFY(innerResult.isString()); - QWASMCOMPARE("Inner data", innerResult.as<std::string>()); - - qstdweb::Promise::make(m_testSupport, "makeTestPromise", { - .thenFunc = [](val innerResult) { - QWASMVERIFY(innerResult.isString()); - QWASMCOMPARE("Innermost data", innerResult.as<std::string>()); - - QWASMSUCCESS(); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - QWASMFAIL("Unexpected catch"); - } - }, std::string("innermost")); - - EM_ASM({ - testSupport.resolve["innermost"]("Innermost data"); - }); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - QWASMFAIL("Unexpected catch"); - } - }, std::string("inner")); - - EM_ASM({ - testSupport.resolve["inner"]("Inner data"); - }); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - QWASMFAIL("Unexpected catch"); - } - }, std::string("outer")); - - EM_ASM({ - testSupport.resolve["outer"]("Outer data"); - }); -} - -void WasmPromiseTest::all() -{ - init(); - - static constexpr int promiseCount = 1000; - auto thenCalledOnce = std::shared_ptr<bool>(); - *thenCalledOnce = true; - - std::vector<val> promises; - promises.reserve(promiseCount); - - for (int i = 0; i < promiseCount; ++i) { - promises.push_back(m_testSupport.call<val>("makeTestPromise", val(("all" + QString::number(i)).toStdString()))); - } - - qstdweb::Promise::all(std::move(promises), { - .thenFunc = [=](val result) { - QWASMVERIFY(*thenCalledOnce); - *thenCalledOnce = false; - - QWASMVERIFY(result.isArray()); - QWASMCOMPARE(promiseCount, result["length"].as<int>()); - for (int i = 0; i < promiseCount; ++i) { - QWASMCOMPARE(QStringLiteral("Data %1").arg(i).toStdString(), result[i].as<std::string>()); - } - - QWASMSUCCESS(); - }, - .catchFunc = [](val error) { - Q_UNUSED(error); - QWASMFAIL("Unexpected catch"); - } - }); - - EM_ASM({ - console.log('Resolving'); - for (let i = $0 - 1; i >= 0; --i) { - testSupport.resolve['all' + i](`Data ${i}`); - } - }, promiseCount); -} - -void WasmPromiseTest::allWithThrow() -{ - init(); - - val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1")); - val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2")); - val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3")); - - auto catchCalledOnce = std::shared_ptr<bool>(); - *catchCalledOnce = true; - - qstdweb::Promise::all({promise1, promise2, promise3}, { - .thenFunc = [](val result) { - Q_UNUSED(result); - QWASMFAIL("Unexpected then"); - }, - .catchFunc = [catchCalledOnce](val result) { - QWASMVERIFY(*catchCalledOnce); - *catchCalledOnce = false; - QWASMVERIFY(result.isString()); - QWASMCOMPARE("Error 2", result.as<std::string>()); - QWASMSUCCESS(); - } - }); - - EM_ASM({ - testSupport.resolve["promise3"]("Data 3"); - testSupport.resolve["promise1"]("Data 1"); - testSupport.reject["promise2"]("Error 2"); - }); -} - -void WasmPromiseTest::allWithFinally() -{ - init(); - - val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1")); - val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2")); - val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3")); - - auto finallyCalledOnce = std::shared_ptr<bool>(); - *finallyCalledOnce = true; - - qstdweb::Promise::all({promise1, promise2, promise3}, { - .thenFunc = [](val result) { - Q_UNUSED(result); - }, - .finallyFunc = [finallyCalledOnce]() { - QWASMVERIFY(*finallyCalledOnce); - *finallyCalledOnce = false; - QWASMSUCCESS(); - } - }); - - EM_ASM({ - testSupport.resolve["promise3"]("Data 3"); - testSupport.resolve["promise1"]("Data 1"); - testSupport.resolve["promise2"]("Data 2"); - }); -} - -void WasmPromiseTest::allWithFinallyAndThrow() -{ - init(); - - val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1")); - val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2")); - val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3")); - - auto finallyCalledOnce = std::shared_ptr<bool>(); - *finallyCalledOnce = true; - - qstdweb::Promise::all({promise1, promise2, promise3}, { - .thenFunc = [](val result) { - Q_UNUSED(result); - EM_ASM({ - throw "This breaks it all!!!"; - }); - }, - .finallyFunc = [finallyCalledOnce]() { - QWASMVERIFY(*finallyCalledOnce); - *finallyCalledOnce = false; - QWASMSUCCESS(); - } - }); - - EM_ASM({ - testSupport.resolve["promise3"]("Data 3"); - testSupport.resolve["promise1"]("Data 1"); - testSupport.resolve["promise2"]("Data 2"); - }); -} - -int main(int argc, char **argv) -{ - auto testObject = std::make_shared<WasmPromiseTest>(); - QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject); - return 0; -} - -#include "promise_main.moc" |
