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:
@@ -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()
|
||||
+20
@@ -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@" }
|
||||
}
|
||||
+28
@@ -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@" }
|
||||
}
|
||||
+2
@@ -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##
|
||||
}
|
||||
+528
@@ -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})
|
||||
Reference in New Issue
Block a user