Revision control

Copy as Markdown

# Copyright (C) 2026 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#
# Internal HarmonyOS deployment functions for automatic HAP target generation
#
# This file implements the finalizer pattern for HarmonyOS, similar to Android.
# When a Qt executable is created, these functions are called automatically to:
# 1. Set smart defaults for deployment properties
# 2. Generate deployment configuration
# 3. Create the ${target}_make_hap build target
#
# Promote QT_HARMONYOS_HDC / QT_HARMONYOS_HVIGOR from the environment to the
# matching cache var when the latter is not already set on the command line.
if(NOT DEFINED HARMONYOS_HDC
AND DEFINED ENV{QT_HARMONYOS_HDC}
AND NOT "$ENV{QT_HARMONYOS_HDC}" STREQUAL "")
set(HARMONYOS_HDC "$ENV{QT_HARMONYOS_HDC}")
endif()
if(NOT DEFINED HARMONYOS_HVIGOR
AND DEFINED ENV{QT_HARMONYOS_HVIGOR}
AND NOT "$ENV{QT_HARMONYOS_HVIGOR}" STREQUAL "")
set(HARMONYOS_HVIGOR "$ENV{QT_HARMONYOS_HVIGOR}")
endif()
# Detect the HarmonyOS SDK and NDK root directories from environment, CMake variables,
# the toolchain file or the compiler path, and set the output variables in the caller's
# scope. Either output may end up empty, most notably in a native on-device build, where
# there is no SDK and no NDK.
function(_qt_internal_harmonyos_get_sdk_ndk_paths sdk_root_var ndk_root_var)
set(sdk_root "")
set(ndk_root "")
if(DEFINED OHOS_SDK_ROOT)
set(sdk_root "${OHOS_SDK_ROOT}")
elseif(DEFINED ENV{OHOS_SDK_ROOT})
set(sdk_root "$ENV{OHOS_SDK_ROOT}")
elseif(CMAKE_SYSTEM_NAME STREQUAL "OHOS" AND CMAKE_CXX_COMPILER)
# Derive from compiler path: .../sdk/default/openharmony/native/llvm/bin/clang++
get_filename_component(_compiler_dir "${CMAKE_CXX_COMPILER}" DIRECTORY)
get_filename_component(_llvm_dir "${_compiler_dir}" DIRECTORY)
get_filename_component(_native_dir "${_llvm_dir}" DIRECTORY)
get_filename_component(sdk_root "${_native_dir}" DIRECTORY)
endif()
if(DEFINED OHOS_SDK_NATIVE)
# Set by the SDK's ohos.toolchain.cmake.
set(ndk_root "${OHOS_SDK_NATIVE}")
elseif(sdk_root AND IS_DIRECTORY "${sdk_root}/native")
set(ndk_root "${sdk_root}/native")
elseif(CMAKE_SYSTEM_NAME STREQUAL "OHOS" AND CMAKE_CXX_COMPILER)
# Derive from compiler path: .../native/llvm/bin/clang++
get_filename_component(_compiler_dir "${CMAKE_CXX_COMPILER}" DIRECTORY)
get_filename_component(_llvm_dir "${_compiler_dir}" DIRECTORY)
get_filename_component(ndk_root "${_llvm_dir}" DIRECTORY)
endif()
set(${sdk_root_var} "${sdk_root}" PARENT_SCOPE)
set(${ndk_root_var} "${ndk_root}" PARENT_SCOPE)
endfunction()
# Resolve the Qt installation root and subdirectory variables.
# QT6_INSTALL_PREFIX is set by QtInstallPaths.cmake (via find_package), but not during
# an internal qtbase build; in that case fall back to QtBase_BINARY_DIR.
function(_qt_internal_harmonyos_get_qt_install_dirs
install_root_var libs_dir_var plugins_dir_var qml_dir_var data_dir_var)
if(QT6_INSTALL_PREFIX)
set(${install_root_var} "${QT6_INSTALL_PREFIX}" PARENT_SCOPE)
set(${libs_dir_var} "${QT6_INSTALL_LIBS}" PARENT_SCOPE)
set(${plugins_dir_var} "${QT6_INSTALL_PLUGINS}" PARENT_SCOPE)
set(${qml_dir_var} "${QT6_INSTALL_QML}" PARENT_SCOPE)
set(${data_dir_var} "${QT6_INSTALL_DATA}" PARENT_SCOPE)
else()
set(${install_root_var} "${QtBase_BINARY_DIR}" PARENT_SCOPE)
set(${libs_dir_var} "${INSTALL_LIBDIR}" PARENT_SCOPE)
set(${plugins_dir_var} "${INSTALL_PLUGINSDIR}" PARENT_SCOPE)
set(${qml_dir_var} "${INSTALL_QMLDIR}" PARENT_SCOPE)
set(${data_dir_var} "${INSTALL_DATADIR}" PARENT_SCOPE)
endif()
endfunction()
# Collects the project's shared and module libraries once (cached globally) and adds them to the
# target's project-libraries list, except for the target itself.
# Runs deferred at the end of the top-level directory, when all project targets exist.
function(_qt_internal_harmonyos_add_project_libraries target)
get_property(collected GLOBAL PROPERTY _qt_harmonyos_project_library_candidates SET)
if(collected)
get_property(targets GLOBAL PROPERTY _qt_harmonyos_project_library_candidates)
else()
_qt_internal_collect_buildsystem_targets(targets
"${CMAKE_SOURCE_DIR}" INCLUDE SHARED_LIBRARY MODULE_LIBRARY)
set_property(GLOBAL PROPERTY _qt_harmonyos_project_library_candidates "${targets}")
endif()
list(REMOVE_ITEM targets ${target})
list(TRANSFORM targets REPLACE "(.+)" "$<TARGET_FILE:\\1>")
get_property(libs TARGET ${target} PROPERTY _qt_harmonyos_project_libraries)
list(APPEND libs ${targets})
list(REMOVE_DUPLICATES libs)
set_target_properties(${target} PROPERTIES _qt_harmonyos_project_libraries "${libs}")
endfunction()
# Collect directly-linked SHARED_LIBRARY CMake targets into a list of their
# output file paths (using generator expressions so paths are resolved at
# generation time). Also respects an explicit QT_HARMONYOS_EXTRA_LIBS property,
# whose entries may be CMake target names (resolved to the target's output file)
# or file paths / generator expressions.
# Analogous to Qt Android's QT_ANDROID_EXTRA_LIBS mechanism.
function(_qt_internal_harmonyos_collect_extra_libs target output_var)
set(result "")
get_target_property(link_libs "${target}" LINK_LIBRARIES)
if(link_libs AND NOT link_libs STREQUAL "link_libs-NOTFOUND")
foreach(lib IN LISTS link_libs)
# Skip generator expressions and linker flags — they can't be
# tested with if(TARGET ...) and don't refer to CMake targets.
if(lib MATCHES "^\\$<" OR lib MATCHES "^-")
continue()
endif()
if(NOT TARGET "${lib}")
continue()
endif()
get_target_property(lib_type "${lib}" TYPE)
if(lib_type STREQUAL "SHARED_LIBRARY")
list(APPEND result "$<TARGET_FILE:${lib}>")
endif()
endforeach()
endif()
# Honour explicit QT_HARMONYOS_EXTRA_LIBS target property. Each entry is
# either the name of a CMake target (resolved to the target's output file)
# or a file path / generator expression that evaluates to such a path.
set(extra_libs_props QT_HARMONYOS_EXTRA_LIBS)
# TODO Remove QT_OHOS_EXTRA_LIBS after a grace period but latest after 6.12.0.
list(APPEND extra_libs_props QT_OHOS_EXTRA_LIBS)
foreach(prop IN LISTS extra_libs_props)
get_target_property(extra_libs "${target}" ${prop})
if(extra_libs AND NOT extra_libs STREQUAL "extra_libs-NOTFOUND")
foreach(item IN LISTS extra_libs)
if(TARGET "${item}")
list(APPEND result "$<TARGET_FILE:${item}>")
else()
list(APPEND result "${item}")
endif()
endforeach()
endif()
endforeach()
list(REMOVE_DUPLICATES result)
set(${output_var} "${result}" PARENT_SCOPE)
endfunction()
# Append a JSON `"key": "value"` entry (with leading comma + newline) to
# `${out_var}` for the target's property, evaluated at generate-time via
# GENEX_EVAL so transitively-set values reach the JSON. Emits nothing if the
# property is unset or empty.
function(_qt_internal_harmonyos_add_deployment_property out_var json_key target property)
set(property_genex "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},${property}>>")
string(APPEND ${out_var}
"$<$<BOOL:${property_genex}>:"
",\n \"${json_key}\": \"${property_genex}\""
">"
)
set(${out_var} "${${out_var}}" PARENT_SCOPE)
endfunction()
# Integer variant: emits the value without surrounding quotes. An integer 0
# is treated as unset (matches the historical semantics of the metadata loop).
function(_qt_internal_harmonyos_add_deployment_int_property out_var json_key target property)
set(property_genex "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},${property}>>")
string(APPEND ${out_var}
"$<$<BOOL:${property_genex}>:"
",\n \"${json_key}\": ${property_genex}"
">"
)
set(${out_var} "${${out_var}}" PARENT_SCOPE)
endfunction()
# Append a JSON `"key": [ "v1", "v2" ]` entry. The semicolon-delimited list
# value is joined with $<COMMA> at generate-time. Emits nothing if the
# property is unset or empty.
function(_qt_internal_harmonyos_add_deployment_list_property out_var json_key target property)
set(property_genex "$<GENEX_EVAL:$<TARGET_PROPERTY:${target},${property}>>")
string(APPEND ${out_var}
"$<$<BOOL:${property_genex}>:"
",\n \"${json_key}\": [\"$<JOIN:${property_genex},\"$<COMMA>\">\"]"
">"
)
set(${out_var} "${${out_var}}" PARENT_SCOPE)
endfunction()
# Stash CMake-format copies of path-bearing target properties on internal
# `_qt_harmonyos_*` properties so the emission helpers can read them
# via genex without further escaping. Pre-release contract: users supply
# CMake-format paths (forward slashes), mirroring the Qt 6.6+ NEW behavior of
# the Android equivalent (`_qt_internal_android_format_deployment_paths`).
#
# qml-root-path concatenates two sources with a `;` glue when both are
# non-empty:
# * QT_QML_ROOT_PATH -- user-set roots
# * _qt_internal_qml_root_path -- roots auto-collected by
# _qt_internal_collect_qml_root_paths when .qml files are added as
# resources (populated for OHOS via the corresponding hook in
# Qt6CoreMacros.cmake).
function(_qt_internal_harmonyos_format_deployment_paths target)
string(JOIN "" qml_root_path_genex
"$<GENEX_EVAL:$<TARGET_PROPERTY:${target},QT_QML_ROOT_PATH>>"
"$<"
"$<AND:"
"$<BOOL:$<GENEX_EVAL:$<TARGET_PROPERTY:${target},QT_QML_ROOT_PATH>>>,"
"$<BOOL:$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_internal_qml_root_path>>>"
">:;"
">"
"$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_internal_qml_root_path>>"
)
set_target_properties(${target} PROPERTIES
_qt_harmonyos_qml_import_paths
"$<GENEX_EVAL:$<TARGET_PROPERTY:${target},QT_QML_IMPORT_PATH>>"
_qt_harmonyos_qml_root_paths
"${qml_root_path_genex}"
_qt_harmonyos_package_source_dir
"$<GENEX_EVAL:$<TARGET_PROPERTY:${target},QT_HARMONYOS_PACKAGE_SOURCE_DIR>>"
)
# QT_HARMONYOS_EXTRA_PLUGINS items are either CMake plugin target names or
# absolute file paths; target names are resolved to $<TARGET_FILE:> here so
# the emission helper sees a flat list of paths/genexes.
get_target_property(_extra_plugins ${target} QT_HARMONYOS_EXTRA_PLUGINS)
set(_resolved_extra_plugins "")
if(_extra_plugins AND NOT _extra_plugins STREQUAL "_extra_plugins-NOTFOUND")
foreach(item IN LISTS _extra_plugins)
if(TARGET ${item})
list(APPEND _resolved_extra_plugins "$<TARGET_FILE:${item}>")
else()
list(APPEND _resolved_extra_plugins "${item}")
endif()
endforeach()
endif()
set_target_properties(${target} PROPERTIES
_qt_harmonyos_extra_plugins "${_resolved_extra_plugins}"
)
endfunction()
# Sets default values for HarmonyOS deployment properties if not already set
function(_qt_internal_set_harmonyos_deployment_defaults target)
# Set default bundle name: org.qtproject.example.${target_sanitized}
get_target_property(bundle_name ${target} QT_HARMONYOS_APP_BUNDLE_NAME)
if(NOT bundle_name OR bundle_name STREQUAL "bundle_name-NOTFOUND")
# Sanitize target name to be a valid identifier
string(MAKE_C_IDENTIFIER "${target}" target_sanitized)
set_target_properties(${target} PROPERTIES
QT_HARMONYOS_APP_BUNDLE_NAME "org.qtproject.example.${target_sanitized}"
)
endif()
# Set default app name: ${target}
get_target_property(app_name ${target} QT_HARMONYOS_APP_NAME)
if(NOT app_name OR app_name STREQUAL "app_name-NOTFOUND")
set_target_properties(${target} PROPERTIES
QT_HARMONYOS_APP_NAME "${target}"
)
endif()
# Set default target architecture to the architecture used with the toolchain file.
get_target_property(target_archs ${target} QT_HARMONYOS_TARGET_ARCHS)
if(NOT target_archs OR target_archs STREQUAL "target_archs-NOTFOUND")
set_target_properties(${target} PROPERTIES
QT_HARMONYOS_TARGET_ARCHS "${OHOS_ARCH}"
)
endif()
endfunction()
# Generates the HarmonyOS deployment settings JSON file
function(_qt_internal_harmonyos_generate_deployment_settings target)
# Query bundle name from target property (now guaranteed to exist)
get_target_property(bundle_name ${target} QT_HARMONYOS_APP_BUNDLE_NAME)
# Query app name from target property
get_target_property(app_name ${target} QT_HARMONYOS_APP_NAME)
if(NOT app_name OR app_name STREQUAL "app_name-NOTFOUND")
set(app_name "${target}")
endif()
# Query target architectures from property
get_target_property(target_archs ${target} QT_HARMONYOS_TARGET_ARCHS)
if(NOT target_archs OR target_archs STREQUAL "target_archs-NOTFOUND")
set(target_archs "${OHOS_ARCH}")
endif()
# Check for multi-arch support (not yet implemented)
if(target_archs MATCHES ";")
list(LENGTH target_archs arch_count)
if(arch_count GREATER 1)
message(WARNING "Multiple architectures specified for ${target}: ${target_archs}. "
"Multi-architecture HAP generation is not yet supported. "
"Only the first architecture will be used.")
endif()
endif()
# Detect SDK and NDK paths
_qt_internal_harmonyos_get_sdk_ndk_paths(sdk_root ndk_root)
# Stash JSON-escaped copies of string-typed user-supplied properties on
# internal `_qt_harmonyos_*` properties. The emission helpers below
# substitute property values verbatim into the JSON; an unescaped `"` or
# `\` (e.g. in an app LABEL containing quoted text, or a Windows-style
# ICON path with backslashes) would otherwise corrupt the JSON.
foreach(prop_kv IN ITEMS
"QT_HARMONYOS_APP_VENDOR;_qt_harmonyos_app_vendor;string"
"QT_HARMONYOS_APP_VERSION_NAME;_qt_harmonyos_app_version_name;string"
"QT_HARMONYOS_APP_LABEL;_qt_harmonyos_app_label;string"
"QT_HARMONYOS_APP_ICON;_qt_harmonyos_app_icon;path"
"QT_HARMONYOS_MODULE_DESCRIPTION;_qt_harmonyos_module_description;string"
"QT_HARMONYOS_ABILITY_ORIENTATION;_qt_harmonyos_ability_orientation;string"
"QT_HARMONYOS_COMPATIBLE_SDK_VERSION;_qt_harmonyos_compatible_sdk_version;string"
"QT_HARMONYOS_TARGET_SDK_VERSION;_qt_harmonyos_target_sdk_version;string"
"QT_HARMONYOS_COMPILE_SDK_VERSION;_qt_harmonyos_compile_sdk_version;string"
)
list(GET prop_kv 0 _prop)
list(GET prop_kv 1 _mirror)
list(GET prop_kv 2 _type)
get_target_property(_value ${target} ${_prop})
if(_value)
# A `$media:<name>` value is a resource reference, not a path. It must
# not go through file(TO_CMAKE_PATH), which turns the `:` into a list
# separator on non-Windows hosts.
if(_type STREQUAL "path" AND NOT _value MATCHES "^\\$media:")
file(TO_CMAKE_PATH "${_value}" _value)
get_target_property(_target_source_dir ${target} SOURCE_DIR)
cmake_path(ABSOLUTE_PATH _value
BASE_DIRECTORY "${_target_source_dir}"
NORMALIZE
)
endif()
_qt_internal_json_escape_content("${_value}" _value)
set_target_properties(${target} PROPERTIES ${_mirror} "${_value}")
endif()
endforeach()
# Cache path-bearing properties on internal `_qt_harmonyos_*`
# properties for the genex-driven emission helpers below.
_qt_internal_harmonyos_format_deployment_paths(${target})
# Get binary path (will be a generator expression for multi-config)
set(BINARY_PATH "$<TARGET_FILE:${target}>")
# Convert target architectures to JSON array
if(target_archs MATCHES ";")
# List format
set(TARGET_ARCHS_JSON "")
foreach(arch ${target_archs})
if(TARGET_ARCHS_JSON)
string(APPEND TARGET_ARCHS_JSON ", ")
endif()
string(APPEND TARGET_ARCHS_JSON "\"${arch}\"")
endforeach()
else()
# Single value or semicolon-separated string
string(REPLACE ";" "\", \"" TARGET_ARCHS_JSON "${target_archs}")
set(TARGET_ARCHS_JSON "\"${TARGET_ARCHS_JSON}\"")
endif()
# Generate JSON config file
set(CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/${target}-harmony-deployment-settings.json")
set(JSON_CONTENT "{\n")
string(APPEND JSON_CONTENT " \"application-binary\": \"${BINARY_PATH}\",\n")
string(APPEND JSON_CONTENT " \"harmonyos-app-name\": \"${app_name}\",\n")
string(APPEND JSON_CONTENT " \"harmonyos-app-bundle-name\": \"${bundle_name}\",\n")
string(APPEND JSON_CONTENT " \"harmonyos-target-arch\": [${TARGET_ARCHS_JSON}]")
# App-level metadata set via the QT_HARMONYOS_APP_* target properties.
# Properties are read via $<TARGET_PROPERTY:> genex so transitively-set
# values (e.g. from linked targets) reach the JSON; absent entries leave
# the template default in place.
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-app-vendor" ${target} _qt_harmonyos_app_vendor)
_qt_internal_harmonyos_add_deployment_int_property(JSON_CONTENT
"harmonyos-app-version-code" ${target} QT_HARMONYOS_APP_VERSION_CODE)
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-app-version-name" ${target} _qt_harmonyos_app_version_name)
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-app-label" ${target} _qt_harmonyos_app_label)
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-app-icon" ${target} _qt_harmonyos_app_icon)
# SDK versions land in entry/build-profile.json5 via harmonydeployqt
# template substitution.
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-compatible-sdk-version" ${target} _qt_harmonyos_compatible_sdk_version)
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-target-sdk-version" ${target} _qt_harmonyos_target_sdk_version)
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-compile-sdk-version" ${target} _qt_harmonyos_compile_sdk_version)
# Module-level metadata.
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-module-description" ${target} _qt_harmonyos_module_description)
_qt_internal_harmonyos_add_deployment_list_property(JSON_CONTENT
"harmonyos-module-device-types" ${target} QT_HARMONYOS_MODULE_DEVICE_TYPES)
_qt_internal_harmonyos_add_deployment_property(JSON_CONTENT
"harmonyos-ability-orientation" ${target} _qt_harmonyos_ability_orientation)
if(sdk_root)
file(TO_CMAKE_PATH "${sdk_root}" sdk_root)
string(APPEND JSON_CONTENT ",\n \"sdk-root\": \"${sdk_root}\"")
endif()
if(ndk_root)
file(TO_CMAKE_PATH "${ndk_root}" ndk_root)
string(APPEND JSON_CONTENT ",\n \"ndk-root\": \"${ndk_root}\"")
endif()
# Add Qt installation directories (following androiddeployqt pattern)
# For cross-compilation: target dirs from current build, host tools from QT_HOST_PATH
_qt_internal_harmonyos_get_qt_install_dirs(
_qt_install_root _qt_install_libs_dir _qt_install_plugins_dir _qt_install_qml_dir
_qt_install_data_dir)
string(APPEND JSON_CONTENT
",\n \"qtLibsDirectory\": \"${_qt_install_root}/${_qt_install_libs_dir}\"")
string(APPEND JSON_CONTENT
",\n \"qtPluginsDirectory\": \"${_qt_install_root}/${_qt_install_plugins_dir}\"")
string(APPEND JSON_CONTENT
",\n \"qtQmlDirectory\": \"${_qt_install_root}/${_qt_install_qml_dir}\"")
# Host Qt tools directory (macOS build for qmlimportscanner, etc.)
if(QT_HOST_PATH)
file(TO_CMAKE_PATH "${QT_HOST_PATH}" QT_HOST_PATH)
string(APPEND JSON_CONTENT
",\n \"qtLibExecsDirectory\": \"${QT_HOST_PATH}/${QT6_HOST_INFO_LIBEXECDIR}\"")
string(APPEND JSON_CONTENT ",\n \"qtHostDirectory\": \"${QT_HOST_PATH}\"")
endif()
# Package source directory. User-set QT_HARMONYOS_PACKAGE_SOURCE_DIR wins
# over the bundled template (templates are only built for OHOS, so they
# live under the target Qt prefix, not QT_HOST_PATH). $<IF:> picks the
# right value at generate time; if both are empty (Qt install lacks the
# template AND the user didn't set the property), harmonydeployqt's own
# template search kicks in on the empty value.
set(_template_default "")
set(_template_dir
"${_qt_install_root}/${_qt_install_data_dir}/src/harmonyos/templates")
if(EXISTS "${_template_dir}")
file(REAL_PATH "${_template_dir}" _template_default)
endif()
set(_user_pkg_src_genex
"$<GENEX_EVAL:$<TARGET_PROPERTY:${target},_qt_harmonyos_package_source_dir>>")
string(APPEND JSON_CONTENT
",\n \"harmonyos-package-source-directory\": "
"\"$<IF:$<BOOL:${_user_pkg_src_genex}>,${_user_pkg_src_genex},${_template_default}>\""
)
# Extra library search directories for third-party deps (e.g. HARMONYOS_DEPS_ROOT/lib for ICU).
# CMAKE_FIND_ROOT_PATH has the Qt install prefix prepended by qt.toolchain.cmake; skip it so
# we only emit paths that point to user-supplied dependency prefixes.
set(_extra_root_paths "${CMAKE_FIND_ROOT_PATH}")
list(REMOVE_DUPLICATES _extra_root_paths)
if(QT6_INSTALL_PREFIX)
list(REMOVE_ITEM _extra_root_paths "${QT6_INSTALL_PREFIX}")
endif()
set(_extra_libs_dirs_json "")
foreach(_root_path IN LISTS _extra_root_paths)
set(_lib_dir "${_root_path}/lib")
if(EXISTS "${_lib_dir}")
file(TO_CMAKE_PATH "${_lib_dir}" _lib_dir)
if(_extra_libs_dirs_json)
string(APPEND _extra_libs_dirs_json ", ")
endif()
string(APPEND _extra_libs_dirs_json "\"${_lib_dir}\"")
endif()
endforeach()
if(_extra_libs_dirs_json)
string(APPEND JSON_CONTENT ",\n \"extra-libs-dirs\": [${_extra_libs_dirs_json}]")
endif()
# QML root + import paths. Both emitted as JSON arrays so multiple roots /
# import paths can be forwarded to qmlimportscanner.
_qt_internal_harmonyos_add_deployment_list_property(JSON_CONTENT
"qml-root-path" ${target} _qt_harmonyos_qml_root_paths)
_qt_internal_harmonyos_add_deployment_list_property(JSON_CONTENT
"qml-import-paths" ${target} _qt_harmonyos_qml_import_paths)
# Collect permissions: the executable's own first (so its entries win
# for duplicate names), then transitive permissions contributed by Qt
# modules (and any other linked targets) via _qt_harmonyos_permissions.
set(harmonyos_permissions "")
get_target_property(user_permissions ${target} _qt_harmonyos_permissions)
if(user_permissions AND NOT user_permissions STREQUAL "user_permissions-NOTFOUND")
list(APPEND harmonyos_permissions ${user_permissions})
endif()
__qt_internal_collect_all_target_dependencies(${target} dep_targets)
foreach(dep IN LISTS dep_targets)
get_target_property(dep_perms ${dep} _qt_harmonyos_permissions)
if(dep_perms AND NOT dep_perms STREQUAL "dep_perms-NOTFOUND")
list(APPEND harmonyos_permissions ${dep_perms})
endif()
endforeach()
if(harmonyos_permissions)
# De-duplicate by the JSON "name" field, keeping the first occurrence.
set(seen_names "")
set(unique_permissions "")
foreach(entry IN LISTS harmonyos_permissions)
string(JSON name GET "${entry}" name)
if(NOT name IN_LIST seen_names)
list(APPEND seen_names "${name}")
list(APPEND unique_permissions "${entry}")
endif()
endforeach()
list(JOIN unique_permissions ",\n " harmonyos_permissions_joined)
string(APPEND JSON_CONTENT
",\n \"permissions\": [\n ${harmonyos_permissions_joined}\n ]")
endif()
# Directly-linked SHARED_LIBRARY targets (e.g. test helper libs like qmetatype_lib1) are
# not Qt libraries and would otherwise be omitted from harmonydeployqt's ELF dependency
# scan, which only tracks libQt6* libs.
_qt_internal_harmonyos_collect_extra_libs(${target} extra_libs)
set_target_properties(${target} PROPERTIES _qt_harmonyos_project_libraries "${extra_libs}")
cmake_language(EVAL CODE
"cmake_language(DEFER DIRECTORY [[${CMAKE_SOURCE_DIR}]]
CALL _qt_internal_harmonyos_add_project_libraries [[${target}]])"
)
_qt_internal_harmonyos_add_deployment_list_property(JSON_CONTENT
"project-libraries" ${target} _qt_harmonyos_project_libraries)
# User-supplied extra plugin paths/targets (resolved to $<TARGET_FILE:>
# in _qt_internal_harmonyos_format_deployment_paths).
_qt_internal_harmonyos_add_deployment_list_property(JSON_CONTENT
"harmonyos-extra-plugins" ${target} _qt_harmonyos_extra_plugins)
string(APPEND JSON_CONTENT "\n}\n")
# Write config file at generation time (will contain generator expressions)
file(GENERATE OUTPUT "${CONFIG_FILE}" CONTENT "${JSON_CONTENT}")
# Store config file path on target for use in HAP target creation
set_target_properties(${target} PROPERTIES
QT_HARMONYOS_DEPLOYMENT_SETTINGS_FILE "${CONFIG_FILE}"
)
endfunction()
# Creates the ${target}_make_hap custom target
function(_qt_internal_harmonyos_add_hap_target target)
# Check if target already created (avoid duplicates)
get_target_property(hap_target_created ${target} _qt_harmonyos_hap_target_created)
if(hap_target_created)
return()
endif()
set_target_properties(${target} PROPERTIES _qt_harmonyos_hap_target_created TRUE)
# Get config file path
get_target_property(CONFIG_FILE ${target} QT_HARMONYOS_DEPLOYMENT_SETTINGS_FILE)
if(NOT CONFIG_FILE)
message(WARNING "Cannot create HAP target for ${target}: deployment settings not generated")
return()
endif()
# Set default output directory
set(OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/harmonyos-build")
# Check if the generator supports DEPFILE
_qt_internal_check_depfile_support(has_depfile_support)
# Define HAP output file and depfile paths
set(HAP_OUTPUT_FILE "${OUTPUT_DIR}/entry/build/default/outputs/default/${target}.hap")
set(DEP_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${target}_hap.d")
# Find harmonydeployqt tool
if(QT_HOST_PATH)
set(HARMONYDEPLOYQT_EXECUTABLE "${QT_HOST_PATH}/${QT6_HOST_INFO_BINDIR}/harmonydeployqt")
else()
set(HARMONYDEPLOYQT_EXECUTABLE
"${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${QT6_HOST_INFO_BINDIR}/harmonydeployqt")
endif()
# hvigor tool path override. HARMONYOS_HVIGOR is an internal cache variable,
# also populated from the QT_HARMONYOS_HVIGOR environment variable above. The
# hvigorw wrapper is part of the build toolchain, not per-target metadata.
# When unset, harmonydeployqt auto-detects the hvigorw script inside the
# generated package.
if(HARMONYOS_HVIGOR)
set(_hvigor_args --hvigor "${HARMONYOS_HVIGOR}")
else()
set(_hvigor_args "")
endif()
# Create deployment target with DEPFILE support when available
if(has_depfile_support)
# Use add_custom_command with DEPFILE for dependency tracking
cmake_policy(PUSH)
if(POLICY CMP0116)
cmake_policy(SET CMP0116 NEW)
set(relative_to_dir ${CMAKE_CURRENT_BINARY_DIR})
else()
set(relative_to_dir ${CMAKE_BINARY_DIR})
endif()
add_custom_command(OUTPUT "${HAP_OUTPUT_FILE}"
COMMAND ${HARMONYDEPLOYQT_EXECUTABLE}
--input "${CONFIG_FILE}"
--output "${OUTPUT_DIR}"
${_hvigor_args}
--depfile "${DEP_FILE_PATH}"
--depfile-base "${relative_to_dir}"
DEPENDS ${target} "${CONFIG_FILE}"
DEPFILE "${DEP_FILE_PATH}"
COMMENT "Generating HarmonyOS HAP for ${target}"
VERBATIM
)
cmake_policy(POP)
add_custom_target(${target}_make_hap DEPENDS "${HAP_OUTPUT_FILE}")
else()
# Fallback for generators/CMake versions without DEPFILE support
# (see _qt_internal_check_depfile_support for the exact conditions)
add_custom_target(${target}_make_hap
COMMAND ${HARMONYDEPLOYQT_EXECUTABLE}
--input "${CONFIG_FILE}"
--output "${OUTPUT_DIR}"
${_hvigor_args}
DEPENDS ${target}
COMMENT "Generating HarmonyOS HAP for ${target}"
VERBATIM
)
endif()
endfunction()
# Main finalizer function called automatically by Qt's finalization system
# This is the entry point that gets registered with INTERFACE_QT_EXECUTABLE_FINALIZERS
function(_qt_internal_harmonyos_executable_finalizer target)
# Prevent duplicate calls
get_target_property(already_called ${target} _qt_harmonyos_executable_finalizer_called)
if(already_called)
return()
endif()
set_target_properties(${target} PROPERTIES _qt_harmonyos_executable_finalizer_called TRUE)
# Check target type - process executables and module libraries
# (OHOS uses MODULE_LIBRARY for apps)
get_target_property(target_type ${target} TYPE)
if(NOT target_type STREQUAL "EXECUTABLE" AND NOT target_type STREQUAL "MODULE_LIBRARY")
return()
endif()
# Set defaults, generate settings, create HAP target
_qt_internal_set_harmonyos_deployment_defaults(${target})
_qt_internal_harmonyos_generate_deployment_settings(${target})
_qt_internal_harmonyos_add_hap_target(${target})
endfunction()
# Helper to register the finalizer with a target
# This adds the finalizer function to INTERFACE_QT_EXECUTABLE_FINALIZERS property
function(_qt_internal_add_harmonyos_executable_finalizer target)
set_property(TARGET ${target} APPEND PROPERTY
INTERFACE_QT_EXECUTABLE_FINALIZERS
_qt_internal_harmonyos_executable_finalizer
)
endfunction()
function(qt6_add_harmonyos_permission target)
_qt_internal_add_harmonyos_permission(${ARGV})
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_add_harmonyos_permission target)
qt6_add_harmonyos_permission(${ARGV})
endfunction()
endif()
function(_qt_internal_harmonyos_test_bundle_settings_file out_var)
set(${out_var} "${CMAKE_BINARY_DIR}/all_tests-harmony-deployment-settings.json" PARENT_SCOPE)
endfunction()
# Test bundle: deployment-settings JSON for an "uber" HAP containing every
# libtst_*.so under CMAKE_BINARY_DIR.
function(_qt_internal_harmonyos_generate_test_bundle_deployment_settings)
_qt_internal_harmonyos_get_sdk_ndk_paths(sdk_root ndk_root)
_qt_internal_harmonyos_test_bundle_settings_file(CONFIG_FILE)
set(JSON_CONTENT "{\n")
string(APPEND JSON_CONTENT " \"test-bundle\": true,\n")
string(APPEND JSON_CONTENT " \"test-binaries-directory\": \"${CMAKE_BINARY_DIR}\",\n")
string(APPEND JSON_CONTENT " \"harmonyos-app-name\": \"QtAutoTests\",\n")
string(APPEND JSON_CONTENT " \"harmonyos-app-bundle-name\": \"org.qtproject.autotests\",\n")
string(APPEND JSON_CONTENT " \"harmonyos-target-arch\": [\"${OHOS_ARCH}\"]")
# Exclude "phone" so the bundle is a native 2-in-1 app: a bundle that
# lists "phone" is treated as a phone-app-on-PC, which makes
# window.restore() fail (QTBUG-148467) and breaks window-state tests.
string(APPEND JSON_CONTENT ",\n \"harmonyos-module-device-types\": [\"tablet\", \"2in1\"]")
if(sdk_root)
string(APPEND JSON_CONTENT ",\n \"sdk-root\": \"${sdk_root}\"")
endif()
if(ndk_root)
string(APPEND JSON_CONTENT ",\n \"ndk-root\": \"${ndk_root}\"")
endif()
_qt_internal_harmonyos_get_qt_install_dirs(
_qt_install_root _qt_install_libs_dir _qt_install_plugins_dir _qt_install_qml_dir
_qt_install_data_dir)
string(APPEND JSON_CONTENT
",\n \"qtLibsDirectory\": \"${_qt_install_root}/${_qt_install_libs_dir}\"")
string(APPEND JSON_CONTENT
",\n \"qtPluginsDirectory\": \"${_qt_install_root}/${_qt_install_plugins_dir}\"")
string(APPEND JSON_CONTENT
",\n \"qtQmlDirectory\": \"${_qt_install_root}/${_qt_install_qml_dir}\"")
# Add the build-tree QML and plugins directories as higher-priority import
# paths. harmonydeployqt only scans the installed Qt prefix
# (qtQmlDirectory / qtPluginsDirectory), which fails in two cases: a
# prefix build where install has not been run yet, and a non-prefix build
# where nothing is ever installed. harmonydeployqt processes
# qml-import-paths and plugins-import-paths before their respective
# install-prefix counterparts, so build-tree files shadow installed copies
# via copyFileIfNewer's mtime-based priority. In a superbuild the two
# paths resolve to the same absolute location; the normalised-path
# comparisons below suppress duplicate entries. Both fields are arrays
# for consistency with QT_QML_IMPORT_PATH and to allow multiple paths in
# the future; currently each holds at most one entry.
set(_build_qml_dir "${QT_BUILD_DIR}/${INSTALL_QMLDIR}")
get_filename_component(_norm_build_qml "${_build_qml_dir}" ABSOLUTE)
get_filename_component(_norm_install_qml "${_qt_install_root}/${_qt_install_qml_dir}" ABSOLUTE)
if(NOT "${_norm_build_qml}" STREQUAL "${_norm_install_qml}")
string(APPEND JSON_CONTENT
",\n \"qml-import-paths\": [\"${_norm_build_qml}\"]")
endif()
unset(_build_qml_dir)
unset(_norm_build_qml)
unset(_norm_install_qml)
set(_build_plugins_dir "${QT_BUILD_DIR}/${INSTALL_PLUGINSDIR}")
get_filename_component(_norm_build_plugins "${_build_plugins_dir}" ABSOLUTE)
get_filename_component(_norm_install_plugins "${_qt_install_root}/${_qt_install_plugins_dir}" ABSOLUTE)
if(NOT "${_norm_build_plugins}" STREQUAL "${_norm_install_plugins}")
string(APPEND JSON_CONTENT
",\n \"plugins-import-paths\": [\"${_norm_build_plugins}\"]")
endif()
unset(_build_plugins_dir)
unset(_norm_build_plugins)
unset(_norm_install_plugins)
if(QT_HOST_PATH)
string(APPEND JSON_CONTENT
",\n \"qtLibExecsDirectory\": \"${QT_HOST_PATH}/${QT6_HOST_INFO_LIBEXECDIR}\"")
string(APPEND JSON_CONTENT ",\n \"qtHostDirectory\": \"${QT_HOST_PATH}\"")
endif()
# Templates are built only for OHOS, so they live under the target prefix.
set(TEMPLATE_DIR
"${_qt_install_root}/${_qt_install_data_dir}/src/harmonyos/templates")
if(EXISTS "${TEMPLATE_DIR}")
file(REAL_PATH "${TEMPLATE_DIR}" TEMPLATE_DIR)
string(APPEND JSON_CONTENT
",\n \"harmonyos-package-source-directory\": \"${TEMPLATE_DIR}\"")
endif()
# Strip the Qt install prefix that qt.toolchain.cmake prepends — we only
# want user-supplied third-party prefixes (e.g. HARMONYOS_DEPS_ROOT/lib).
set(_extra_root_paths "${CMAKE_FIND_ROOT_PATH}")
list(REMOVE_DUPLICATES _extra_root_paths)
if(QT6_INSTALL_PREFIX)
list(REMOVE_ITEM _extra_root_paths "${QT6_INSTALL_PREFIX}")
endif()
set(_extra_libs_dirs_json "")
foreach(_root_path IN LISTS _extra_root_paths)
set(_lib_dir "${_root_path}/lib")
if(EXISTS "${_lib_dir}")
file(TO_CMAKE_PATH "${_lib_dir}" _lib_dir)
if(_extra_libs_dirs_json)
string(APPEND _extra_libs_dirs_json ", ")
endif()
string(APPEND _extra_libs_dirs_json "\"${_lib_dir}\"")
endif()
endforeach()
if(_extra_libs_dirs_json)
string(APPEND JSON_CONTENT ",\n \"extra-libs-dirs\": [${_extra_libs_dirs_json}]")
endif()
# Collect permissions mirroring _qt_internal_harmonyos_generate_deployment_settings():
# each test target's own permissions first (so explicit overrides win), then the
# union of all transitive Qt module dependencies. Walk each unique dep only once.
# De-duplicate by JSON "name" field; first occurrence wins.
set(_all_permissions "")
set(_seen_names "")
set(_all_deps "")
get_property(_test_targets GLOBAL PROPERTY QT_INTERNAL_HARMONYOS_TEST_TARGETS)
set_property(GLOBAL PROPERTY QT_INTERNAL_HARMONYOS_TEST_TARGETS "")
foreach(_test IN LISTS _test_targets)
if(NOT TARGET "${_test}")
continue()
endif()
get_target_property(_own_perms "${_test}" _qt_harmonyos_permissions)
if(_own_perms AND NOT _own_perms STREQUAL "_own_perms-NOTFOUND")
foreach(_entry IN LISTS _own_perms)
string(JSON _name GET "${_entry}" name)
if(NOT _name IN_LIST _seen_names)
list(APPEND _seen_names "${_name}")
list(APPEND _all_permissions "${_entry}")
endif()
endforeach()
endif()
__qt_internal_collect_all_target_dependencies("${_test}" _deps)
list(APPEND _all_deps ${_deps})
endforeach()
list(REMOVE_DUPLICATES _all_deps)
foreach(_dep IN LISTS _all_deps)
get_target_property(_perms "${_dep}" _qt_harmonyos_permissions)
if(NOT _perms OR _perms STREQUAL "_perms-NOTFOUND")
continue()
endif()
foreach(_entry IN LISTS _perms)
string(JSON _name GET "${_entry}" name)
if(NOT _name IN_LIST _seen_names)
list(APPEND _seen_names "${_name}")
list(APPEND _all_permissions "${_entry}")
endif()
endforeach()
endforeach()
if(_all_permissions)
list(JOIN _all_permissions ",\n " _permissions_joined)
string(APPEND JSON_CONTENT
",\n \"permissions\": [\n ${_permissions_joined}\n ]")
endif()
set(_all_dialogs "")
set(_seen_dialogs "")
foreach(_dialog_target IN LISTS _test_targets _all_deps)
if(NOT TARGET "${_dialog_target}")
continue()
endif()
get_target_property(_dialogs "${_dialog_target}" _qt_harmonyos_blocking_test_dialogs)
if(NOT _dialogs OR _dialogs STREQUAL "_dialogs-NOTFOUND")
continue()
endif()
foreach(_entry IN LISTS _dialogs)
string(JSON _name GET "${_entry}" name)
if(NOT _name IN_LIST _seen_dialogs)
list(APPEND _seen_dialogs "${_name}")
list(APPEND _all_dialogs "${_entry}")
endif()
endforeach()
endforeach()
if(_all_dialogs)
list(JOIN _all_dialogs ",\n " _dialogs_joined)
string(APPEND JSON_CONTENT
",\n \"blockingTestDialogs\": [\n ${_dialogs_joined}\n ]")
endif()
# Collect QT_HARMONYOS_EXTRA_PLUGINS from every test target.
set(_all_extra_plugins "")
foreach(_test IN LISTS _test_targets)
if(NOT TARGET "${_test}")
continue()
endif()
get_target_property(_extra_plugins "${_test}" QT_HARMONYOS_EXTRA_PLUGINS)
if(NOT _extra_plugins OR _extra_plugins STREQUAL "_extra_plugins-NOTFOUND")
continue()
endif()
foreach(_item IN LISTS _extra_plugins)
if(TARGET "${_item}")
list(APPEND _all_extra_plugins "$<TARGET_FILE:${_item}>")
else()
list(APPEND _all_extra_plugins "${_item}")
endif()
endforeach()
endforeach()
list(REMOVE_DUPLICATES _all_extra_plugins)
if(_all_extra_plugins)
list(JOIN _all_extra_plugins "\", \"" _extra_plugins_joined)
string(APPEND JSON_CONTENT
",\n \"harmonyos-extra-plugins\": [\"${_extra_plugins_joined}\"]")
endif()
string(APPEND JSON_CONTENT "\n}\n")
file(GENERATE OUTPUT "${CONFIG_FILE}" CONTENT "${JSON_CONTENT}")
endfunction()
# Create all_tests_make_hap: invoke harmonydeployqt in test-bundle mode.
# When HARMONYOS_HVIGOR is set, the helper also runs hvigor to build the
# signed HAP; otherwise only the project is generated.
# hvigor writes entry-default-signed.hap when signing is configured and
# entry-default-unsigned.hap otherwise. Pick the matching name so the build
# edge's OUTPUT and the install fixture target what hvigor actually produces.
function(_qt_internal_harmonyos_test_bundle_hap_basename out_var)
if(DEFINED ENV{QT_HARMONYOS_SIGNING_CERT_PATH}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_PROFILE}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_STORE_FILE}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_KEY_ALIAS}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_KEY_PASSWORD}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_STORE_PASSWORD})
set(${out_var} "entry-default-signed.hap" PARENT_SCOPE)
else()
set(${out_var} "entry-default-unsigned.hap" PARENT_SCOPE)
endif()
endfunction()
function(_qt_internal_harmonyos_add_all_tests_hap_target)
_qt_internal_harmonyos_test_bundle_settings_file(CONFIG_FILE)
cmake_language(DEFER CALL _qt_internal_harmonyos_generate_test_bundle_deployment_settings)
set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/harmonyos-tests-bundle")
_qt_internal_check_depfile_support(has_depfile_support)
_qt_internal_harmonyos_test_bundle_hap_basename(_hap_basename)
set(HAP_OUTPUT_FILE "${OUTPUT_DIR}/entry/build/default/outputs/default/${_hap_basename}")
set(BINARIES_TXT_FILE "${OUTPUT_DIR}/binaries.txt")
set(DEP_FILE_PATH "${CMAKE_BINARY_DIR}/all_tests_hap.d")
if(QT_HOST_PATH)
set(HARMONYDEPLOYQT_EXECUTABLE
"${QT_HOST_PATH}/${QT6_HOST_INFO_BINDIR}/harmonydeployqt")
else()
set(HARMONYDEPLOYQT_EXECUTABLE
"${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${QT6_HOST_INFO_BINDIR}/harmonydeployqt")
endif()
set(extra_deploy_args "")
if(HARMONYOS_HVIGOR)
list(APPEND extra_deploy_args --hvigor "${HARMONYOS_HVIGOR}")
endif()
# Without hvigor the signed HAP is never produced and the install fixture
# would later fail with a confusing "file not found".
if(NOT HARMONYOS_HVIGOR
AND (DEFINED ENV{QT_HARMONYOS_SIGNING_CERT_PATH}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_PROFILE}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_STORE_FILE}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_KEY_ALIAS}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_KEY_PASSWORD}
OR DEFINED ENV{QT_HARMONYOS_SIGNING_STORE_PASSWORD}))
message(WARNING "QT_HARMONYOS_SIGNING_* environment variables are set, but "
"HARMONYOS_HVIGOR is not. The signed test HAP will not be produced; the "
"HAP install fixture will fail. Pass -DHARMONYOS_HVIGOR=<path-to-hvigorw> "
"or export QT_HARMONYOS_HVIGOR before configuring.")
endif()
# Match the EXCLUDE_FROM_ALL policy that qt_build_tests() applies to the
# individual test binaries: build the HAP as part of the default target
# only when QT_BUILD_TESTS_BY_DEFAULT is on.
if(QT_INTERNAL_TEST_TARGETS_EXCLUDE_FROM_ALL)
set(_all_keyword "")
else()
set(_all_keyword "ALL")
endif()
if(has_depfile_support)
cmake_policy(PUSH)
if(POLICY CMP0116)
cmake_policy(SET CMP0116 NEW)
endif()
set(relative_to_dir ${CMAKE_BINARY_DIR})
add_custom_command(OUTPUT "${HAP_OUTPUT_FILE}" "${BINARIES_TXT_FILE}"
COMMAND ${HARMONYDEPLOYQT_EXECUTABLE}
--test-bundle
--input "${CONFIG_FILE}"
--output "${OUTPUT_DIR}"
--depfile "${DEP_FILE_PATH}"
--depfile-base "${relative_to_dir}"
${extra_deploy_args}
DEPENDS "${CONFIG_FILE}"
DEPFILE "${DEP_FILE_PATH}"
COMMENT "Generating HarmonyOS test bundle HAP"
VERBATIM
)
cmake_policy(POP)
add_custom_target(all_tests_make_hap ${_all_keyword}
DEPENDS "${HAP_OUTPUT_FILE}" "${BINARIES_TXT_FILE}")
else()
add_custom_target(all_tests_make_hap ${_all_keyword}
COMMAND ${HARMONYDEPLOYQT_EXECUTABLE}
--test-bundle
--input "${CONFIG_FILE}"
--output "${OUTPUT_DIR}"
${extra_deploy_args}
DEPENDS "${CONFIG_FILE}"
COMMENT "Generating HarmonyOS test bundle HAP"
VERBATIM
)
endif()
_qt_internal_ensure_internal_custom_target(qt_internal_plugins)
add_dependencies(all_tests_make_hap qt_internal_plugins)
endfunction()
# FIXTURES_SETUP test that pushes the signed HAP to the device before any
# autotest runs. Requires HARMONYOS_HDC. Use QT_HARMONYOS_DEVICE (read from
# the environment when the generated script runs, i.e. per ctest invocation)
# to target a specific device, matching harmonyostestrunner's --device/-t.
function(_qt_internal_harmonyos_add_hap_install_fixture hap_signed_file)
if(NOT HARMONYOS_HDC)
return()
endif()
file(TO_CMAKE_PATH "${HARMONYOS_HDC}" hdc_cmake_path)
file(TO_CMAKE_PATH "${hap_signed_file}" hap_cmake_path)
set(install_script "${CMAKE_BINARY_DIR}/HarmonyOSInstallHapFixture.cmake")
file(GENERATE OUTPUT "${install_script}" CONTENT
"set(hdc \"${hdc_cmake_path}\")
set(hap \"${hap_cmake_path}\")
set(hdc_args \"\")
if(DEFINED ENV{QT_HARMONYOS_DEVICE} AND NOT \"\$ENV{QT_HARMONYOS_DEVICE}\" STREQUAL \"\")
set(hdc_args -t \"\$ENV{QT_HARMONYOS_DEVICE}\")
endif()
message(STATUS \"Installing HarmonyOS test HAP: \${hap}\")
file(TO_NATIVE_PATH \"\${hap}\" hap_native)
execute_process(
COMMAND \"\${hdc}\" \${hdc_args} app install -r \"\${hap_native}\"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
message(STATUS \"\${output}\")
if(NOT result EQUAL 0 OR output MATCHES \"error: failed to install bundle\")
message(FATAL_ERROR \"HAP install failed (exit code: \${result}):\\n\${output}\")
endif()
"
)
add_test(NAME HarmonyOSTestBundleInstall
COMMAND "${CMAKE_COMMAND}" -P "${install_script}"
)
set_tests_properties(HarmonyOSTestBundleInstall PROPERTIES
FIXTURES_SETUP HarmonyOSTestBundle
RUN_SERIAL TRUE
TIMEOUT 120
)
endfunction()
# Entry point. Idempotent; safe to call from qt_build_tests() for every module.
function(qt_internal_add_harmonyos_test_bundle)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "OHOS")
message(WARNING "qt_internal_add_harmonyos_test_bundle() called on non-OHOS platform")
return()
endif()
if(TARGET all_tests_make_hap)
return()
endif()
_qt_internal_harmonyos_add_all_tests_hap_target()
set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/harmonyos-tests-bundle")
_qt_internal_harmonyos_test_bundle_hap_basename(_hap_basename)
set(HAP_INSTALL_FILE
"${OUTPUT_DIR}/entry/build/default/outputs/default/${_hap_basename}")
_qt_internal_harmonyos_add_hap_install_fixture("${HAP_INSTALL_FILE}")
endfunction()