diff --git a/CMakeLists.txt b/CMakeLists.txt index 99490f742a6..80aa59e57c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -702,7 +702,11 @@ message(" bench_bitcoin ....................... ${BUILD_BENCH}") message(" fuzz binary ......................... ${BUILD_FUZZ_BINARY}") message("") if(CMAKE_CROSSCOMPILING) - set(cross_status "TRUE, for ${CMAKE_SYSTEM_NAME}, ${CMAKE_SYSTEM_PROCESSOR}") + if(ANDROID) + set(cross_status "TRUE, for ${CMAKE_SYSTEM_NAME}, ${CMAKE_ANDROID_ARCH_ABI}") + else() + set(cross_status "TRUE, for ${CMAKE_SYSTEM_NAME}, ${CMAKE_SYSTEM_PROCESSOR}") + endif() else() set(cross_status "FALSE") endif() diff --git a/depends/Makefile b/depends/Makefile index 9eb00a425a5..103d843f98b 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -97,6 +97,10 @@ host_os+=$(findstring netbsd,$(full_host_os)) host_os+=$(findstring openbsd,$(full_host_os)) host_os+=$(findstring mingw32,$(full_host_os)) +ifeq (android,$(findstring android,$(full_host_os))) +host_os:=android +endif + host_os:=$(strip $(host_os)) ifeq ($(host_os),) host_os=$(full_host_os) @@ -235,6 +239,10 @@ $(host_prefix)/toolchain.cmake : toolchain.cmake.in $(host_prefix)/.stamp_$(fina -e 's|@bdb_packages@|$(bdb_packages_)|' \ -e 's|@usdt_packages@|$(usdt_packages_)|' \ -e 's|@multiprocess@|$(MULTIPROCESS)|' \ + -e 's|@android_abi@|$(android_abi)|' \ + -e 's|@android_ndk@|$(ANDROID_NDK)|' \ + -e 's|@android_toolchain_bin_dir@|$(android_toolchain_bin_dir)|' \ + -e 's|@android_libcxx_shared_dir@|$(android_libcxx_shared_dir)|' \ $< > $@ touch $@ diff --git a/depends/README.md b/depends/README.md index ab1df59a884..c60b65bc849 100644 --- a/depends/README.md +++ b/depends/README.md @@ -34,8 +34,11 @@ Common `host-platform-triplet`s for cross compilation are: - `riscv32-linux-gnu` for Linux RISC-V 32 bit - `riscv64-linux-gnu` for Linux RISC-V 64 bit - `s390x-linux-gnu` for Linux S390X +- `armv7a-linux-android` for Android ARM 32 bit +- `aarch64-linux-android` for Android ARM 64 bit +- `x86_64-linux-android` for Android x86 64 bit -The paths are automatically configured and no other options are needed. +The paths are automatically configured and no other options are needed unless targeting [Android](../doc/build-android.md). ### Install the required dependencies: Ubuntu & Debian diff --git a/depends/funcs.mk b/depends/funcs.mk index b499b6a78fe..466b761972c 100644 --- a/depends/funcs.mk +++ b/depends/funcs.mk @@ -207,33 +207,45 @@ ifneq ($($(1)_ldflags),) $(1)_autoconf += LDFLAGS="$$($(1)_ldflags)" endif +$(1)_cmake = env CFLAGS="$$($(1)_cppflags) $$($(1)_cflags)" \ + CXXFLAGS="$$($(1)_cppflags) $$($(1)_cxxflags)" \ + LDFLAGS="$$($(1)_ldflags)" +ifeq ($(host_os),android) + # For Android, we use the toolchain file provided by the Android SDK. + # See: https://developer.android.com/ndk/guides/cmake + $(1)_cmake += cmake \ + -DANDROID_ABI=$(android_abi) \ + -DANDROID_PLATFORM=android-$(android_cmake_system_version) \ + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake +else + $(1)_cmake += CC="$$($(1)_cc)" \ + CXX="$$($(1)_cxx)" + $(1)_cmake += cmake \ + -DCMAKE_AR=`which $$($(1)_ar)` \ + -DCMAKE_NM=`which $$($FILEPATH(1)_nm)` \ + -DCMAKE_RANLIB=`which $$($(1)_ranlib)` +endif +$(1)_cmake += -G "Unix Makefiles" \ + -DCMAKE_INSTALL_PREFIX:PATH="$$($($(1)_type)_prefix)" \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_VERBOSE_MAKEFILE:BOOL=$(V) \ + $$($(1)_config_opts) # We hardcode the library install path to "lib" to match the PKG_CONFIG_PATH # setting in depends/toolchain.cmake.in, which also hardcodes "lib". # Without this setting, CMake by default would use the OS library # directory, which might be "lib64" or something else, not "lib", on multiarch systems. -$(1)_cmake=env CC="$$($(1)_cc)" \ - CFLAGS="$$($(1)_cppflags) $$($(1)_cflags)" \ - CXX="$$($(1)_cxx)" \ - CXXFLAGS="$$($(1)_cppflags) $$($(1)_cxxflags)" \ - LDFLAGS="$$($(1)_ldflags)" \ - cmake -G "Unix Makefiles" \ - -DCMAKE_INSTALL_PREFIX:PATH="$$($($(1)_type)_prefix)" \ - -DCMAKE_AR=`which $$($(1)_ar)` \ - -DCMAKE_NM=`which $$($(1)_nm)` \ - -DCMAKE_RANLIB=`which $$($(1)_ranlib)` \ - -DCMAKE_INSTALL_LIBDIR=lib/ \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ - -DCMAKE_VERBOSE_MAKEFILE:BOOL=$(V) \ - $$($(1)_config_opts) +$(1)_cmake += -DCMAKE_INSTALL_LIBDIR=lib/ ifeq ($($(1)_type),build) $(1)_cmake += -DCMAKE_INSTALL_RPATH:PATH="$$($($(1)_type)_prefix)/lib" else ifneq ($(host),$(build)) +ifneq ($(host_os),android) $(1)_cmake += -DCMAKE_SYSTEM_NAME=$($(host_os)_cmake_system_name) $(1)_cmake += -DCMAKE_C_COMPILER_TARGET=$(host) $(1)_cmake += -DCMAKE_CXX_COMPILER_TARGET=$(host) endif endif +endif endef define int_add_cmds diff --git a/depends/hosts/android.mk b/depends/hosts/android.mk new file mode 100644 index 00000000000..f04b344b145 --- /dev/null +++ b/depends/hosts/android.mk @@ -0,0 +1,31 @@ +ANDROID_API_LEVEL ?= 26 + +android_toolchain_bin_dir := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(build_os)-$(build_arch)/bin + +ifeq ($(HOST),armv7a-linux-android) +android_CXX := $(android_toolchain_bin_dir)/$(HOST)eabi$(ANDROID_API_LEVEL)-clang++ +android_CC := $(android_toolchain_bin_dir)/$(HOST)eabi$(ANDROID_API_LEVEL)-clang +android_libcxx_shared_dir := arm-linux-androideabi +else +android_CXX := $(android_toolchain_bin_dir)/$(HOST)$(ANDROID_API_LEVEL)-clang++ +android_CC := $(android_toolchain_bin_dir)/$(HOST)$(ANDROID_API_LEVEL)-clang +android_libcxx_shared_dir := $(host_arch)-linux-android +endif + +android_CXXFLAGS := -std=$(CXX_STANDARD) +android_CFLAGS := -std=$(C_STANDARD) + +android_AR := $(android_toolchain_bin_dir)/llvm-ar +android_RANLIB := $(android_toolchain_bin_dir)/llvm-ranlib + +android_cmake_system_name := Android +android_cmake_system_version := $(ANDROID_API_LEVEL) + +# https://developer.android.com/ndk/guides/abis +ifeq ($(host_arch),armv7a) + android_abi := armeabi-v7a +else ifeq ($(host_arch),aarch64) + android_abi := arm64-v8a +else ifeq ($(host_arch),x86_64) + android_abi := x86_64 +endif diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk index dadd3b1b75d..4f01dd3199a 100644 --- a/depends/packages/libevent.mk +++ b/depends/packages/libevent.mk @@ -4,6 +4,7 @@ $(package)_download_path=https://github.com/libevent/libevent/releases/download/ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb $(package)_patches=cmake_fixups.patch +$(package)_patches += android_threads_fixup.patch $(package)_patches += netbsd_fixup.patch $(package)_build_subdir=build @@ -21,6 +22,7 @@ endef define $(package)_preprocess_cmds patch -p1 < $($(package)_patch_dir)/cmake_fixups.patch && \ + patch -p1 < $($(package)_patch_dir)/android_threads_fixup.patch && \ patch -p1 < $($(package)_patch_dir)/netbsd_fixup.patch endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index dddf8339638..158ca89b136 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -5,10 +5,12 @@ boost_packages = boost libevent_packages = libevent qrencode_linux_packages = qrencode +qrencode_android_packages = qrencode qrencode_darwin_packages = qrencode qrencode_mingw32_packages = qrencode qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm +qt_android_packages=qt qt_darwin_packages=qt qt_mingw32_packages=qt ifneq ($(host),$(build)) diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 56cb002e225..76c0d519df9 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -10,6 +10,8 @@ endif $(package)_linux_dependencies=freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm $(package)_patches_path := $(qt_details_patches_path) $(package)_patches := dont_hardcode_pwd.patch +$(package)_patches += fix_android_duplicate_symbol.patch +$(package)_patches += fix_android_jni_static.patch $(package)_patches += qtbase-moc-ignore-gcc-macro.patch $(package)_patches += qtbase_avoid_native_float16.patch $(package)_patches += qtbase_avoid_qmain.patch @@ -154,6 +156,16 @@ ifneq ($(LTO),) $(package)_config_opts_mingw32 += -ltcg endif +$(package)_config_opts_android = -android-sdk $(ANDROID_SDK) +$(package)_config_opts_android += -android-ndk $(ANDROID_NDK) +$(package)_config_opts_android += -android-ndk-platform android-$(ANDROID_API_LEVEL) +$(package)_config_opts_android += -android-abis $(android_abi) +$(package)_config_opts_android += -egl +$(package)_config_opts_android += -no-dbus +$(package)_config_opts_android += -no-fontconfig +$(package)_config_opts_android += -opengl es2 +$(package)_config_opts_android += -qt-freetype + # CMake build options. $(package)_config_env := CC="$$($(package)_cc)" $(package)_config_env += CXX="$$($(package)_cxx)" @@ -257,6 +269,8 @@ endif define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \ + patch -p1 -i $($(package)_patch_dir)/fix_android_duplicate_symbol.patch && \ + patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase_avoid_native_float16.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase_avoid_qmain.patch && \ @@ -281,6 +295,9 @@ endef define $(package)_stage_cmds cmake --install . --prefix $($(package)_staging_prefix_dir) endef +ifeq ($(host_os),android) + $(package)_stage_cmds += && cp -r $($(package)_extract_dir)/qtbase/src/android/jar/src $($(package)_staging_prefix_dir)/src/android/java +endif define $(package)_postprocess_cmds rm -rf doc/ diff --git a/depends/patches/libevent/android_threads_fixup.patch b/depends/patches/libevent/android_threads_fixup.patch new file mode 100644 index 00000000000..0ca81ef5f16 --- /dev/null +++ b/depends/patches/libevent/android_threads_fixup.patch @@ -0,0 +1,21 @@ +cmake: Fix Android build. +Android/Bionic C library needs no special flags to have threading support. +Found when trying to build with vcpkg. + +See https://github.com/libevent/libevent/commit/8f47d8de281b877450474734594fdc0a60ee35d1. + + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -858,6 +858,11 @@ endif() + if (NOT EVENT__DISABLE_THREAD_SUPPORT) + if (WIN32) + list(APPEND SRC_CORE evthread_win32.c) ++ elseif(ANDROID) ++ # pthreads is built in to bionic ++ set(EVENT__HAVE_PTHREADS 1) ++ CHECK_TYPE_SIZE(pthread_t EVENT__SIZEOF_PTHREAD_T) ++ list(APPEND SYMBOLS_TO_CHECK pthread_mutexattr_setprotocol) + else() + find_package(Threads REQUIRED) + if (NOT CMAKE_USE_PTHREADS_INIT) diff --git a/depends/patches/qt/fix_android_duplicate_symbol.patch b/depends/patches/qt/fix_android_duplicate_symbol.patch new file mode 100644 index 00000000000..09420138aa6 --- /dev/null +++ b/depends/patches/qt/fix_android_duplicate_symbol.patch @@ -0,0 +1,157 @@ +Fix "duplicate symbol: JNI_OnLoad" linker error + +Qt does not officially support static linking on Android. See: + - https://bugreports.qt.io/browse/QTBUG-36097 + - https://bugreports.qt.io/browse/QTBUG-102326 + +Reverting upstream commit 175e3ac8fabc864ffcc23c7769eaf7f220bddf59 +avoids the linker error. + +Note: the revert is not clean and required minor adjustments. + + +--- a/qtbase/src/corelib/CMakeLists.txt ++++ b/qtbase/src/corelib/CMakeLists.txt +@@ -1016,6 +1016,7 @@ qt_internal_extend_target(Core CONDITION ANDROID + kernel/qjnienvironment.cpp kernel/qjnienvironment.h + kernel/qjniobject.cpp kernel/qjniobject.h + kernel/qjnihelpers.cpp kernel/qjnihelpers_p.h ++ kernel/qjnionload.cpp + platform/android/qandroidextras_p.h platform/android/qandroidextras.cpp + platform/android/qandroidnativeinterface.cpp + NO_UNITY_BUILD_SOURCES + + +--- a/qtbase/src/corelib/kernel/qjnihelpers.cpp ++++ b/qtbase/src/corelib/kernel/qjnihelpers.cpp +@@ -12,7 +12,6 @@ + #include + #include + +-#include + #include + #include + +@@ -349,38 +348,3 @@ void QtAndroidPrivate::releaseAndroidDeadlockProtector() + } + + QT_END_NAMESPACE +- +-JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +-{ +- Q_UNUSED(reserved); +- +- static const char logTag[] = "QtCore"; +- static bool initialized = false; +- if (initialized) +- return JNI_VERSION_1_6; +- initialized = true; +- +- typedef union { +- JNIEnv *nenv; +- void *venv; +- } _JNIEnv; +- +- __android_log_print(ANDROID_LOG_INFO, logTag, "Start"); +- +- _JNIEnv uenv; +- uenv.venv = nullptr; +- +- if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) { +- __android_log_print(ANDROID_LOG_FATAL, logTag, "GetEnv failed"); +- return JNI_ERR; +- } +- +- JNIEnv *env = uenv.nenv; +- const jint ret = QT_PREPEND_NAMESPACE(QtAndroidPrivate::initJNI(vm, env)); +- if (ret != 0) { +- __android_log_print(ANDROID_LOG_FATAL, logTag, "initJNI failed"); +- return ret; +- } +- +- return JNI_VERSION_1_6; +-} + + +--- /dev/null ++++ b/qtbase/src/corelib/kernel/qjnionload.cpp +@@ -0,0 +1,79 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2021 The Qt Company Ltd. ++** Contact: https://www.qt.io/licensing/ ++** ++** This file is part of the QtCore module of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:LGPL$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and The Qt Company. For licensing terms ++** and conditions see https://www.qt.io/terms-conditions. For further ++** information use the contact form at https://www.qt.io/contact-us. ++** ++** GNU Lesser General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU Lesser ++** General Public License version 3 as published by the Free Software ++** Foundation and appearing in the file LICENSE.LGPL3 included in the ++** packaging of this file. Please review the following information to ++** ensure the GNU Lesser General Public License version 3 requirements ++** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 2.0 or (at your option) the GNU General ++** Public license version 3 or any later version approved by the KDE Free ++** Qt Foundation. The licenses are as published by the Free Software ++** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ++** included in the packaging of this file. Please review the following ++** information to ensure the GNU General Public License requirements will ++** be met: https://www.gnu.org/licenses/gpl-2.0.html and ++** https://www.gnu.org/licenses/gpl-3.0.html. ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include "qjnihelpers_p.h" ++ ++#include ++#include ++ ++static const char logTag[] = "QtCore"; ++ ++Q_CORE_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) ++{ ++ Q_UNUSED(reserved); ++ ++ static bool initialized = false; ++ if (initialized) ++ return JNI_VERSION_1_6; ++ initialized = true; ++ ++ typedef union { ++ JNIEnv *nenv; ++ void *venv; ++ } _JNIEnv; ++ ++ __android_log_print(ANDROID_LOG_INFO, logTag, "Start"); ++ ++ _JNIEnv uenv; ++ uenv.venv = nullptr; ++ ++ if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) { ++ __android_log_print(ANDROID_LOG_FATAL, logTag, "GetEnv failed"); ++ return JNI_ERR; ++ } ++ ++ JNIEnv *env = uenv.nenv; ++ const jint ret = QT_PREPEND_NAMESPACE(QtAndroidPrivate::initJNI(vm, env)); ++ if (ret != 0) { ++ __android_log_print(ANDROID_LOG_FATAL, logTag, "initJNI failed"); ++ return ret; ++ } ++ ++ return JNI_VERSION_1_6; ++} diff --git a/depends/patches/qt/fix_android_jni_static.patch b/depends/patches/qt/fix_android_jni_static.patch new file mode 100644 index 00000000000..c9706a06fd6 --- /dev/null +++ b/depends/patches/qt/fix_android_jni_static.patch @@ -0,0 +1,16 @@ +--- a/qtbase/src/plugins/platforms/android/androidjnimain.cpp ++++ b/qtbase/src/plugins/platforms/android/androidjnimain.cpp +@@ -895,6 +895,13 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) + __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); + return -1; + } ++ ++ const jint ret = QT_PREPEND_NAMESPACE(QtAndroidPrivate::initJNI(vm, QJniEnvironment::getJniEnv())); ++ if (ret != 0) { ++ __android_log_print(ANDROID_LOG_FATAL, "Qt", "initJNI failed"); ++ return ret; ++ } ++ + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + + // attach qt main thread data to this thread diff --git a/depends/toolchain.cmake.in b/depends/toolchain.cmake.in index d247a80c9cc..21ad616382d 100644 --- a/depends/toolchain.cmake.in +++ b/depends/toolchain.cmake.in @@ -2,21 +2,54 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or https://opensource.org/license/mit/. -# This file is expected to be highly volatile and may still change substantially. +if("@host_system_name@" STREQUAL "Android") + # For Android, we use the toolchain file provided by the Android SDK. + # See: https://developer.android.com/ndk/guides/cmake + set(ANDROID_ABI @android_abi@) + set(ANDROID_PLATFORM android-@host_system_version@) + include(@android_ndk@/build/cmake/android.toolchain.cmake) + # Qt's Java sources for Android Application Package (APK). + cmake_path(SET ANDROID_APK_SRC "${CMAKE_CURRENT_LIST_DIR}/src/android/java") + # The libc++_shared.so shared library, provided by the Android SDK, + # is required to build an Android Application Package (APK). + cmake_path(SET ANDROID_SHARED_CXX_LIB NORMALIZE "@android_toolchain_bin_dir@/../sysroot/usr/lib/@android_libcxx_shared_dir@/libc++_shared.so") + # Qt recommends using the same Gradle version that it comes with. + # Therefore, we use the Gradle wrapper provided by Qt to build + # an Android Application Package (APK). + # See: + # - https://doc.qt.io/qt-6/deployment-android.html + # - https://doc.qt.io/qt-6/qtcore-attribution-android-gradle-wrapper.html + cmake_path(SET GRADLEW_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/src/3rdparty/gradle/gradlew") +else() + # If CMAKE_SYSTEM_NAME is set within a toolchain file, CMake will also + # set CMAKE_CROSSCOMPILING to TRUE, even if CMAKE_SYSTEM_NAME matches + # CMAKE_HOST_SYSTEM_NAME. To avoid potential misconfiguration of CMake, + # it is best not to touch CMAKE_SYSTEM_NAME unless cross-compiling is + # intended. + if(@depends_crosscompiling@) + set(CMAKE_SYSTEM_NAME @host_system_name@) + set(CMAKE_SYSTEM_VERSION @host_system_version@) + set(CMAKE_SYSTEM_PROCESSOR @host_arch@) -# If CMAKE_SYSTEM_NAME is set within a toolchain file, CMake will also -# set CMAKE_CROSSCOMPILING to TRUE, even if CMAKE_SYSTEM_NAME matches -# CMAKE_HOST_SYSTEM_NAME. To avoid potential misconfiguration of CMake, -# it is best not to touch CMAKE_SYSTEM_NAME unless cross-compiling is -# intended. -if(@depends_crosscompiling@) - set(CMAKE_SYSTEM_NAME @host_system_name@) - set(CMAKE_SYSTEM_VERSION @host_system_version@) - set(CMAKE_SYSTEM_PROCESSOR @host_arch@) + set(CMAKE_C_COMPILER_TARGET @host@) + set(CMAKE_CXX_COMPILER_TARGET @host@) + set(CMAKE_OBJCXX_COMPILER_TARGET @host@) + endif() - set(CMAKE_C_COMPILER_TARGET @host@) - set(CMAKE_CXX_COMPILER_TARGET @host@) - set(CMAKE_OBJCXX_COMPILER_TARGET @host@) + if(NOT DEFINED CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER @CC@) + endif() + + if(NOT DEFINED CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER @CXX@) + set(CMAKE_OBJCXX_COMPILER ${CMAKE_CXX_COMPILER}) + endif() + + set(CMAKE_AR "@AR@") + set(CMAKE_RANLIB "@RANLIB@") + set(CMAKE_STRIP "@STRIP@") + set(CMAKE_OBJCOPY "@OBJCOPY@") + set(CMAKE_OBJDUMP "@OBJDUMP@") endif() if(NOT DEFINED CMAKE_C_FLAGS_INIT) @@ -29,10 +62,6 @@ if(NOT DEFINED CMAKE_C_FLAGS_DEBUG_INIT) set(CMAKE_C_FLAGS_DEBUG_INIT "@CFLAGS_DEBUG@") endif() -if(NOT DEFINED CMAKE_C_COMPILER) - set(CMAKE_C_COMPILER @CC@) -endif() - if(NOT DEFINED CMAKE_CXX_FLAGS_INIT) set(CMAKE_CXX_FLAGS_INIT "@CXXFLAGS@") set(CMAKE_OBJCXX_FLAGS_INIT "@CXXFLAGS@") @@ -46,11 +75,6 @@ if(NOT DEFINED CMAKE_CXX_FLAGS_DEBUG_INIT) set(CMAKE_OBJCXX_FLAGS_DEBUG_INIT "@CXXFLAGS_DEBUG@") endif() -if(NOT DEFINED CMAKE_CXX_COMPILER) - set(CMAKE_CXX_COMPILER @CXX@) - set(CMAKE_OBJCXX_COMPILER ${CMAKE_CXX_COMPILER}) -endif() - # The DEPENDS_COMPILE_DEFINITIONS* variables are to be treated as lists. set(DEPENDS_COMPILE_DEFINITIONS @CPPFLAGS@) set(DEPENDS_COMPILE_DEFINITIONS_RELWITHDEBINFO @CPPFLAGS_RELEASE@) @@ -75,12 +99,6 @@ if(NOT DEFINED CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT "@LDFLAGS_DEBUG@") endif() -set(CMAKE_AR "@AR@") -set(CMAKE_RANLIB "@RANLIB@") -set(CMAKE_STRIP "@STRIP@") -set(CMAKE_OBJCOPY "@OBJCOPY@") -set(CMAKE_OBJDUMP "@OBJDUMP@") - # Using our own built dependencies should not be # affected by a potentially random environment. set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF) diff --git a/doc/README.md b/doc/README.md index 79ca53ce763..7492bb3cc47 100644 --- a/doc/README.md +++ b/doc/README.md @@ -45,6 +45,7 @@ The following are developer notes on how to build Bitcoin Core on your native pl - [FreeBSD Build Notes](build-freebsd.md) - [OpenBSD Build Notes](build-openbsd.md) - [NetBSD Build Notes](build-netbsd.md) +- [Android Build Notes](build-android.md) Development --------------------- diff --git a/doc/build-android.md b/doc/build-android.md new file mode 100644 index 00000000000..2dc7e453f99 --- /dev/null +++ b/doc/build-android.md @@ -0,0 +1,44 @@ +# Android Build Guide + +This guide describes how to build and package the `bitcoin-qt` GUI for Android on Linux and macOS. + + +## Dependencies + +Before proceeding with an Android build one needs to get the [Android SDK](https://developer.android.com/studio) and use the "SDK Manager" tool to download the NDK and one or more "Platform packages" (these are Android versions and have a corresponding API level). + +Qt 6.7.3 used in depends supports Android NDK version is [r26b (26.1.10909125)](https://github.com/android/ndk/wiki/Changelog-r26). + +In order to build `ANDROID_API_LEVEL` (API level corresponding to the Android version targeted, e.g. Android 9.0 Pie is 28 and its "Platform package" needs to be available) needs to be set. + +Qt 6.7.3 used in depends supports API levels from 26 to 34. + +When building [depends](../depends/README.md), additional variables `ANDROID_SDK` and `ANDROID_NDK` need to be set. + +This is an example command for a default build with no disabled dependencies: + + gmake HOST=aarch64-linux-android ANDROID_SDK=/home/user/Android/Sdk ANDROID_NDK=/home/user/Android/Sdk/ndk/26.1.10909125 ANDROID_API_LEVEL=34 + + +## Building and packaging + +After the depends are built configure, build and create an Android Application Package (APK) as follows (based on the example above): + +```bash +cmake -B build --toolchain depends/aarch64-linux-android/toolchain.cmake +cmake --build build --target apk_package # Use "-j N" for N parallel jobs. +``` + +The APKs will be available in the following directory: +```bash +$ tree build/src/qt/android/build/outputs/apk +build/src/qt/android/build/outputs/apk +├── debug +│ ├── android-debug.apk +│ └── output-metadata.json +└── release + ├── android-release-unsigned.apk + └── output-metadata.json + +3 directories, 4 files +``` diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 7379a1f328e..71f783064cb 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -15,13 +15,18 @@ get_target_property(qt_lib_type Qt6::Core TYPE) function(import_plugins target) if(qt_lib_type STREQUAL "STATIC_LIBRARY") - set(plugins Qt6::QMinimalIntegrationPlugin) + set(plugins "") + if(TARGET Qt6::QMinimalIntegrationPlugin) + list(APPEND plugins Qt6::QMinimalIntegrationPlugin) + endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND plugins Qt6::QXcbIntegrationPlugin) elseif(WIN32) list(APPEND plugins Qt6::QWindowsIntegrationPlugin Qt6::QModernWindowsStylePlugin) elseif(APPLE) list(APPEND plugins Qt6::QCocoaIntegrationPlugin Qt6::QMacStylePlugin) + elseif(ANDROID) + list(APPEND plugins Qt6::QAndroidIntegrationPlugin Qt6::QAndroidStylePlugin) endif() qt6_import_plugins(${target} INCLUDE ${plugins} @@ -357,3 +362,7 @@ else() VERBATIM ) endif() + +if(ANDROID) + add_subdirectory(android) +endif() diff --git a/src/qt/android/AndroidManifest.xml b/src/qt/android/AndroidManifest.xml new file mode 100644 index 00000000000..c0bdef6cc93 --- /dev/null +++ b/src/qt/android/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qt/android/CMakeLists.txt b/src/qt/android/CMakeLists.txt new file mode 100644 index 00000000000..061c5cb8748 --- /dev/null +++ b/src/qt/android/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) 2025 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + PATTERN "CMakeLists.txt" EXCLUDE +) + +file(COPY ${ANDROID_APK_SRC}/src/org DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/src) + +file(COPY ${ANDROID_SHARED_CXX_LIB} DESTINATION libs/${CMAKE_ANDROID_ARCH_ABI}) + +cmake_path(RELATIVE_PATH GRADLEW_EXECUTABLE BASE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_target(apk_package + COMMAND ${CMAKE_COMMAND} -E copy $ libs/${CMAKE_ANDROID_ARCH_ABI}/libbitcoin-qt_${CMAKE_ANDROID_ARCH_ABI}.so + COMMAND ${GRADLEW_EXECUTABLE} build + VERBATIM +) diff --git a/src/qt/android/build.gradle b/src/qt/android/build.gradle new file mode 100644 index 00000000000..59e73532001 --- /dev/null +++ b/src/qt/android/build.gradle @@ -0,0 +1,51 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.0.2' + } +} + +repositories { + google() + mavenCentral() +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + namespace 'org.bitcoincore.qt' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qtAndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } + + defaultConfig { + minSdkVersion 26 + targetSdk 33 + } +} diff --git a/src/qt/android/gradle.properties b/src/qt/android/gradle.properties new file mode 100644 index 00000000000..92de68d00b8 --- /dev/null +++ b/src/qt/android/gradle.properties @@ -0,0 +1,4 @@ +androidBuildToolsVersion=33.0.3 +androidCompileSdkVersion=33 +qtAndroidDir=new File(".").absolutePath +org.gradle.jvmargs=-Xmx4608M diff --git a/src/qt/android/res/drawable-hdpi/bitcoin.png b/src/qt/android/res/drawable-hdpi/bitcoin.png new file mode 100644 index 00000000000..31a556a35f2 Binary files /dev/null and b/src/qt/android/res/drawable-hdpi/bitcoin.png differ diff --git a/src/qt/android/res/drawable-ldpi/bitcoin.png b/src/qt/android/res/drawable-ldpi/bitcoin.png new file mode 100644 index 00000000000..76d80d4196c Binary files /dev/null and b/src/qt/android/res/drawable-ldpi/bitcoin.png differ diff --git a/src/qt/android/res/drawable-mdpi/bitcoin.png b/src/qt/android/res/drawable-mdpi/bitcoin.png new file mode 100644 index 00000000000..c2aeab851a8 Binary files /dev/null and b/src/qt/android/res/drawable-mdpi/bitcoin.png differ diff --git a/src/qt/android/res/drawable-xhdpi/bitcoin.png b/src/qt/android/res/drawable-xhdpi/bitcoin.png new file mode 100644 index 00000000000..2bd5e3defc7 Binary files /dev/null and b/src/qt/android/res/drawable-xhdpi/bitcoin.png differ diff --git a/src/qt/android/res/drawable-xxhdpi/bitcoin.png b/src/qt/android/res/drawable-xxhdpi/bitcoin.png new file mode 100644 index 00000000000..d236cf2132a Binary files /dev/null and b/src/qt/android/res/drawable-xxhdpi/bitcoin.png differ diff --git a/src/qt/android/res/drawable-xxxhdpi/bitcoin.png b/src/qt/android/res/drawable-xxxhdpi/bitcoin.png new file mode 100644 index 00000000000..bb1dbc35546 Binary files /dev/null and b/src/qt/android/res/drawable-xxxhdpi/bitcoin.png differ diff --git a/src/qt/android/res/values/libs.xml b/src/qt/android/res/values/libs.xml new file mode 100644 index 00000000000..b4b77b1c7b8 --- /dev/null +++ b/src/qt/android/res/values/libs.xml @@ -0,0 +1,14 @@ + + + + + arm64-v8a;libbitcoin-qt_arm64-v8a.so + + + armeabi-v7a;libbitcoin-qt_armeabi-v7a.so + + + x86_64;libbitcoin-qt_x86_64.so + + + diff --git a/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java b/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java new file mode 100644 index 00000000000..c8120b69740 --- /dev/null +++ b/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java @@ -0,0 +1,23 @@ +package org.bitcoincore.qt; + +import android.os.Bundle; +import android.system.ErrnoException; +import android.system.Os; + +import org.qtproject.qt.android.bindings.QtActivity; + +import java.io.File; + +public class BitcoinQtActivity extends QtActivity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + final File bitcoinDir = new File(getFilesDir().getAbsolutePath() + "/.bitcoin"); + if (!bitcoinDir.exists()) { + bitcoinDir.mkdir(); + } + + super.onCreate(savedInstanceState); + } +}