Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,322 @@
# SPDX-FileCopyrightText: 2014 Aleix Pol i Gonzalez <aleixpol@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
AndroidToolchain
----------------
Enable easy compilation of cmake projects on Android.
By using this android toolchain, the projects will be set up to compile the
specified project targeting an Android platform, depending on its input.
Furthermore, if desired, an APK can be directly generated by using the
`androiddeployqt <https://doc.qt.io/qt-5/deployment-android.html>`_ tool.
CMake upstream has Android support now. This module will still give us some
useful features offering androiddeployqt integration and adequate executables
format for our Android applications.
Since we are using CMake Android support, any information from CMake documentation
still applies:
https://cmake.org/cmake/help/v3.7/manual/cmake-toolchains.7.html#cross-compiling-for-android
.. note::
This module requires CMake 3.18.
Since 1.7.0.
Usage
=====
To use this file, you need to set the ``CMAKE_TOOLCHAIN_FILE`` to point to
``Android.cmake`` on the command line::
cmake -DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake
You will also need to provide the locations of the Android NDK and SDK. This
can be done on the commandline or with environment variables; in either case
the variable names are:
``CMAKE_ANDROID_NDK``
The NDK root path.
``ANDROID_SDK_ROOT``
The SDK root path.
Additional options are specified as cache variables (eg: on the command line):
``ANDROID_ABI``
The ABI to use. See the ``sources/cxx-stl/gnu-libstdc++/*/libs``
directories in the NDK. Default: ``armeabi-v7a``.
``ANDROID_SDK_COMPILE_API``
The platform API level to compile against. May be different from the NDK
target. Default: newest installed version (e.g. android-30).
``ANDROID_SDK_BUILD_TOOLS_REVISION``
The build tools version to use.
Default: newest installed version (e.g. ``30.0.2``).
``ANDROID_EXTRA_LIBS``
The ";"-separated list of full paths to libs to include in resulting APK (Qt 5 only).
For integrating other libraries which are not part of the Android toolchain,
like Qt5, and installed to a separate prefix on the host system, the install
prefixes of those libraries would be passed as alternative roots as list via
``ECM_ADDITIONAL_FIND_ROOT_PATH``. Since 5.30.0.
For example, for integrating a Qt5 for Android present at
``~/Qt/5.14.2/android/`` and some other libraries installed to
the prefix ``/opt/android/foo``, you would use::
cmake \
-DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
-DECM_ADDITIONAL_FIND_ROOT_PATH="~/Qt/5.14.2/android/;/opt/android/foo"
If your project uses ``find_package()`` to locate build tools on the host
system, make sure to pass ``CMAKE_FIND_ROOT_PATH_BOTH`` or
``NO_CMAKE_FIND_ROOT_PATH`` as argument in the call. See the
``find_package()`` documentation for more details.
Deploying Qt 5 Applications
===========================
After building the application, you will need to generate an APK that can be
deployed to an Android device. This module integrates androiddeployqt support
to help with this for Qt-based projects. To enable this, set the
``QTANDROID_EXPORTED_TARGET`` variable to the targets you wish to export as an
APK (in a ;-separed list), as well as ``ANDROID_APK_DIR`` to a directory
containing some basic information. This will create a ``create-apk-<target>``
target that will generate the APK file. See the `Qt on Android deployment
documentation <https://doc.qt.io/qt-5/deployment-android.html>`_ for more
information.
For example, you could do::
cmake \
-DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
-DQTANDROID_EXPORTED_TARGET=myapp \
-DANDROID_APK_DIR=myapp-apk
make
make create-apk-myapp
You can specify the APK output directory by setting ``ANDROID_APK_OUTPUT_DIR``.
Otherwise the APK can be found in ``myapp_build_apk/`` in the build directory.
The create-apk-myapp target will be able to take an ARGS parameter with further
arguments for androiddeployqt. For example, one can use::
make create-apk-myapp ARGS="--install"
To install the apk to test. To generate a signed apk, one can do it with the
following syntax::
make create-apk-myapp ARGS="--sign ~/my.keystore alias_name"
In case it's needed for your application to set the APK directory from cmake
scripting you can also set the directory as the ANDROID_APK_DIR property of
the create-apk-myapp target.
See Android documentation on how to create a keystore to use
Advanced Options
================
The following packaging options are mainly interesting for automation or integration
with CI/CD pipelines:
``ANDROID_APK_OUTPUT_DIR``
Specifies a folder where the generated APK files should be placed.
``ANDROID_FASTLANE_METADATA_OUTPUT_DIR``
Specifies a folder where the generated metadata for the F-Droid store
should be placed.
``ANDROIDDEPLOYQT_EXTRA_ARGS``
Allows to pass additional arguments to `androiddeployqt`. This is an alternative to
the `ARGS=` argument for `make` and unlike that works with all CMake generators.
#]=======================================================================]
cmake_minimum_required(VERSION "3.18")
macro(set_deprecated_variable actual_variable deprecated_variable default_value)
set(${deprecated_variable} "${default_value}" CACHE STRING "Deprecated. Use ${actual_variable}")
if (NOT DEFINED ${actual_variable})
set(${actual_variable} ${${deprecated_variable}})
endif()
endmacro()
set_deprecated_variable(CMAKE_ANDROID_NDK ANDROID_NDK "$ENV{ANDROID_NDK}")
set_deprecated_variable(CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION ANDROID_GCC_VERSION "clang")
set_deprecated_variable(CMAKE_ANDROID_API ANDROID_API_LEVEL "21")
if(NOT DEFINED ENV{ANDROID_ARCH})
set(ENV{ANDROID_ARCH} "arm")
endif()
set_deprecated_variable(CMAKE_ANDROID_ARCH ANDROID_ARCHITECTURE $ENV{ANDROID_ARCH})
if(NOT DEFINED ENV{ANDROID_ARCH_ABI})
set(ENV{ANDROID_ARCH_ABI} "armeabi-v7a")
endif()
set_deprecated_variable(CMAKE_ANDROID_ARCH_ABI ANDROID_ABI "$ENV{ANDROID_ARCH_ABI}")
set(ANDROID_SDK_ROOT "$ENV{ANDROID_SDK_ROOT}" CACHE PATH "Android SDK path")
file(GLOB platforms LIST_DIRECTORIES TRUE RELATIVE ${ANDROID_SDK_ROOT}/platforms ${ANDROID_SDK_ROOT}/platforms/*)
list(SORT platforms COMPARE NATURAL)
list(GET platforms -1 _default_platform)
set(ANDROID_SDK_COMPILE_API "${_default_platform}" CACHE STRING "Android API Level")
if(ANDROID_SDK_COMPILE_API MATCHES "^android-([0-9]+)$")
set(ANDROID_SDK_COMPILE_API ${CMAKE_MATCH_1})
endif()
file(GLOB build-tools LIST_DIRECTORIES TRUE RELATIVE ${ANDROID_SDK_ROOT}/build-tools ${ANDROID_SDK_ROOT}/build-tools/*)
list(SORT build-tools COMPARE NATURAL)
list(GET build-tools -1 _default_sdk)
set(ANDROID_SDK_BUILD_TOOLS_REVISION "${_default_sdk}" CACHE STRING "Android Build Tools version")
set(CMAKE_SYSTEM_VERSION ${CMAKE_ANDROID_API})
set(CMAKE_SYSTEM_NAME Android)
if (NOT CMAKE_ANDROID_STL_TYPE)
set(CMAKE_ANDROID_STL_TYPE c++_shared)
endif()
# Workaround link failure at FindThreads in CXX-only mode,
# armv7 really doesn't like mixing PIC/PIE code.
# Since we only have to care about a single compiler,
# hard-code the values here.
# Qt6 fixes this and breaks in nested CMake calls (e.g. try_compile) if we
# define Threads::Threads here, at least if the "C" language is enabled. In
# CXX-only projects we still need to do this unconditionally...
#
# We cannot use our usual Qt version check at this point though yet,
# so check whether we are chainloaded by the Qt toolchain as an indicator
# for Qt6.
# When building Qt6Base itself the check does not work, hence we have
# ECM_THREADS_WORKAROUND for that case which set to OFF in the Craft blueprints.
# As that doesn't propagate to nested CMake calls (e.g. tye_compile), we also check
# whether that is set via an environment variable (necessary with Qt >= 6.8.1).
if (NOT DEFINED ECM_THREADS_WORKAROUND AND NOT DEFINED ENV{ECM_THREADS_WORKAROUND})
set(ECM_THREADS_WORKAROUND TRUE)
endif()
get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES)
if (ECM_THREADS_WORKAROUND AND NOT TARGET Threads::Threads AND (NOT DEFINED __qt_chainload_toolchain_file OR NOT "C" IN_LIST _languages))
set(Threads_FOUND TRUE)
set(CMAKE_THREAD_LIBS_INIT "-pthread")
add_library(Threads::Threads INTERFACE IMPORTED)
set_property(TARGET Threads::Threads PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
set_property(TARGET Threads::Threads PROPERTY INTERFACE_LINK_LIBRARIES "-pthread")
endif()
# let the Android NDK toolchain file do the actual work
set(ANDROID_PLATFORM "android-${CMAKE_ANDROID_API}")
set(ANDROID_STL ${CMAKE_ANDROID_STL_TYPE})
include(${CMAKE_ANDROID_NDK}/build/cmake/android.toolchain.cmake REQUIRED)
# Export configurable variables for the try_compile() command.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
CMAKE_ANDROID_NDK
CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION
CMAKE_ANDROID_API
CMAKE_ANDROID_ARCH
CMAKE_ANDROID_ARCH_ABI
ANDROID_SDK_ROOT
ANDROID_SDK_COMPILE_API
)
# needed for androiddeployqt's stdcpp-path setting
if (ANDROID_TOOLCHAIN_ROOT)
set(ANDROID_SYSROOT_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/sysroot/usr")
else()
set(ANDROID_SYSROOT_PREFIX "${CMAKE_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr")
endif()
## HACK: Remove when we can depend on NDK r23
# Workaround issue https://github.com/android/ndk/issues/929
if(ANDROID_NDK_MAJOR VERSION_LESS 23)
unset(CMAKE_SYSROOT)
list(APPEND CMAKE_SYSTEM_INCLUDE_PATH
"${ANDROID_SYSROOT_PREFIX}/include/${CMAKE_LIBRARY_ARCHITECTURE}")
list(APPEND CMAKE_SYSTEM_INCLUDE_PATH "${ANDROID_SYSROOT_PREFIX}/include")
# Prepend in reverse order
list(PREPEND CMAKE_SYSTEM_LIBRARY_PATH
"${ANDROID_SYSROOT_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE}")
list(PREPEND CMAKE_SYSTEM_LIBRARY_PATH
"${ANDROID_SYSROOT_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE}/${ANDROID_PLATFORM_LEVEL}")
endif()
# Qt6 still expects ABI suffixes but only applies them in its own qt_add_[executable|library] macros
# as we typically don't use those we need another way to get those
if (DEFINED __qt_chainload_toolchain_file) # indicator for Qt6, see above
set(CMAKE_MODULE_LIBRARY_SUFFIX_C "_${CMAKE_ANDROID_ARCH_ABI}.so")
set(CMAKE_MODULE_LIBRARY_SUFFIX_CXX "_${CMAKE_ANDROID_ARCH_ABI}.so")
set(CMAKE_SHARED_LIBRARY_SUFFIX_C "_${CMAKE_ANDROID_ARCH_ABI}.so")
set(CMAKE_SHARED_LIBRARY_SUFFIX_CXX "_${CMAKE_ANDROID_ARCH_ABI}.so")
endif()
# these aren't set yet at this point by the Android toolchain, but without
# those the find_package() call in ECMAndroidDeployQt5 will fail
set(CMAKE_FIND_LIBRARY_PREFIXES "lib")
set(CMAKE_FIND_LIBRARY_SUFFIXES "_${CMAKE_ANDROID_ARCH_ABI}.so" ".so" ".a")
# Work around Qt messing with CMAKE_SHARED_LIBRARY_SUFFIX and thus breaking find_library()
# Unfortunately, just setting CMAKE_FIND_LIBRARY_SUFFIXES here won't help, as this will
# be subsequently overwritten.
macro(addAbiSuffix _var _access)
if (${_access} STREQUAL "MODIFIED_ACCESS")
list(PREPEND CMAKE_FIND_LIBRARY_SUFFIXES "_${CMAKE_ANDROID_ARCH_ABI}.so")
endif()
endmacro()
variable_watch(CMAKE_FIND_LIBRARY_SUFFIXES addAbiSuffix)
# determine STL architecture, which is using a different format than ANDROID_ARCH_ABI
string(REGEX REPLACE "-(clang)?([0-9].[0-9])?$" "" ECM_ANDROID_STL_ARCH "${ANDROID_TOOLCHAIN_NAME}")
if (NOT DEFINED ECM_ADDITIONAL_FIND_ROOT_PATH)
SET(ECM_ADDITIONAL_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH})
endif()
LIST(APPEND CMAKE_FIND_ROOT_PATH ${ECM_ADDITIONAL_FIND_ROOT_PATH})
#we want executables to be shared libraries, hooks will invoke the exported cmake function
set(CMAKE_CXX_LINK_EXECUTABLE
"<CMAKE_CXX_COMPILER> <CMAKE_SHARED_LIBRARY_CXX_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS> <SONAME_FLAG><TARGET_SONAME> -o <TARGET> <OBJECTS> <LINK_LIBRARIES>"
)
# As our executables are shared libraries, we also need them build with position independent code (PIC).
# Qt 5 forces that anyway, but in Qt 6 that is no longer the case for exectuables (which we pretend to build here),
# and so we end up with just PIE (coming from CMake).
# And as subsequent steps overwrite that setting again, we have to watch for that and redo our change.
set(CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC")
macro(resetPieOption _var _access)
if (${_access} STREQUAL "MODIFIED_ACCESS")
set(CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC")
endif()
endmacro()
variable_watch(CMAKE_CXX_COMPILE_OPTIONS_PIE resetPieOption)
set(ECM_DIR "${CMAKE_CURRENT_LIST_DIR}/../cmake" CACHE STRING "")
######### generation (Qt 5 only)
# Need to ensure we only get in here once, as this file is included twice:
# from CMakeDetermineSystem.cmake and from CMakeSystem.cmake generated within the
# build directory.
if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk" AND NOT __qt_chainload_toolchain_file)
get_filename_component(_CMAKE_ANDROID_DIR "${CMAKE_TOOLCHAIN_FILE}" PATH)
list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount)
include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt5.cmake)
math(EXPR last "${targetsCount}-1")
foreach(idx RANGE 0 ${last})
list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget)
list(GET ANDROID_APK_DIR ${idx} APK_DIR)
if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR)
message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}")
elseif(NOT APK_DIR)
get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE)
set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/")
endif()
ecm_androiddeployqt5("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}")
set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}")
endforeach()
elseif (NOT __qt_chainload_toolchain_file)
message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET=<targetname> and -DANDROID_APK_DIR=<paths>")
endif()
@@ -0,0 +1,129 @@
cmake_minimum_required (VERSION 3.19 FATAL_ERROR)
find_package(Qt5Core REQUIRED)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
# Taken from https://stackoverflow.com/a/62311397
function(_ecm_get_all_targets var)
set(targets)
_ecm_get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR})
set(${var} ${targets} PARENT_SCOPE)
endfunction()
macro(_ecm_get_all_targets_recursive targets dir)
get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
foreach(subdir ${subdirectories})
_ecm_get_all_targets_recursive(${targets} ${subdir})
endforeach()
get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS)
list(APPEND ${targets} ${current_targets})
endmacro()
function(_ecm_deferred_androiddeployqt)
_ecm_get_all_targets(all_targets)
set(module_targets)
foreach(tgt ${all_targets})
get_target_property(tgt_type ${tgt} TYPE)
if(tgt_type STREQUAL "MODULE_LIBRARY")
list(APPEND module_targets "$<TARGET_FILE:${tgt}>")
endif()
endforeach()
file(GENERATE OUTPUT "module-plugins" CONTENT "${module_targets}")
endfunction()
cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL _ecm_deferred_androiddeployqt)
function(ecm_androiddeployqt5 QTANDROID_EXPORTED_TARGET ECM_ADDITIONAL_FIND_ROOT_PATH)
set(EXPORT_DIR "${CMAKE_BINARY_DIR}/${QTANDROID_EXPORTED_TARGET}_build_apk/")
if (Qt5Core_VERSION VERSION_LESS 5.14.0)
set(EXECUTABLE_DESTINATION_PATH "${EXPORT_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI}/lib${QTANDROID_EXPORTED_TARGET}.so")
else()
set(EXECUTABLE_DESTINATION_PATH "${EXPORT_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI}/lib${QTANDROID_EXPORTED_TARGET}_${CMAKE_ANDROID_ARCH_ABI}.so")
endif()
set(QML_IMPORT_PATHS "")
# add build directory to the search path as well, so this works without installation
if (EXISTS ${CMAKE_BINARY_DIR}/lib)
set(QML_IMPORT_PATHS ${CMAKE_BINARY_DIR}/lib)
endif()
foreach(prefix ${ECM_ADDITIONAL_FIND_ROOT_PATH})
# qmlimportscanner chokes on symlinks, so we need to resolve those first
get_filename_component(qml_path "${prefix}/lib/qml" REALPATH)
if(EXISTS ${qml_path})
if (QML_IMPORT_PATHS)
set(QML_IMPORT_PATHS "${QML_IMPORT_PATHS},${qml_path}")
else()
set(QML_IMPORT_PATHS "${qml_path}")
endif()
endif()
endforeach()
if (QML_IMPORT_PATHS)
set(DEFINE_QML_IMPORT_PATHS "\"qml-import-paths\": \"${QML_IMPORT_PATHS}\",")
endif()
set(EXTRA_PREFIX_DIRS "\"${CMAKE_BINARY_DIR}\"")
foreach(prefix ${ECM_ADDITIONAL_FIND_ROOT_PATH})
set(EXTRA_PREFIX_DIRS "${EXTRA_PREFIX_DIRS}, \"${prefix}\"")
endforeach()
if (Qt5Core_VERSION VERSION_LESS 5.14.0)
set(_deployment_file_template "${_CMAKE_ANDROID_DIR}/deployment-file.json.in")
else()
set(_deployment_file_template "${_CMAKE_ANDROID_DIR}/deployment-file-qt514.json.in")
endif()
string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" _LOWER_CMAKE_HOST_SYSTEM_NAME)
configure_file("${_deployment_file_template}" "${CMAKE_BINARY_DIR}/${QTANDROID_EXPORTED_TARGET}-deployment.json.in1")
file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/${QTANDROID_EXPORTED_TARGET}-deployment.json.in2"
INPUT "${CMAKE_BINARY_DIR}/${QTANDROID_EXPORTED_TARGET}-deployment.json.in1")
if (CMAKE_GENERATOR STREQUAL "Unix Makefiles")
set(arguments "\\$(ARGS)")
endif()
function(havestl var access VALUE)
if (NOT VALUE STREQUAL "")
# look for ++ and .so as in libc++.so
string (REGEX MATCH "\"[^ ]+\\+\\+[^ ]*\.so\"" OUT ${VALUE})
file(WRITE ${CMAKE_BINARY_DIR}/stl "${OUT}")
endif()
endfunction()
function(haveranlib var access VALUE)
if (NOT VALUE STREQUAL "")
file(WRITE ${CMAKE_BINARY_DIR}/ranlib "${VALUE}")
endif()
endfunction()
variable_watch(CMAKE_CXX_STANDARD_LIBRARIES havestl)
variable_watch(CMAKE_RANLIB haveranlib)
if (NOT TARGET create-apk)
add_custom_target(create-apk)
if (NOT DEFINED ANDROID_FASTLANE_METADATA_OUTPUT_DIR)
set(ANDROID_FASTLANE_METADATA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/fastlane)
endif()
add_custom_target(create-fastlane
COMMAND Python3::Interpreter ${CMAKE_CURRENT_LIST_DIR}/generate-fastlane-metadata.py --output ${ANDROID_FASTLANE_METADATA_OUTPUT_DIR} --source ${CMAKE_SOURCE_DIR}
)
endif()
if (NOT DEFINED ANDROID_APK_OUTPUT_DIR)
set(ANDROID_APK_OUTPUT_DIR ${EXPORT_DIR})
endif()
set(CREATEAPK_TARGET_NAME "create-apk-${QTANDROID_EXPORTED_TARGET}")
add_custom_target(${CREATEAPK_TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E echo "Generating $<TARGET_NAME:${QTANDROID_EXPORTED_TARGET}> with $<TARGET_FILE_DIR:Qt5::qmake>/androiddeployqt"
COMMAND ${CMAKE_COMMAND} -E remove_directory "${EXPORT_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "$<TARGET_PROPERTY:create-apk-${QTANDROID_EXPORTED_TARGET},ANDROID_APK_DIR>" "${EXPORT_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${QTANDROID_EXPORTED_TARGET}>" "${EXECUTABLE_DESTINATION_PATH}"
COMMAND LANG=C ${CMAKE_COMMAND} "-DTARGET=$<TARGET_FILE:${QTANDROID_EXPORTED_TARGET}>" -P ${_CMAKE_ANDROID_DIR}/hasMainSymbol.cmake
COMMAND LANG=C ${CMAKE_COMMAND} -DINPUT_FILE="${QTANDROID_EXPORTED_TARGET}-deployment.json.in2" -DOUTPUT_FILE="${QTANDROID_EXPORTED_TARGET}-deployment.json" "-DTARGET=$<TARGET_FILE:${QTANDROID_EXPORTED_TARGET}>" "-DOUTPUT_DIR=$<TARGET_FILE_DIR:${QTANDROID_EXPORTED_TARGET}>" "-DEXPORT_DIR=${CMAKE_INSTALL_PREFIX}" "-DECM_ADDITIONAL_FIND_ROOT_PATH=\"${ECM_ADDITIONAL_FIND_ROOT_PATH}\"" "-DANDROID_EXTRA_LIBS=\"${ANDROID_EXTRA_LIBS}\"" "-DUSE_LLVM=\"${USE_LLVM}\"" -P ${_CMAKE_ANDROID_DIR}/specifydependencies.cmake
COMMAND $<TARGET_FILE_DIR:Qt5::qmake>/androiddeployqt ${ANDROIDDEPLOYQT_EXTRA_ARGS} --gradle --input "${QTANDROID_EXPORTED_TARGET}-deployment.json" --apk "${ANDROID_APK_OUTPUT_DIR}/${QTANDROID_EXPORTED_TARGET}-${CMAKE_ANDROID_ARCH_ABI}.apk" --output "${EXPORT_DIR}" --android-platform android-${ANDROID_SDK_COMPILE_API} --deployment bundled ${arguments}
)
# --android-platform above is only available as a command line option
# This specifies the actual version of the SDK files to use, and
# can be different from both the NDK target and the Java API target.
add_custom_target(install-apk-${QTANDROID_EXPORTED_TARGET}
COMMAND adb install -r "${ANDROID_APK_OUTPUT_DIR}/${QTANDROID_EXPORTED_TARGET}-${CMAKE_ANDROID_ARCH_ABI}.apk"
)
add_dependencies(create-apk ${CREATEAPK_TARGET_NAME})
add_dependencies(${CREATEAPK_TARGET_NAME} create-fastlane)
endfunction()
@@ -0,0 +1,20 @@
{
"qt": "@_qt5Core_install_prefix@",
"sdk": "@ANDROID_SDK_ROOT@",
"ndk": "@CMAKE_ANDROID_NDK@",
"toolchain-prefix": "llvm",
"tool-prefix": "llvm",
"ndk-host": "@_LOWER_CMAKE_HOST_SYSTEM_NAME@-@CMAKE_HOST_SYSTEM_PROCESSOR@",
"application-binary": "@QTANDROID_EXPORTED_TARGET@",
"qml-root-path": "@CMAKE_SOURCE_DIR@",
@DEFINE_QML_IMPORT_PATHS@
##EXTRALIBS##
##EXTRAPLUGINS##
"android-package-source-directory": "$<TARGET_PROPERTY:create-apk-${QTANDROID_EXPORTED_TARGET},ANDROID_APK_DIR>",
"stdcpp-path": "@ANDROID_SYSROOT_PREFIX@/lib",
"sdkBuildToolsRevision": "@ANDROID_SDK_BUILD_TOOLS_REVISION@",
"android-min-sdk-version": "@ANDROID_API_LEVEL@",
"android-target-sdk-version": "@ANDROID_SDK_COMPILE_API@",
"extraPrefixDirs": [ @EXTRA_PREFIX_DIRS@ ],
"architectures": { "@CMAKE_ANDROID_ARCH_ABI@": "@ECM_ANDROID_STL_ARCH@" }
}
@@ -0,0 +1,28 @@
{
"qt": "@ANDROID_QT6_INSTALL_PREFIX@",
"qtDataDirectory": "@QT6_INSTALL_DATA@",
"qtLibExecsDirectory": "@QT6_INSTALL_LIBEXECS@",
"qtLibsDirectory": "@QT6_INSTALL_LIBS@",
"qtPluginsDirectory": "@QT6_INSTALL_PLUGINS@",
"qtQmlDirectory": "@QT6_INSTALL_QML@",
"rcc-binary": "@QT6_RCC_BINARY@",
"sdk": "@ANDROID_SDK_ROOT@",
"ndk": "@CMAKE_ANDROID_NDK@",
"toolchain-prefix": "llvm",
"tool-prefix": "llvm",
"toolchain-version": "@CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION@",
"ndk-host": "@_LOWER_CMAKE_HOST_SYSTEM_NAME@-@CMAKE_HOST_SYSTEM_PROCESSOR@",
"application-binary": "@APK_NAME@",
"qml-root-path": "@CMAKE_SOURCE_DIR@",
@DEFINE_QML_IMPORT_PATHS@
##EXTRALIBS##
##EXTRAPLUGINS##
"android-package-source-directory": "@ANDROID_APK_DIR@",
"stdcpp-path": "@ANDROID_SYSROOT_PREFIX@/lib",
"sdkBuildToolsRevision": "@ANDROID_SDK_BUILD_TOOLS_REVISION@",
"android-min-sdk-version": "@ANDROID_API_LEVEL@",
"android-target-sdk-version": "@ANDROID_SDK_COMPILE_API@",
"extraPrefixDirs": [ @EXTRA_PREFIX_DIRS@ ],
"extraLibraryDirs": [ @EXTRA_LIB_DIRS@ ],
"architectures": { "@CMAKE_ANDROID_ARCH_ABI@": "@ECM_ANDROID_STL_ARCH@" }
}
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
@@ -0,0 +1,22 @@
{
"qt": "@_qt5Core_install_prefix@",
"sdk": "@ANDROID_SDK_ROOT@",
"ndk": "@CMAKE_ANDROID_NDK@",
"toolchain-prefix": "##ANDROID_TOOL_PREFIX##",
"tool-prefix": "##ANDROID_COMPILER_PREFIX##",
"toolchain-version": "##ANDROID_TOOLCHAIN_VERSION##",
"ndk-host": "@_LOWER_CMAKE_HOST_SYSTEM_NAME@-@CMAKE_HOST_SYSTEM_PROCESSOR@",
"target-architecture": "@CMAKE_ANDROID_ARCH_ABI@",
"application-binary": "@EXECUTABLE_DESTINATION_PATH@",
"qml-root-path": "@CMAKE_SOURCE_DIR@",
@DEFINE_QML_IMPORT_PATHS@
##EXTRALIBS##
##EXTRAPLUGINS##
"android-package-source-directory": "$<TARGET_PROPERTY:create-apk-${QTANDROID_EXPORTED_TARGET},ANDROID_APK_DIR>",
"stdcpp-path":##CMAKE_CXX_STANDARD_LIBRARIES##,
"sdkBuildToolsRevision": "@ANDROID_SDK_BUILD_TOOLS_REVISION@",
"android-min-sdk-version": "@ANDROID_API_LEVEL@",
"android-target-sdk-version": "@ANDROID_SDK_COMPILE_API@",
"extraPrefixDirs": [ @EXTRA_PREFIX_DIRS@ ],
"useLLVM": ##USE_LLVM##
}
@@ -0,0 +1,528 @@
#!/usr/bin/env python3
#
# SPDX-FileCopyrightText: 2018-2020 Aleix Pol Gonzalez <aleixpol@kde.org>
# SPDX-FileCopyrightText: 2019-2020 Ben Cooksley <bcooksley@kde.org>
# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Generates fastlane metadata for Android apps from appstream files.
#
import argparse
import glob
import io
import os
import re
import requests
import shutil
import subprocess
import sys
import tempfile
import xdg.DesktopEntry
import xml.etree.ElementTree as ET
import yaml
import zipfile
# Constants used in this script
# map KDE's translated language codes to those expected by Android
# see https://f-droid.org/en/docs/Translation_and_Localization/
# F-Droid is more tolerant than the Play Store here, the latter rejects anything not exactly matching its known codes
# Android does do the expected fallbacks, so the seemingly "too specific" mappings here are still working as expected
# see https://developer.android.com/guide/topics/resources/multilingual-support#resource-resolution-examples
# The following is the list of languages/translations that can be selected on the page "Main store listing" under
# "Store presence" of Google Play Console (as of 2024-03-15).
# Afrikaans af
# Albanian sq
# Amharic am
# Arabic ar
# Armenian hy-AM
# Azerbaijani az-AZ
# Bangla bn-BD
# Basque eu-ES
# Belarusian be
# Bulgarian bg
# Burmese my-MM
# Catalan ca
# Chinese (Hong Kong) zh-HK
# Chinese (Simplified) zh-CN
# Chinese (Traditional) zh-TW
# Croatian hr
# Czech cs-CZ
# Danish da-DK
# Dutch nl-NL
# English (Australia) en-AU
# English (Canada) en-CA
# English (United Kingdom) en-GB
# English en-IN
# English en-SG
# English en-ZA
# Estonian et
# Filipino fil
# Finnish fi-FI
# French (Canada) fr-CA
# French (France) fr-FR
# Galician gl-ES
# Georgian ka-GE
# German de-DE
# Greek el-GR
# Gujarati gu
# Hebrew iw-IL
# Hindi hi-IN
# Hungarian hu-HU
# Icelandic is-IS
# Indonesian id
# Italian it-IT
# Japanese ja-JP
# Kannada kn-IN
# Kazakh kk
# Khmer km-KH
# Korean ko-KR
# Kyrgyz ky-KG
# Lao lo-LA
# Latvian lv
# Lithuanian lt
# Macedonian mk-MK
# Malay (Malaysia) ms-MY
# Malay ms
# Malayalam ml-IN
# Marathi mr-IN
# Mongolian mn-MN
# Nepali ne-NP
# Norwegian no-NO
# Persian fa
# Persian fa-AE
# Persian fa-AF
# Persian fa-IR
# Polish pl-PL
# Portuguese (Brazil) pt-BR
# Portuguese (Portugal) pt-PT
# Punjabi pa
# Romanian ro
# Romansh rm
# Russian ru-RU
# Serbian sr
# Sinhala si-LK
# Slovak sk
# Slovenian sl
# Spanish (Latin America) es-419
# Spanish (Spain) es-ES
# Spanish (United States) es-US
# Swahili sw
# Swedish sv-SE
# Tamil ta-IN
# Telugu te-IN
# Thai th
# Turkish tr-TR
# Ukrainian uk
# Urdu ur
# Vietnamese vi
# Zulu zu
languageMap = {
None: "en-US",
"ast": None, # not supported by Google Play for meta data
"ca-valencia": None, # not supported by Android
"cs": "cs-CZ",
"da": "da-DK",
"de": "de-DE",
"el": "el-GR",
"eo": None, # neither supported by Android nor by Google Play for meta data
"es": "es-ES",
"eu": "eu-ES",
"fi": "fi-FI",
"fr": "fr-FR",
"gl": "gl-ES",
"ia": None, # not supported by Google Play for meta data
"it": "it-IT",
"ka": "ka-GE",
"ko": "ko-KR",
"nl": "nl-NL",
"nn": "no-NO", # Google Play only supports no-NO (no = macrolanguage for nb/Bokmal and nn/Nynorsk)
"pl": "pl-PL",
"pt": "pt-PT",
"ru": "ru-RU",
"sr": "sr-Cyrl-RS",
"sr@latin": "sr-Latn-RS",
"sv": "sv-SE",
"tr": "tr-TR",
'x-test': None
}
# The subset of supported rich text tags in F-Droid and Google Play
# - see https://f-droid.org/en/docs/All_About_Descriptions_Graphics_and_Screenshots/ for F-Droid
# - Google Play doesn't support lists
supportedRichTextTags = { 'b', 'u', 'i' }
# List all translated languages present in an Appstream XML file
def listAllLanguages(root, langs):
for elem in root:
lang = elem.get('{http://www.w3.org/XML/1998/namespace}lang')
if not lang in langs:
langs.add(lang)
listAllLanguages(elem, langs)
# Apply language fallback to a map of translations
def applyLanguageFallback(data, allLanguages):
for l in allLanguages:
if not l in data or not data[l] or len(data[l]) == 0:
data[l] = data[None]
# Android appdata.xml textual item parser
# This function handles reading standard text entries within an Android appdata.xml file
# In particular, it handles splitting out the various translations, and converts some HTML to something which F-Droid can make use of
# We have to handle incomplete translations both on top-level and intermediate tags,
# and fall back to the English default text where necessary.
def readText(elem, found, allLanguages):
# Determine the language this entry is in
lang = elem.get('{http://www.w3.org/XML/1998/namespace}lang')
# Do we have any text for this language yet?
# If not, get everything setup
for l in allLanguages:
if not l in found:
found[l] = ""
# If there is text available, we'll want to extract it
# Additionally, if this element has any children, make sure we read those as well
if elem.tag in supportedRichTextTags:
if (elem.text and elem.text.strip()) or lang:
found[lang] += '<' + elem.tag + '>'
else:
for l in allLanguages:
found[l] += '<' + elem.tag + '>'
elif elem.tag == 'li':
found[lang] += '· '
if elem.text and elem.text.strip():
found[lang] += elem.text
subOutput = {}
for child in elem:
if not child.get('{http://www.w3.org/XML/1998/namespace}lang') and len(subOutput) > 0:
applyLanguageFallback(subOutput, allLanguages)
for l in allLanguages:
found[l] += subOutput[l]
subOutput = {}
readText(child, subOutput, allLanguages)
if len(subOutput) > 0:
applyLanguageFallback(subOutput, allLanguages)
for l in allLanguages:
found[l] += subOutput[l]
if elem.tag in supportedRichTextTags:
if (elem.text and elem.text.strip()) or lang:
found[lang] += '</' + elem.tag + '>'
else:
for l in allLanguages:
found[l] += '</' + elem.tag + '>'
# Finally, if this element is a HTML Paragraph (p) or HTML List Item (li) make sure we add a new line for presentation purposes
if elem.tag == 'li' or elem.tag == 'p':
found[lang] += "\n"
# Create the various Fastlane format files per the information we've previously extracted
# These files are laid out following the Fastlane specification (links below)
# https://github.com/fastlane/fastlane/blob/2.28.7/supply/README.md#images-and-screenshots
# https://docs.fastlane.tools/actions/supply/
def createFastlaneFile( applicationName, filenameToPopulate, fileContent ):
# Go through each language and content pair we've been given
for lang, text in fileContent.items():
# First, do we need to amend the language id, to turn the Android language ID into something more F-Droid/Fastlane friendly?
languageCode = languageMap.get(lang, lang)
if not languageCode:
continue
# Next we need to determine the path to the directory we're going to be writing the data into
repositoryBasePath = arguments.output
path = os.path.join( repositoryBasePath, 'metadata', applicationName, languageCode )
# Make sure the directory exists
os.makedirs(path, exist_ok=True)
# Now write out file contents!
with open(path + '/' + filenameToPopulate, 'w') as f:
f.write(text.strip()) # trim whitespaces, to avoid spurious differences after a Google Play roundtrip
# Create the summary appname.yml file used by F-Droid to summarise this particular entry in the repository
# see https://f-droid.org/en/docs/Build_Metadata_Reference/
def createYml(appname, data):
# Prepare to retrieve the existing information
info = {}
# Determine the path to the appname.yml file
repositoryBasePath = arguments.output
path = os.path.join( repositoryBasePath, 'metadata', appname + '.yml' )
# Update the categories first
# Now is also a good time to add 'KDE' to the list of categories as well
if 'categories' in data:
info['Categories'] = data['categories'][None] + ['KDE']
else:
info['Categories'] = ['KDE']
# Update the general summary as well
info['Summary'] = data['summary'][None]
# Check to see if we have a Homepage...
if 'url-homepage' in data:
info['WebSite'] = data['url-homepage'][None]
# What about a bug tracker?
if 'url-bugtracker' in data:
info['IssueTracker'] = data['url-bugtracker'][None]
if 'project_license' in data:
info["License"] = data['project_license'][None]
if 'source-repo' in data:
info['SourceCode'] = data['source-repo']
if 'url-donation' in data:
info['Donate'] = data['url-donation'][None]
else:
info['Donate'] = 'https://kde.org/community/donations/'
# static data
info['Translation'] = 'https://l10n.kde.org/'
# Finally, with our updates completed, we can save the updated appname.yml file back to disk
with open(path, 'w') as output:
yaml.dump(info, output, default_flow_style=False)
# Integrates locally existing image assets into the metadata
def processLocalImages(applicationName, data):
if not os.path.exists(os.path.join(arguments.source, 'fastlane')):
return
outPath = os.path.abspath(arguments.output);
oldcwd = os.getcwd()
os.chdir(os.path.join(arguments.source, 'fastlane'))
imageFiles = glob.glob('metadata/**/*.png', recursive=True)
imageFiles.extend(glob.glob('metadata/**/*.jpg', recursive=True))
for image in imageFiles:
# noramlize single- vs multi-app layouts
imageDestName = image.replace('metadata/android', 'metadata/' + applicationName)
# copy image
os.makedirs(os.path.dirname(os.path.join(outPath, imageDestName)), exist_ok=True)
shutil.copy(image, os.path.join(outPath, imageDestName))
# if the source already contains screenshots, those override whatever we found in the appstream file
if 'phoneScreenshots' in image:
data['screenshots'] = {}
os.chdir(oldcwd)
# Attempt to find the application icon if we haven't gotten that explicitly from processLocalImages
def findIcon(applicationName, iconBaseName):
iconPath = os.path.join(arguments.output, 'metadata', applicationName, 'en-US', 'images', 'icon.png')
if os.path.exists(iconPath):
return
oldcwd = os.getcwd()
os.chdir(arguments.source)
iconFiles = glob.glob(f"**/{iconBaseName}-playstore.png", recursive=True)
for icon in iconFiles:
os.makedirs(os.path.dirname(iconPath), exist_ok=True)
shutil.copy(icon, iconPath)
break
os.chdir(oldcwd)
# Download screenshots referenced in the appstream data
# see https://f-droid.org/en/docs/All_About_Descriptions_Graphics_and_Screenshots/
def downloadScreenshots(applicationName, data):
if not 'screenshots' in data:
return
path = os.path.join(arguments.output, 'metadata', applicationName, 'en-US', 'images', 'phoneScreenshots')
os.makedirs(path, exist_ok=True)
i = 1 # number screenshots starting at 1 rather than 0 to match what the fastlane tool does
for screenshot in data['screenshots']:
fileName = str(i) + '-' + screenshot[screenshot.rindex('/') + 1:]
r = requests.get(screenshot)
if r.status_code < 400:
with open(os.path.join(path, fileName), 'wb') as f:
f.write(r.content)
i += 1
# Put all metadata for the given application name into an archive
# We need this to easily transfer the entire metadata to the signing machine for integration
# into the F-Droid nightly repository
def createMetadataArchive(applicationName):
srcPath = os.path.join(arguments.output, 'metadata')
zipFileName = os.path.join(srcPath, 'fastlane-' + applicationName + '.zip')
if os.path.exists(zipFileName):
os.unlink(zipFileName)
archive = zipfile.ZipFile(zipFileName, 'w')
archive.write(os.path.join(srcPath, applicationName + '.yml'), applicationName + '.yml')
oldcwd = os.getcwd()
os.chdir(srcPath)
for file in glob.iglob(applicationName + '/**', recursive=True):
archive.write(file, file)
os.chdir(oldcwd)
# Generate metadata for the given appstream and desktop files
def processAppstreamFile(appstreamFileName, desktopFileName, iconBaseName):
# appstreamFileName has the form <id>.appdata.xml or <id>.metainfo.xml, so we
# have to strip off two extensions
applicationName = os.path.splitext(os.path.splitext(os.path.basename(appstreamFileName))[0])[0]
data = {}
# Within this file we look at every entry, and where possible try to export it's content so we can use it later
appstreamFile = open(appstreamFileName, "rb")
root = ET.fromstring(appstreamFile.read())
allLanguages = set()
listAllLanguages(root, allLanguages)
for child in root:
# Make sure we start with a blank slate for this entry
output = {}
# Grab the name of this particular attribute we're looking at
# Within the Fastlane specification, it is possible to have several items with the same name but as different types
# We therefore include this within our extracted name for the attribute to differentiate them
tag = child.tag
if 'type' in child.attrib:
tag += '-' + child.attrib['type']
# Have we found some information already for this particular attribute?
if tag in data:
output = data[tag]
# Are we dealing with category information here?
# If so, then we need to look into this items children to find out all the categories this APK belongs in
if tag == 'categories':
cats = []
for x in child:
cats.append(x.text)
output = { None: cats }
# screenshot links
elif tag == 'screenshots':
output = []
for screenshot in child:
if screenshot.tag == 'screenshot':
for image in screenshot:
if image.tag == 'image':
output.append(image.text)
# Otherwise this is just textual information we need to extract
else:
readText(child, output, allLanguages)
# Save the information we've gathered!
data[tag] = output
applyLanguageFallback(data['name'], allLanguages)
applyLanguageFallback(data['summary'], allLanguages)
applyLanguageFallback(data['description'], allLanguages)
# Did we find any categories?
# Sometimes we don't find any within the Fastlane information, but without categories the F-Droid store isn't of much use
# In the event this happens, fallback to the *.desktop file for the application to see if it can provide any insight.
if not 'categories' in data and desktopFileName:
# Parse the XDG format *.desktop file, and extract the categories within it
desktopFile = xdg.DesktopEntry.DesktopEntry(desktopFileName)
data['categories'] = { None: desktopFile.getCategories() }
# Try to figure out the source repository
if arguments.source and os.path.exists(os.path.join(arguments.source, '.git')):
upstream_ref = subprocess.check_output(['git', 'rev-parse', '--symbolic-full-name', '@{u}'], cwd=arguments.source).decode('utf-8')
remote = upstream_ref.split('/')[2]
output = subprocess.check_output(['git', 'remote', 'get-url', remote], cwd=arguments.source).decode('utf-8')
data['source-repo'] = output.strip()
# write meta data
createFastlaneFile( applicationName, "title.txt", data['name'] )
createFastlaneFile( applicationName, "short_description.txt", data['summary'] )
createFastlaneFile( applicationName, "full_description.txt", data['description'] )
createYml(applicationName, data)
# cleanup old image files before collecting new ones
imagePath = os.path.join(arguments.output, 'metadata', applicationName, 'en-US', 'images')
shutil.rmtree(imagePath, ignore_errors=True)
processLocalImages(applicationName, data)
downloadScreenshots(applicationName, data)
findIcon(applicationName, iconBaseName)
# put the result in an archive file for easier use by Jenkins
createMetadataArchive(applicationName)
# scan source directory for manifests/metadata we can work with
def scanSourceDir():
files = glob.iglob(arguments.source + "/**/AndroidManifest.xml*", recursive=True)
for file in files:
# third-party libraries might contain AndroidManifests which we are not interested in
if "3rdparty" in file:
continue
# find application id from manifest files
root = ET.parse(file)
appname = root.getroot().attrib['package']
is_app = False
prefix = '{http://schemas.android.com/apk/res/android}'
for md in root.findall("application/activity/meta-data"):
if md.attrib[prefix + 'name'] == 'android.app.lib_name':
is_app = True
if not appname or not is_app:
continue
iconBaseName = None
for elem in root.findall('application'):
if prefix + 'icon' in elem.attrib:
iconBaseName = elem.attrib[prefix + 'icon'].split('/')[-1]
# now that we have the app id, look for matching appdata/desktop files
appdataFiles = glob.glob(arguments.source + "/**/" + appname + ".metainfo.xml", recursive=True)
appdataFiles.extend(glob.glob(arguments.source + "/**/" + appname + ".appdata.xml", recursive=True))
appdataFile = None
for f in appdataFiles:
appdataFile = f
break
if not appdataFile:
continue
desktopFiles = glob.iglob(arguments.source + "/**/" + appname + ".desktop", recursive=True)
desktopFile = None
for f in desktopFiles:
desktopFile = f
break
processAppstreamFile(appdataFile, desktopFile, iconBaseName)
### Script Commences
# Parse the command line arguments we've been given
parser = argparse.ArgumentParser(description='Generate fastlane metadata for Android apps from appstream metadata')
parser.add_argument('--appstream', type=str, required=False, help='Appstream file to extract metadata from')
parser.add_argument('--desktop', type=str, required=False, help='Desktop file to extract additional metadata from')
parser.add_argument('--source', type=str, required=False, help='Source directory to find metadata in')
parser.add_argument('--output', type=str, required=True, help='Path to which the metadata output should be written to')
arguments = parser.parse_args()
# ensure the output path exists
os.makedirs(arguments.output, exist_ok=True)
# if we have an appstream file explicitly specified, let's use that one
if arguments.appstream and os.path.exists(arguments.appstream):
processAppstreamFile(arguments.appstream, arguments.desktop)
sys.exit(0)
# else, look in the source dir for appstream/desktop files
# this follows roughly what get-apk-args from binary factory does
if arguments.source and os.path.exists(arguments.source):
scanSourceDir()
sys.exit(0)
# else: missing arguments
print("Either one of --appstream or --source have to be provided!")
sys.exit(1)
@@ -0,0 +1,10 @@
execute_process(COMMAND nm --dynamic ${TARGET} ERROR_VARIABLE nm_errors OUTPUT_VARIABLE out RESULT_VARIABLE result)
if (NOT result EQUAL 0)
message(FATAL_ERROR "nm failed on ${TARGET} exit(${result}): ${nm_errors}")
endif()
string(FIND ${out} " T main\n" found)
if(found LESS 0)
message(FATAL_ERROR "Could not find a main() symbol on ${TARGET}")
endif()
@@ -0,0 +1,108 @@
function(list_dependencies target libs)
execute_process(COMMAND readelf --wide --dynamic ${target} ERROR_VARIABLE readelf_errors OUTPUT_VARIABLE out RESULT_VARIABLE result)
if (NOT result EQUAL 0)
message(FATAL_ERROR "readelf failed on ${target} exit(${result}): ${readelf_errors}")
endif()
string(REPLACE "\n" ";" lines "${out}")
set(extralibs ${${libs}})
foreach(line ${lines})
string(REGEX MATCH ".*\\(NEEDED\\) +Shared library: +\\[(.+)\\]$" matched ${line})
set(currentLib ${CMAKE_MATCH_1})
if(NOT ${currentLib} MATCHES "libQt5.*" AND matched)
find_file(ourlib-${currentLib} ${currentLib} HINTS ${OUTPUT_DIR} ${EXPORT_DIR} ${ECM_ADDITIONAL_FIND_ROOT_PATH} NO_DEFAULT_PATH PATH_SUFFIXES lib)
if(ourlib-${currentLib})
list(APPEND extralibs "${ourlib-${currentLib}}")
else()
message(STATUS "could not find ${currentLib} in ${OUTPUT_DIR} ${EXPORT_DIR} ${ECM_ADDITIONAL_FIND_ROOT_PATH}")
endif()
endif()
endforeach()
set(${libs} ${extralibs} PARENT_SCOPE)
endfunction()
list_dependencies(${TARGET} extralibs)
function(contains_library libpath IS_EQUAL)
get_filename_component (name ${libpath} NAME)
unset (IS_EQUAL PARENT_SCOPE)
foreach (extralib ${extralibs})
get_filename_component (extralibname ${extralib} NAME)
if (${extralibname} STREQUAL ${name})
set (IS_EQUAL TRUE PARENT_SCOPE)
break()
endif()
endforeach()
endfunction()
if (ANDROID_EXTRA_LIBS)
foreach (extralib ${ANDROID_EXTRA_LIBS})
contains_library(${extralib} IS_EQUAL)
if (IS_EQUAL)
message (STATUS "found duplicate, skipping: " ${extralib})
else()
message(STATUS "manually specified extra library: " ${extralib})
list(APPEND extralibs ${extralib})
endif()
endforeach()
endif()
set(extraplugins)
foreach(folder "plugins" "share" "lib/qml" "translations") #now we check for folders with extra stuff
set(plugin "${EXPORT_DIR}/${folder}")
if(EXISTS "${plugin}")
list(APPEND extraplugins "${plugin}")
endif()
endforeach()
if(EXISTS "module-plugins")
file(READ "module-plugins" moduleplugins)
foreach(module ${moduleplugins})
list_dependencies(${module} extralibs)
endforeach()
list(REMOVE_DUPLICATES extralibs)
endif()
if(extralibs)
string(REPLACE ";" "," extralibs "${extralibs}")
set(extralibs "\"android-extra-libs\": \"${extralibs}\",")
endif()
if(extraplugins)
string(REPLACE ";" "," extraplugins "${extraplugins}")
set(extraplugins "\"android-extra-plugins\": \"${extraplugins}\",")
endif()
file(READ "${INPUT_FILE}" CONTENTS)
if(EXISTS "stl") # only provided for legacy pre-Clang toolchains
file(READ "stl" stl_contents)
endif()
file(READ "ranlib" ranlib_contents)
string(REGEX MATCH ".+/toolchains/llvm/prebuilt/.+/bin/(.+)-ranlib" USE_LLVM ${ranlib_contents})
if (USE_LLVM)
string(REPLACE "##ANDROID_TOOL_PREFIX##" "llvm" NEWCONTENTS "${CONTENTS}")
string(REPLACE "##ANDROID_COMPILER_PREFIX##" "${CMAKE_MATCH_1}" NEWCONTENTS "${NEWCONTENTS}")
string(REPLACE "##USE_LLVM##" true NEWCONTENTS "${NEWCONTENTS}")
else()
string(REGEX MATCH ".+/toolchains/(.+)-([^\\-]+)/prebuilt/.+/bin/(.+)-ranlib" RANLIB_PATH_MATCH ${ranlib_contents})
if (NOT RANLIB_PATH_MATCH)
message(FATAL_ERROR "Couldn't parse the components of the path to ${ranlib_contents}")
endif()
string(REPLACE "##ANDROID_TOOL_PREFIX##" "${CMAKE_MATCH_1}" NEWCONTENTS "${CONTENTS}")
string(REPLACE "##ANDROID_COMPILER_PREFIX##" "${CMAKE_MATCH_3}" NEWCONTENTS "${NEWCONTENTS}")
string(REPLACE "##USE_LLVM##" false NEWCONTENTS "${NEWCONTENTS}")
endif()
string(REPLACE "##ANDROID_TOOLCHAIN_VERSION##" "${CMAKE_MATCH_2}" NEWCONTENTS "${NEWCONTENTS}") # not used when USE_LLVM is set
string(REPLACE "##EXTRALIBS##" "${extralibs}" NEWCONTENTS "${NEWCONTENTS}")
string(REPLACE "##EXTRAPLUGINS##" "${extraplugins}" NEWCONTENTS "${NEWCONTENTS}")
string(REPLACE "##CMAKE_CXX_STANDARD_LIBRARIES##" "${stl_contents}" NEWCONTENTS "${NEWCONTENTS}")
file(WRITE "${OUTPUT_FILE}" ${NEWCONTENTS})