From 32a889c543865665dad088f73f0efec64d224329 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Mon, 10 Jan 2022 15:40:22 +0100 Subject: [PATCH] More work to ship libretroshare as standalone lib Improved CMake support Initial work on packaging libretroshare as an Android library AAR Deal properly with Android API level which miss largefile support instead of relying on build system trickery --- .gitattributes | 6 + .gitignore | 8 + CMakeLists.txt | 56 +- build.gradle | 226 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++ gradlew.bat | 89 ++ misc/Android/Dockerfile | 117 +++ misc/Android/README.asciidoc | 400 ++++++++ misc/Android/generate_gdb_init_commands.sh | 85 ++ misc/Android/prepare-toolchain-clang.sh | 920 ++++++++++++++++++ misc/Android/pull_sysroot.sh | 90 ++ misc/Android/start_gdbserver.sh | 116 +++ settings.gradle | 10 + src/CMakeLists.txt | 24 +- src/ft/ftdatamultiplex.cc | 4 +- src/ft/ftfilecreator.cc | 21 +- src/ft/ftfileprovider.cc | 17 +- src/libretroshare.pro | 9 +- src/rs_android/AndroidManifest.xml | 2 + src/rs_android/largefile_retrocompat.hpp | 31 + .../service/RetroShareServiceAndroid.java | 7 +- src/util/rsdir.cc | 23 +- 24 files changed, 2391 insertions(+), 60 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 misc/Android/Dockerfile create mode 100644 misc/Android/README.asciidoc create mode 100755 misc/Android/generate_gdb_init_commands.sh create mode 100755 misc/Android/prepare-toolchain-clang.sh create mode 100755 misc/Android/pull_sysroot.sh create mode 100755 misc/Android/start_gdbserver.sh create mode 100644 settings.gradle create mode 100644 src/rs_android/AndroidManifest.xml create mode 100644 src/rs_android/largefile_retrocompat.hpp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..00a51aff5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..2ed2cdd3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Ignore Gradle local options +local.properties diff --git a/CMakeLists.txt b/CMakeLists.txt index 93c8b04a5..64dd17dd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,6 +126,11 @@ set( CACHE STRING "Path where to install RetroShare system wide data" ) +option( + RS_EXPORT_JNI_ONLOAD + "Export libretroshare JNI_OnLoad. See src/rs_android/rsjni.cpp for details" + ON ) + ################################################################################ find_package(Git REQUIRED) @@ -163,6 +168,9 @@ target_include_directories( ${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src ) +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +# TODO: install public headers find_package(OpenSSL REQUIRED) target_include_directories(${PROJECT_NAME} PRIVATE ${OPENSSL_INCLUDE_DIR}) @@ -199,6 +207,7 @@ if(RS_BITDHT) CHECK_PASS "BitDHT submodule found at ${BITDHT_DEVEL_DIR} using it" ) add_subdirectory(${BITDHT_DEVEL_DIR} ${CMAKE_BINARY_DIR}/bitdht) + set(RS_BITDHT_DIR "${BITDHT_DEVEL_DIR}") else() FetchContent_Declare( bitdht @@ -209,6 +218,8 @@ if(RS_BITDHT) TIMEOUT 10 ) FetchContent_MakeAvailable(bitdht) + + set(RS_BITDHT_DIR "${bitdht_SOURCE_DIR}") endif() add_compile_definitions(RS_USE_BITDHT) @@ -230,9 +241,6 @@ if(RS_JSON_API) find_package(Doxygen REQUIRED) find_package(Python3 REQUIRED) - ## TODO: execute at build time instead that at cofiguration time see - ## add_custom_command or add_custom_target - set( JSON_API_GENERATOR_WORK_DIR "${CMAKE_BINARY_DIR}/jsonapi-generator.workdir/" ) @@ -379,7 +387,7 @@ if(RS_BRODCAST_DISCOVERY) ) FetchContent_MakeAvailable(udp-discovery-cpp) - target_link_libraries(${PROJECT_NAME} PRIVATE udp-discovery-cpp) + target_link_libraries(${PROJECT_NAME} PRIVATE udp-discovery) ## TODO: Temporary work around target_include_directories should be added ## upstream @@ -407,5 +415,43 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(RS_DATA_DIR="${RS_DATA_DIR}") endif(CMAKE_SYSTEM_NAME STREQUAL "Linux") +################################################################################ + +if(RS_EXPORT_JNI_ONLOAD) + add_compile_definitions(RS_LIBRETROSHARE_EXPORT_JNI_ONLOAD) +endif(RS_EXPORT_JNI_ONLOAD) + +################################################################################ + +#if(CMAKE_SYSTEM_NAME STREQUAL "Android") +if(RS_ANDROID) + FetchContent_Declare( + jni-hpp + GIT_REPOSITORY "https://gitlab.com/RetroShare/jni-hpp.git" + GIT_TAG "origin/master" + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + TIMEOUT 10 + ) + FetchContent_MakeAvailable(jni-hpp) + + include_directories(${jni-hpp_SOURCE_DIR}/include) + + if(RS_BITDHT) + set(RS_ANDROID_ASSETS_DIR "${CMAKE_BINARY_DIR}/android-assets") + set(RS_ANDROID_VALUES_DIR "${RS_ANDROID_ASSETS_DIR}/values") + file(MAKE_DIRECTORY "${RS_ANDROID_VALUES_DIR}") + file( + COPY "${RS_BITDHT_DIR}/src/bitdht/bdboot.txt" + DESTINATION "${RS_ANDROID_VALUES_DIR}" ) + set( + ANDROID_ASSETS_DIRECTORIES + "${RS_ANDROID_ASSETS_DIR};${ANDROID_ASSETS_DIRECTORIES}" ) + endif(RS_BITDHT) +endif(RS_ANDROID) +#endif(CMAKE_SYSTEM_NAME STREQUAL "Android") + +################################################################################ + ## Useful to debug CMake -#set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..69eeb1d02 --- /dev/null +++ b/build.gradle @@ -0,0 +1,226 @@ +/* + * libretroshare Android AAR library gradle builder + * + * Copyright (C) 2022 Gioacchino Mazzurco + * Copyright (C) 2022 Asociación Civil Altermundi + * + * SPDX-License-Identifier: CC0-1.0 + */ + +buildscript +{ + repositories + { + // The order in which you list these repositories matter. + google() + mavenCentral() + } + + dependencies + { + classpath 'com.android.tools.build:gradle:7.1.+' + } +} + +allprojects +{ + repositories + { + // The order in which you list these repositories matter. + google() + mavenCentral() + } +} + +ext.buildLibretroshareNativeLib = +{ + pApiLevel, /* Android API level */ + pAbi, /* Arch name as seen in AAR native libraries directories */ + pNdkPath, /* Android NDK path */ + pReuseToolchain = true /* If true reuse previously built toochain */ -> + + /* Convert pAbi into corresponding prepare toolchain ANDROID_NDK_ARCH */ + def toolchainArch = "unsupported" + def libcxxsharedTriple = "unsupported" + switch(pAbi) + { + case "armeabi-v7a": + toolchainArch = "arm"; + libcxxsharedTriple = "arm-linux-androideabi"; + break; + case "arm64-v8a": + toolchainArch = "arm64"; + libcxxsharedTriple = "aarch64-linux-android"; + break; + default: + throw new GradleException( + "buildLibretroshareNativeLib unsupported pAbi: $pAbi" ); + break; + } + + def toolchainsWorkdir = "${buildDir}/native_toolchains/" + mkdir toolchainsWorkdir + + def currToolchainPath = "$toolchainsWorkdir/$pApiLevel-$toolchainArch/" + + // Todo: use proper way to resolve the script path + def toolChainScriptPath = "${projectDir}/../build_scripts/Android/prepare-toolchain-clang.sh" + + if(!pReuseToolchain || !file(currToolchainPath).exists()) + { + exec + { + workingDir toolchainsWorkdir + environment "ANDROID_NDK_PATH", pNdkPath + environment "NATIVE_LIBS_TOOLCHAIN_PATH", currToolchainPath + environment "ANDROID_PLATFORM_VER", pApiLevel + environment "ANDROID_NDK_ARCH", toolchainArch + commandLine toolChainScriptPath /*, 'build_libretroshare'*/ + } + } + else + {/* + exec + { + workingDir toolchainsWorkdir + environment "ANDROID_NDK_PATH", pNdkPath + environment "NATIVE_LIBS_TOOLCHAIN_PATH", currToolchainPath + environment "ANDROID_PLATFORM_VER", pApiLevel + environment "ANDROID_NDK_ARCH", toolchainArch + commandLine toolChainScriptPath, 'build_libretroshare' + }*/ + } + + def nativeLibsDir = "${buildDir}/native_libs-$pApiLevel/" + mkdir nativeLibsDir + + def currAbiLibDir = "${nativeLibsDir}/$pAbi/" + mkdir currAbiLibDir + copy + { + from "${currToolchainPath}/sysroot/usr/lib/libretroshare.so" + into currAbiLibDir + } + + copy + { + from "${currToolchainPath}/sysroot/usr/lib/${libcxxsharedTriple}/libc++_shared.so" + into currAbiLibDir + } +} + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' + +android +{ + // see https://stackoverflow.com/questions/27301867/what-is-compilesdkversion + compileSdkVersion 21 + + sourceSets + { + main + { + java.srcDirs = [ 'src/rs_android/' ] + manifest.srcFile 'src/rs_android/AndroidManifest.xml' + assets.srcDirs = [ "${buildDir}/libretroshare-build/android-assets/" ] + jniLibs.srcDirs = [ "${buildDir}/native_libs-16" ] + } + + minApi16 + { + jniLibs.srcDirs = [ "${buildDir}/native_libs-16" ] + } + + minApi21 + { + jniLibs.srcDirs = [ "${buildDir}/native_libs-21" ] + } + + minApi24 + { + jniLibs.srcDirs = [ "${buildDir}/native_libs-24" ] + } + } + + lintOptions + { + disable 'LongLogTag' + } + + flavorDimensions 'api' + + def currNdk = android.getNdkDirectory().getAbsolutePath() + + productFlavors + { + minApi16 + { + minSdkVersion '16' + targetSdkVersion '16' + + def currApi = minSdkVersion.mApiLevel + versionNameSuffix "-API_${currApi}" + + buildLibretroshareNativeLib(currApi, "armeabi-v7a", currNdk) + } + + minApi21 + { + minSdkVersion '21' + targetSdkVersion '21' + + def currApi = minSdkVersion.mApiLevel + versionNameSuffix "-API_${currApi}" + + buildLibretroshareNativeLib(currApi, "arm64-v8a", currNdk) + buildLibretroshareNativeLib(currApi, "armeabi-v7a", currNdk) + } + + minApi24 + { + minSdkVersion '24' + targetSdkVersion '28' + + def currApi = minSdkVersion.mApiLevel + versionNameSuffix "-API_${currApi}" + + buildLibretroshareNativeLib(currApi, "arm64-v8a", currNdk) + buildLibretroshareNativeLib(currApi, "armeabi-v7a", currNdk) + } + } + + publishing + { + multipleVariants + { + allVariants() + } + } +} + +afterEvaluate +{ + publishing + { + // see https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/dsl/LibraryPublishing + publications + { + android.libraryVariants.each + { + variant -> + + publishing.publications.create(variant.name, MavenPublication) + { + from components.findByName(variant.name) + groupId = 'org.retroshare.service' + // -${variant.flavorName} + artifactId "${rootProject.name}-${variant.name}" + version "0.6.6" + + artifacts = [ "${buildDir}/outputs/aar/${rootProject.name}-${variant.flavorName}-${variant.buildType.name}.aar" ] + } + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ffed3a254 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..744e882ed --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..107acd32c --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/misc/Android/Dockerfile b/misc/Android/Dockerfile new file mode 100644 index 000000000..490a247c9 --- /dev/null +++ b/misc/Android/Dockerfile @@ -0,0 +1,117 @@ +## To prepare an image suitable as base for Gitlab CI use +## image name must match gitlab repository name, you can play just with the tag +## the part after : +# export CI_IMAGE_NAME="registry.gitlab.com/retroshare/retroshare:android_arm_base" +# docker build --squash --tag "${CI_IMAGE_NAME}" \ +# --build-arg QT_INSTALLER_JWT_TOKEN="your qt JWT token goes here" . +# +# To build Android ARMv8 (64 bit) package pass also +# export CI_IMAGE_NAME="registry.gitlab.com/retroshare/retroshare:android_arm64_base" +# --build-arg ANDROID_NDK_ARCH=arm64 --build-arg ANDROID_PLATFORM_VER=21 + +## --squash is very important in case of GitlabCI shared runners as they are +## limited to 25GB disk size + +## To push it to gitlab CI registry you need first to login and the to push +# docker login registry.gitlab.com +# docker push ${CI_IMAGE_NAME} + +## To extract the generated APK easily you can run after the build complete +# docker cp \ +# $(docker create --rm ${CI_IMAGE_NAME}):/retroshare-service-android-build/android-build/build/outputs/apk/debug/android-build-debug.apk \ +# /tmp/RetroShare_Android_Service.apk + + +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV APT_UNAT="--assume-yes --quiet" + +RUN apt-get update $APT_UNAT && apt-get upgrade --show-upgraded $APT_UNAT && \ + apt-get clean $APT_UNAT +RUN apt-get install --no-install-recommends $APT_UNAT \ + bash build-essential bzip2 cmake curl chrpath doxygen \ + git p7zip python qt5-default qttools5-dev tclsh unzip wget zip + +# Dependencies to create Android pkg +RUN apt-get install --no-install-recommends $APT_UNAT \ + openjdk-8-jre openjdk-8-jdk openjdk-8-jdk-headless gradle + +ARG FRESHCLONE=0 +ARG REPO_URL=https://gitlab.com/RetroShare/RetroShare.git +ARG REPO_BRANCH=master +ARG REPO_DEPTH="--depth 2000" + +RUN git clone $REPO_DEPTH $REPO_URL -b $REPO_BRANCH && cd RetroShare && \ + git fetch --tags + +ENV PREPARE_TOOLCHAIN="/RetroShare/build_scripts/Android/prepare-toolchain-clang.sh" +ENV NATIVE_LIBS_TOOLCHAIN_PATH="/android-toolchain/" + +ARG ANDROID_PLATFORM_VER=16 +ARG ANDROID_NDK_ARCH=arm + +ENV ANDROID_SDK_PATH="/opt/android-sdk" +ENV ANDROID_HOME="$ANDROID_SDK_PATH" +ENV ANDROID_SDK_ROOT="$ANDROID_SDK_PATH" + +ENV ANDROID_NDK_PATH="/opt/android-ndk" +ENV ANDROID_NDK_ROOT="$ANDROID_NDK_PATH" + +ENV PATH="$PATH:$ANDROID_HOME/tools" +ENV PATH="$PATH:$ANDROID_HOME/platform-tools" +ENV JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64/" + +RUN mkdir /bCache +WORKDIR /bCache + +## Quick HACK to ease development +#COPY prepare-toolchain-clang.sh $PREPARE_TOOLCHAIN + +RUN $PREPARE_TOOLCHAIN install_android_sdk +RUN $PREPARE_TOOLCHAIN install_android_ndk +RUN $PREPARE_TOOLCHAIN + +ARG QT_INSTALLER_JWT_TOKEN +RUN $PREPARE_TOOLCHAIN install_qt_android +# Avoid Qt account details leak into the image +RUN rm -f /root/.local/share/Qt/qtaccount.ini +# Shrink image by removing unneeded Qt components +RUN rm -r \ + $NATIVE_LIBS_TOOLCHAIN_PATH/Qt/Docs/ \ + $NATIVE_LIBS_TOOLCHAIN_PATH/Qt/Examples/ \ + $NATIVE_LIBS_TOOLCHAIN_PATH/Qt/Tools/ + +RUN mkdir /jsonapi-generator-build +WORKDIR /jsonapi-generator-build/ +RUN qmake ../RetroShare/jsonapi-generator/src/ \ + CONFIG+=no_retroshare_plugins \ + CONFIG+=no_retroshare_service CONFIG+=no_retroshare_gui \ + CONFIG+=rs_jsonapi CONFIG+=no_rs_sam3_libsam3 && \ + make -j$(nproc) + +RUN mkdir /retroshare-service-android-build +WORKDIR /retroshare-service-android-build +ARG RS_SERVICE_QMAKE_EXTRA_OPTS +RUN $($PREPARE_TOOLCHAIN get_qt_dir | head -n 1)/bin/qmake ../RetroShare \ + -spec android-clang \ + CONFIG+=retroshare_service CONFIG+=rs_jsonapi \ + RS_UPNP_LIB=miniupnpc \ + JSONAPI_GENERATOR_EXE=/jsonapi-generator-build/jsonapi-generator \ + NATIVE_LIBS_TOOLCHAIN_PATH=$NATIVE_LIBS_TOOLCHAIN_PATH \ + CONFIG+=no_retroshare_gui CONFIG+=no_rs_service_webui_terminal_password \ + CONFIG+=no_rs_service_terminal_login \ + CONFIG+=no_rs_sam3 CONFIG+=no_rs_sam3_libsam3 \ + $RS_SERVICE_QMAKE_EXTRA_OPTS +RUN make -j$(nproc) +RUN make install INSTALL_ROOT=/retroshare-service-android-build/android-build/ +RUN $($PREPARE_TOOLCHAIN get_qt_dir | head -n 1)/bin/androiddeployqt \ + --input retroshare-service/src/android-libretroshare-service.so-deployment-settings.json \ + --output android-build --android-platform android-$ANDROID_PLATFORM_VER \ + --jdk $JAVA_HOME --gradle + + +RUN rm -rf /bCache + +# Clean apt cache +RUN apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/misc/Android/README.asciidoc b/misc/Android/README.asciidoc new file mode 100644 index 000000000..c47974161 --- /dev/null +++ b/misc/Android/README.asciidoc @@ -0,0 +1,400 @@ += RetroShare development on Android + +// SPDX-FileCopyrightText: RetroShare Team +// SPDX-License-Identifier: CC-BY-SA-4.0 + + +Compiling an application for Android is not as easy as one would imagine, +expecially one like RetroShare that has a big codebase and is not well +documented. This document is aimed to empower the reader so she can hopefully +succed or at least have a significant help in compiling her own RetroShare APK +installable on Android. + + +== Preparing The Environement + +First of all setup your Qt for Android development environement following the +guide on the link:http://doc.qt.io/qt-5/androidgs.html[Qt for android web site]. +At this point you should have Android SDK, Android NDK, and Qt for Android +working fine, and you should be capable of executing on an Android emulator or +on your Android phone Qt for Android examples. + +But RetroShare is not as simple to compile as those examples. The good news is +that Android NDK ships all the necessary to build a custom toolchain that is +suitable to build RetroShare. +In order to build the toolchain with needed libraries RetroShare provides the ++android-prepare-toolchain.sh+ script; before you execute it you should define +some variables the script cannot determine in an easy and reliable manner by +itself in your terminal. + +[source,bash] +------------------------------------------------------------------------------- +## The path where Android NDK is installed in your system +export ANDROID_NDK_PATH="/opt/android-ndk/" + +## The path where your fresh compiled toolchain will be installed, make sure +## the parent exists +export NATIVE_LIBS_TOOLCHAIN_PATH="${HOME}/Builds/android-toolchains/retroshare-android/" + +## The CPU architecture of the Android device you want to target +export ANDROID_NDK_ARCH="arm" + +## The Android API level the Android device you want to target +export ANDROID_PLATFORM_VER="16" + +## The number of core that yout host CPU have (just to speed up compilation) set +## it to 1 if unsure +export HOST_NUM_CPU=1 + +./android-prepare-toolchain.sh +------------------------------------------------------------------------------- + + +== Preparing Qt Creator + +Now that your environement is set up you should configure Qt Creator for Android +following the +link:http://doc.qt.io/qtcreator/creator-developing-android.html[official guide]. +At the end of this step your Qt Creator should recognize the Android compiler +and the Qt for Android kit. + +Your Kit is now ready to use. Now you can open RetroShare as a Qt Creator +project and in the Projects left menu add the newly created kit if not already +present, so you can select it on the build type selection button down on the +left. + +Now you need to set properly a few options like `JSONAPI_GENERATOR_EXE` and +disable some of RetroShare modules like `retroshare-gui` that are not available +on Android so you will have to go to + +_Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit -> +Build Steps -> qmake -> Additional arguments_ + + +and add the following configurations (change `Your_Path` according to your +deployment) + +[source,makefile] +------------------------------------------------------------------------------- +CONFIG+=retroshare_service CONFIG+=rs_jsonapi CONFIG+=no_keywords +RS_UPNP_LIB=miniupnpc +JSONAPI_GENERATOR_EXE=Your_Path/jsonapi-generator/src/jsonapi-generator +NATIVE_LIBS_TOOLCHAIN_PATH=Your_Path/retroshare-android-16-arm/ +CONFIG+=no_retroshare_gui CONFIG+=no_rs_service_webui_terminal_password +CONFIG+=no_rs_service_terminal_login +CONFIG+=no_rs_sam3 CONFIG+=no_rs_sam3_libsam3 +------------------------------------------------------------------------------- + +TIP: Some versions of QtCreator try to find the Android SDK in +`/opt/android/sdk`. A workaround to this is to make a symbolic link there +pointing to your SDK installation path, like ++mkdir -p /opt/android/sdk && ln -s /home/user/android-sdk-linux +/opt/android/sdk+ + + +== Quircks + +=== Protected Apps + +On some Android devices like +Huawei ALE-L21+ background applications are +killed when screen is turned off unless they are in the _protected app_ list. +At moment seems apps developers don't have a way to have the application +_protected_ by default, unless the phone vendor decide the app is _popular_ so +the user have to enable _protection_ for RetroShare manually on those mobile +phones. + + +{empty} + +To enable enable _protection_: +Android menu -> Settings -> Privacy & security +-> Protected apps -> RetroShare+ + +{empty} + + +Other devices may offer similar _features_ please report them. + + +=== APK signature mismatch + +If you try to install a RetroShare APK that comes from a different source +(eg: if you try to upgrade from F-Droid when you originally installed an APK +build by yourself) Android will prevent that from happening. In that case the +only solution is to uninstall the app and then install the new APK but if you do +it also the application data and your precious cryptographic keys, friend list +etc. will be lost forever. +To avoid that you can attempt to manually backup and then restore from the +command-line (`adb backup` seems not working either) to change the app source +without erasing the appliation data. + +CAUTION: Following steps require root access on your Android device + +.Backup RetroShare Android application data +[source,bash] +-------------------------------------------------------------------------------- +export ORIG_DIR="/data/data/org.retroshare.android.qml_app" +export BACKUP_DIR="org.retroshare.android.qml_app.backup" + +adb root + +adb shell am force-stop org.retroshare.android.qml_app +sleep 1s + +mkdir ${BACKUP_DIR} + +# Avoid adb pull failing +adb shell rm ${ORIG_DIR}/files/.retroshare/libresapi.sock +adb pull ${ORIG_DIR}/files/ ${BACKUP_DIR}/files/ +-------------------------------------------------------------------------------- + +After this you should be able to uninstall the old APK with your preferred +method, as example from the command-line. + +.Uninstall RetroShare Android from the command-line +[source,bash] +-------------------------------------------------------------------------------- +adb uninstall org.retroshare.android.qml_app +-------------------------------------------------------------------------------- + +Now you can install a different signature APK and then restore the application +data with the following commands. + +[source,bash] +-------------------------------------------------------------------------------- +export ORIG_DIR="/data/data/org.retroshare.android.qml_app" +export BACKUP_DIR="org.retroshare.android.qml_app.backup" + +adb root + +## Launch the app to make sure the parent directory exists and has proper owner +adb shell monkey -p org.retroshare.android.qml_app -c android.intent.category.LAUNCHER 1 +sleep 1s + +adb shell am force-stop org.retroshare.android.qml_app +sleep 1s + + +APP_OWNER="$(adb shell busybox ls -lnd ${ORIG_DIR} | awk '{print $3":"$4}')" +adb shell rm -rf ${ORIG_DIR}/files +adb push ${BACKUP_DIR}/files/ ${ORIG_DIR}/files/ +adb shell busybox chown -R ${APP_OWNER} ${ORIG_DIR}/files/ +-------------------------------------------------------------------------------- + +Opening RetroShare android app now should show your old profile. + + +== Debugging with GDB + +If building RetroShare Android package seems tricky, setting up a functional +debugging environement for it feels like black magic. This section is meant to +help you doing it with less headache and hopefully in a reproducible way. + +Unfortunately at the time of the last update to this guide, Qt build system +strips debugging symbols from the package and from the buildroot also if you +compile with debugging enabled. Fiddling with `qmake` configurations and +variables like `CONFIG+=debug`, `CONFIG+=force_debug_info` or `QMAKE_STRIP` +either as commandline arguments or inside retroshare `.pro` and `.pri` files is +uneffective. IMHO Qt should handle this on itself so it is probably worth +reporting a bug to them. So to workaround this problem you need to fiddle a bit +with the Android NDK. In my case I always keep +Debug+ or +Release+ suffix in +my build directory name depending on what kind of build it is, so I use this +information and modify `llvm-strip` in a way that it will strip only if stripped +file path doesn't contain +Debug+. + +.Modify llvm-strip inside Android NDK +-------------------------------------------------------------------------------- +## Set ANDROID_NDK_PATH to your Android NDK installation path +export ANDROID_NDK_PATH="/opt/android-ndk/" + +## Define a convenience variable with llvm-strip path +export ANDROID_NDK_LLVM_STRIP="${ANDROID_NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip" + +## If not existing yer create a backup of the original llvm-strip +[ -f "${ANDROID_NDK_LLVM_STRIP}.back" ] || + cp "${ANDROID_NDK_LLVM_STRIP}" "${ANDROID_NDK_LLVM_STRIP}.back" + +## Create a new llvm-strip that does nothing if the argument path contains Debug +cat > "${ANDROID_NDK_LLVM_STRIP}" << LLVMSTRIPTRICK +#!/bin/bash + +echo "\${2}" | grep -q Debug || + "${ANDROID_NDK_LLVM_STRIP}.back" --strip-all "\${2}" + +LLVMSTRIPTRICK + +## Eventually you can revert back simply by running +# `mv "${ANDROID_NDK_LLVM_STRIP}.back" "${ANDROID_NDK_LLVM_STRIP}"` +-------------------------------------------------------------------------------- + + +To attach to the `retroshare-service` running on Android you need also to pull a +debugging sysroot out of your device first, RetroShare sources provides an +helper script to do that. + +.Prepare debugging sysroot with helper script +[source,bash] +-------------------------------------------------------------------------------- +## Set RetroShare source path +export RS_SOURCE_DIR="${HOME}/Development/rs-develop" + +## Optionally set your device ID first available will be used, you can run +## `adb devices` to list available devices. +#export ANDROID_SERIAL="YT013PSPGK" + +## Optionally set a path where to save the debugging sysroot otherwise default +## is used. +#export DEBUG_SYSROOT="${HOME}/Builds/debug_sysroot/${ANDROID_SERIAL}/" + +## Run the helper script +${RS_SOURCE_DIR}/build_scripts/Android/pull_sysroot.sh +-------------------------------------------------------------------------------- + +.Prepare Android NDK GDB configurations +[source,bash] +-------------------------------------------------------------------------------- +## Optionally set Qt version variable consistently with your installation +export QT_VERSION="5.12.7" + +## Optionally set Qt architecture variable consistently with Android device +export QT_ARCH="arm64_v8a" + +## Optionally set Qt path variable consistently with your installation +export QT_DIR="/opt/Qt-${QT_VERSION}/${QT_VERSION}/" + +## Optionally set RetroShare buildroot path +export RS_BUILD_DIR="${HOME}/Builds/RetroShare-Android_for_${QT_ARCH}_Clang_Qt_${QT_VERSION//./_}_android_${QT_ARCH}-Debug/" + +## Optionally set gdb config file path +export GDB_CONFIGS_FILE="${HOME}/Builds/gdb_configs_${QT_ARCH}" + +## Generate Android NDK GDB configuration +${RS_SOURCE_DIR}/build_scripts/Android/generate_gdb_init_commands.sh +-------------------------------------------------------------------------------- + + +You will need to run the following steps everytime you want to debug +`retroshare-service` on Android. + +Make sure `retroshare-service` is running on your connected Android device. + +.Run GDB server on your Android device from your host console +[source,bash] +-------------------------------------------------------------------------------- +${RS_SOURCE_DIR}/build_scripts/Android/start_gdbserver.sh +-------------------------------------------------------------------------------- + + +.Run Android NDK GDB on your workstation and attach +[source,bash] +-------------------------------------------------------------------------------- +## Start NDK gdb +${ANDROID_NDK_PATH}/prebuilt/linux-x86_64/bin/gdb + +## Instruct GDB how and where to find debugging symbols +(gdb) source $GDB_CONFIGS_FILE + +## Connect to the gdbserver running on the phone +(gdb) target remote 127.0.01:5039 + +## Have fun debugging +(gdb) +-------------------------------------------------------------------------------- + + +== Debugging with Qt Creator + +WARNING: As of the last update to this guide, debugging retroshare-service +running on Android via Qt creator doesn't wrok even with all the trickery +explained in this section, you better learn how to debug with GDB reading +carefully previous section. + +Qt Creator actually support debugging only for the foreground activity, so to +debug what's happening in the core extra trickery is needed. + +- Run the App in Debug mode from QtCreator "Start Debugging" button +- Enable QtCreator debugger console view Menu->Window->Debugger Log +- Run +show solib-search-path+ in the QtCreator GDB console +- Take note of the output you get in the right pane of the console +- Thanks https://stackoverflow.com/a/31164313 for the idea + +TIP: QtCreator GDB console seems a bit buggy and easly trigger timeout +message when a command is run, in that case attempt to run the command while the +debugging is paused or at breakpoint, or with other similar tricks. + +CAUTION: Following steps require root access on your Android device + +Now on your phone yuo need to authorize root access to applications, then once +you plug your sacrifical Android phone run this commands + +.Run gdbserver as root on Android phone +[source,bash] +-------------------------------------------------------------------------------- +## Open a shell from your workstation on the connected Android phone +adb shell + +## take the note of the phone IP address +ip address show + +## Gain root permissions on the shell +su + +## Attach with gdbserver and listen on one TCP port +gdbserver :4567 --attach $(pgrep org.retroshare.android.qml_app:rs) +-------------------------------------------------------------------------------- + + +.Prepare and run Android NDK GDB on your workstation +[source,bash] +-------------------------------------------------------------------------------- +## Setup some convenience variables +NDK_GDB="${ANDROID_NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gdb" +RS_BUILD_PATH="${HOME}/Builds/RetroShare-Android_for_armeabi_v7a_GCC_4_9_Qt_5_9_2_android_armv7-Debug/" + +## Start Android NDK GDB of your phone architecture passing the executable +$NDK_GDB $RS_BUILD_PATH/retroshare-android-service/src/libretroshare-android-service.so + +## Instruct GDB how and where to find debugging symbols +(gdb) set auto-solib-add on +(gdb) set solib-search-path THE:BIG:LIST:OF:DIRECTORIES:YOU:TAKE:NOTE:BEFORE + +## Connect to the gdbserver running on the phone +(gdb) target remote $PHONE_IP:4567 + +## Have fun debugging +(gdb) +-------------------------------------------------------------------------------- + +TIP: Some time WiFi power saving on Android mess with the GDB connection, +to prevent that from appening open another +adb shell+ and live +ping+ toward +your work-station running + +== Embedding into other Android packages + +As showed by https://elrepo.io/[elRepo.io] developers it is possible and quite +easy to embed `retroshare-service` into other Android packages see description + +https://gitlab.com/elRepo.io/elRepo.io-android/-/blob/master/README.adoc + +And implementation details + +https://gitlab.com/elRepo.io/elRepo.io-android/-/blob/master/android/app/build.gradle + + +== Furter Readings + +- link:http://doc.qt.io/qt-5/android-support.html[] +- link:https://developer.android.com/ndk/guides/libs.html[] +- link:retroshare://forum?name=Compiling%20nogui%20for%20android&id=8fd22bd8f99754461e7ba1ca8a727995&msgid=4e0f92330600bba9cf978f384f4b7b2f2ca64eff[] +- link:retroshare://file?name=Android%20Native%20Development%20Kit%20Cookbook.pdf&size=29214468&hash=0123361c1b14366ce36118e82b90faf7c7b1b136[] +- link:https://groups.google.com/forum/#!topic/android-developers/srATPaL0aRU[] +- link:https://stackoverflow.com/questions/31638986/protected-apps-setting-on-huawei-phones-and-how-to-handle-it[] +- link:https://tthtlc.wordpress.com/2012/09/19/how-to-do-remote-debugging-via-gdbserver-running-inside-the-android-phone/[] +- link:https://source.android.com/devices/tech/debug/[] +- link:https://source.android.com/devices/tech/debug/gdb[] +- link:https://fw4spl-org.github.io/fw4spl-blog/2015/07/27/Native-debugging-on-Android-with-QtCreator.html[] +- link:https://fragglet.livejournal.com/19646.html[] +- link:https://github.com/android-ndk/ndk/issues/773[How to build without using standalone toolchain?] + +== License + +Copyright (C) 2016-2021 Gioacchino Mazzurco + +Copyright (C) 2020-2021 Asociación Civil Altermundi + + +This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License. + +image::https://i.creativecommons.org/l/by-sa/4.0/88x31.png[Creative Commons License, link=http://creativecommons.org/licenses/by-sa/4.0/] diff --git a/misc/Android/generate_gdb_init_commands.sh b/misc/Android/generate_gdb_init_commands.sh new file mode 100755 index 000000000..3a9883950 --- /dev/null +++ b/misc/Android/generate_gdb_init_commands.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Script to prepare Android NDK GDB configurations to debug retroshare-service +# +# Copyright (C) 2020 Gioacchino Mazzurco +# Copyright (C) 2020 Asociación Civil Altermundi +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with this program. If not, see + + +## Define default value for variable, take two arguments, $1 variable name, +## $2 default variable value, if the variable is not already define define it +## with default value. +function define_default_value() +{ + VAR_NAME="${1}" + DEFAULT_VALUE="${2}" + + [ -z "${!VAR_NAME}" ] && export ${VAR_NAME}="${DEFAULT_VALUE}" +} + +define_default_value QT_VERSION "5.12.4" +define_default_value QT_ARCH "arm64_v8a" +define_default_value QT_DIR "/opt/Qt-${QT_VERSION}/${QT_VERSION}/" +define_default_value ANDROID_SERIAL "$(adb devices | head -n 2 | tail -n 1 | awk '{print $1}')" +define_default_value RS_BUILD_DIR "${HOME}/Builds/RetroShare-Android_for_${QT_ARCH}_Clang_Qt_${QT_VERSION//./_}_android_${QT_ARCH}-Debug/" +define_default_value RS_SOURCE_DIR "${HOME}/Development/rs-develop/" +define_default_value DEBUG_SYSROOT "${HOME}/Builds/debug_sysroot/${ANDROID_SERIAL}/" +define_default_value GDB_CONFIGS_FILE "${HOME}/Builds/gdb_configs_${QT_ARCH}" + +scanDir() +{ + find "$1" -type d -not -path '*/\.git/*' | tr '\n' ':' >> $GDB_CONFIGS_FILE +} + +putSeparator() +{ + echo >> $GDB_CONFIGS_FILE + echo >> $GDB_CONFIGS_FILE +} + +echo "set sysroot ${DEBUG_SYSROOT}" > $GDB_CONFIGS_FILE +putSeparator + +echo "set auto-solib-add on" >> $GDB_CONFIGS_FILE +echo -n "set solib-search-path " >> $GDB_CONFIGS_FILE +scanDir "${RS_BUILD_DIR}" +scanDir "${DEBUG_SYSROOT}" +scanDir "${QT_DIR}/android_${QT_ARCH}/lib/" +scanDir "${QT_DIR}/android_${QT_ARCH}/plugins/" +scanDir "${QT_DIR}/android_${QT_ARCH}/qml/" +putSeparator + +echo -n "directory " >> $GDB_CONFIGS_FILE +scanDir ${RS_SOURCE_DIR}/jsonapi-generator/src +scanDir ${RS_SOURCE_DIR}/libbitdht/src +scanDir ${RS_SOURCE_DIR}/openpgpsdk/src +scanDir ${RS_SOURCE_DIR}/libretroshare/src +scanDir ${RS_SOURCE_DIR}/retroshare-service/src +scanDir ${RS_SOURCE_DIR}/supportlibs/rapidjson/include/ +scanDir ${RS_SOURCE_DIR}/supportlibs/restbed/source/ +scanDir ${RS_SOURCE_DIR}/supportlibs/udp-discovery-cpp/ +scanDir ${RS_SOURCE_DIR}/supportlibs/restbed/dependency/asio/asio/include/ +scanDir ${RS_SOURCE_DIR}/supportlibs/restbed/dependency/catch/include/ +putSeparator + +## see https://stackoverflow.com/questions/28972367/gdb-backtrace-without-stopping +echo "catch signal SIGSEGV" >> $GDB_CONFIGS_FILE +echo "commands + bt + continue + end" >> $GDB_CONFIGS_FILE +putSeparator + +echo GDB_CONFIGS_FILE=$GDB_CONFIGS_FILE diff --git a/misc/Android/prepare-toolchain-clang.sh b/misc/Android/prepare-toolchain-clang.sh new file mode 100755 index 000000000..4519d591d --- /dev/null +++ b/misc/Android/prepare-toolchain-clang.sh @@ -0,0 +1,920 @@ +#!/bin/bash + +# Script to prepare RetroShare Android package building toolchain +# +# Copyright (C) 2016-2021 Gioacchino Mazzurco +# Copyright (C) 2020-2021 Asociación Civil Altermundi +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with this program. If not, see +# +# SPDX-FileCopyrightText: Retroshare Team +# SPDX-License-Identifier: AGPL-3.0-only + + +## Define default value for variable, take two arguments, $1 variable name, +## $2 default variable value, if the variable is not already define define it +## with default value. +function define_default_value() +{ + VAR_NAME="${1}" + DEFAULT_VALUE="${2}" + + [ -z "${!VAR_NAME}" ] && export ${VAR_NAME}="${DEFAULT_VALUE}" || true +} + +## You are supposed to provide the following variables according to your system setup +define_default_value ANDROID_NDK_PATH "/opt/android-ndk/" +define_default_value ANDROID_NDK_ARCH "arm" +define_default_value ANDROID_PLATFORM_VER "16" +define_default_value NATIVE_LIBS_TOOLCHAIN_PATH "${HOME}/Builds/android-toolchains/retroshare-android-${ANDROID_PLATFORM_VER}-${ANDROID_NDK_ARCH}/" +define_default_value HOST_NUM_CPU $(nproc) + +define_default_value ANDROID_SDK_INSTALL "false" +define_default_value ANDROID_SDK_TOOLS_VERSION "3859397" +define_default_value ANDROID_SDK_TOOLS_SHA256 444e22ce8ca0f67353bda4b85175ed3731cae3ffa695ca18119cbacef1c1bea0 +define_default_value ANDROID_SDK_VERSION "29.0.3" + +define_default_value ANDROID_NDK_INSTALL "false" +define_default_value ANDROID_NDK_VERSION "r21" +define_default_value ANDROID_NDK_SHA256 b65ea2d5c5b68fb603626adcbcea6e4d12c68eb8a73e373bbb9d23c252fc647b + +define_default_value BZIP2_SOURCE_VERSION "1.0.6" +define_default_value BZIP2_SOURCE_SHA256 a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd + +define_default_value OPENSSL_SOURCE_VERSION "1.1.1c" +define_default_value OPENSSL_SOURCE_SHA256 f6fb3079ad15076154eda9413fed42877d668e7069d9b87396d0804fdb3f4c90 + +define_default_value SQLITE_SOURCE_YEAR "2018" +define_default_value SQLITE_SOURCE_VERSION "3250200" +define_default_value SQLITE_SOURCE_SHA256 da9a1484423d524d3ac793af518cdf870c8255d209e369bd6a193e9f9d0e3181 + +define_default_value SQLCIPHER_SOURCE_VERSION "4.4.3" +define_default_value SQLCIPHER_SOURCE_SHA256 b8df69b998c042ce7f8a99f07cf11f45dfebe51110ef92de95f1728358853133 + +define_default_value LIBUPNP_SOURCE_VERSION "1.8.4" +define_default_value LIBUPNP_SOURCE_SHA256 976c3e4555604cdd8391ed2f359c08c9dead3b6bf131c24ce78e64d6669af2ed + +define_default_value QT_ANDROID_VIA_INSTALLER "false" +define_default_value QT_VERSION "5.12.11" +define_default_value QT_INSTALLER_VERSION "4.1.1" +define_default_value QT_INSTALLER_SHA256 1266ffd0d1b0e466244e3bc8422975c1aa9d96745b6bb28d422f7f92df11f34c +define_default_value QT_INSTALLER_JWT_TOKEN "" +define_default_value QT_INSTALL_PATH "${NATIVE_LIBS_TOOLCHAIN_PATH}/Qt/" + +define_default_value QT_ANDROID_INSTALLER_SHA256 a214084e2295c9a9f8727e8a0131c37255bf724bfc69e80f7012ba3abeb1f763 + +define_default_value RESTBED_SOURCE_REPO "https://github.com/Corvusoft/restbed.git" +define_default_value RESTBED_SOURCE_VERSION f74f9329dac82e662c1d570b7cd72c192b729eb4 + +define_default_value UDP_DISCOVERY_CPP_SOURCE "https://github.com/truvorskameikin/udp-discovery-cpp.git" +define_default_value UDP_DISCOVERY_CPP_VERSION "develop" + +define_default_value XAPIAN_SOURCE_VERSION "1.4.7" +define_default_value XAPIAN_SOURCE_SHA256 13f08a0b649c7afa804fa0e85678d693fd6069dd394c9b9e7d41973d74a3b5d3 + +define_default_value RAPIDJSON_SOURCE_VERSION "1.1.0" +define_default_value RAPIDJSON_SOURCE_SHA256 bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e + +define_default_value MINIUPNPC_SOURCE_VERSION "2.1.20190625" +define_default_value MINIUPNPC_SOURCE_SHA256 8723f5d7fd7970de23635547700878cd29a5c2bb708b5e5475b2d1d2510317fb + +# zlib and libpng versions walks toghether +define_default_value ZLIB_SOURCE_VERSION "1.2.11" +define_default_value ZLIB_SOURCE_SHA256 4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066 + +define_default_value LIBPNG_SOURCE_VERSION "1.6.37" +define_default_value LIBPNG_SOURCE_SHA256 505e70834d35383537b6491e7ae8641f1a4bed1876dbfe361201fc80868d88ca + +define_default_value LIBJPEG_SOURCE_VERSION "9d" +define_default_value LIBJPEG_SOURCE_SHA256 6c434a3be59f8f62425b2e3c077e785c9ce30ee5874ea1c270e843f273ba71ee + +define_default_value TIFF_SOURCE_VERSION "4.2.0" +define_default_value TIFF_SOURCE_SHA256 eb0484e568ead8fa23b513e9b0041df7e327f4ee2d22db5a533929dfc19633cb + +define_default_value CIMG_SOURCE_VERSION "2.9.7" +define_default_value CIMG_SOURCE_SHA256 595dda9718431a123b418fa0db88e248c44590d47d9b1646970fa0503e27fa5c + +define_default_value PHASH_SOURCE_REPO "https://gitlab.com/g10h4ck/pHash.git" +define_default_value PHASH_SOURCE_VERSION origin/android-ndk + +define_default_value MVPTREE_SOURCE_REPO "https://github.com/starkdg/mvptree.git" +define_default_value MVPTREE_SOURCE_VERSION origin/master + +define_default_value REPORT_DIR "$(pwd)/$(basename ${NATIVE_LIBS_TOOLCHAIN_PATH})_build_report/" + +define_default_value RS_SRC_DIR "$(realpath $(dirname $BASH_SOURCE)/../../)" + + +cArch="" +eABI="" +cmakeABI="" + +case "${ANDROID_NDK_ARCH}" in +"arm") + cArch="${ANDROID_NDK_ARCH}" + eABI="eabi" + ;; +"arm64") + cArch="aarch64" + eABI="" + ;; +"x86") + cArch="i686" + eABI="" + ;; +"x86_64") + echo "ANDROID_NDK_ARCH=${ANDROID_NDK_ARCH} not supported yet" + exit -1 + cArch="??" + eABI="" +esac + +export SYSROOT="${NATIVE_LIBS_TOOLCHAIN_PATH}/sysroot/" +export PREFIX="${SYSROOT}/usr/" +export CC="${NATIVE_LIBS_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-clang" +export CXX="${NATIVE_LIBS_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-clang++" +export AR="${NATIVE_LIBS_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-ar" +export RANLIB="${NATIVE_LIBS_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-ranlib" + +# Used to instruct cmake to explicitely ignore host libraries +export HOST_IGNORE_PREFIX="/usr/" + + +## $1 filename, $2 sha256 hash +function check_sha256() +{ + echo ${2} "${1}" | sha256sum -c &> /dev/null +} + +## $1 filename, $2 sha256 hash, $3 url +function verified_download() +{ + FILENAME="$1" + SHA256="$2" + URL="$3" + + check_sha256 "${FILENAME}" "${SHA256}" || + { + rm -rf "${FILENAME}" + + wget -O "${FILENAME}" "$URL" || + { + echo "Failed downloading ${FILENAME} from $URL" + exit 1 + } + + check_sha256 "${FILENAME}" "${SHA256}" || + { + echo "SHA256 mismatch for ${FILENAME} from ${URL} expected sha256 ${SHA256} got $(sha256sum ${FILENAME} | awk '{print $1}')" + exit 1 + } + } +} + +# This function is the result of reading and testing many many stuff be very +# careful editing it +function andro_cmake() +{ +# Using android.toolchain.cmake as documented here +# https://developer.android.com/ndk/guides/cmake seens to break more things then +# it fixes :-\ + + cmakeProc="" + case "${ANDROID_NDK_ARCH}" in + "arm") + cmakeProc="armv7-a" + ;; + "arm64") + cmakeProc="aarch64" + ;; + "x86") + cmakeProc="i686" + ;; + "x86_64") + cmakeProc="x86_64" + ;; + *) + echo "Unhandled NDK architecture ${ANDROID_NDK_ARCH}" + exit -1 + ;; + esac + + _hi="$HOST_IGNORE_PREFIX" + + cmake \ + -DCMAKE_SYSTEM_PROCESSOR=$cmakeProc \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_PREFIX_PATH="${PREFIX}" \ + -DCMAKE_SYSTEM_PREFIX_PATH="${PREFIX}" \ + -DCMAKE_INCLUDE_PATH="${PREFIX}/include" \ + -DCMAKE_SYSTEM_INCLUDE_PATH="${PREFIX}/include" \ + -DCMAKE_LIBRARY_PATH="${PREFIX}/lib" \ + -DCMAKE_SYSTEM_LIBRARY_PATH="${PREFIX}/lib" \ + -DCMAKE_INSTALL_PREFIX="${PREFIX}" \ + -DCMAKE_IGNORE_PATH="$_hi/include;$_hi/lib;$_hi/lib64" \ + $@ + + # It is probably ok to do not touch CMAKE_PROGRAM_PATH and + # CMAKE_SYSTEM_PROGRAM_PATH +} + +function git_source_get() +{ + sourceDir="$1" ; shift #$1 + sourceRepo="$1" ; shift #$2 + sourceVersion="$1" ; shift #$3 + # extra paramethers are treated as submodules + + [ -d "$sourceDir" ] && + { + pushd "$sourceDir" + actUrl="$(git remote get-url origin)" + [ "$actUrl" != "$sourceRepo" ] && rm -rf "${sourceDir}" + popd + } || true + + [ -d $sourceDir ] || git clone "$sourceRepo" "$sourceDir" + pushd $sourceDir + + git fetch --all + git reset --hard ${sourceVersion} + + while [ "$1" != "" ] ; do + git submodule update --init "$1" + pushd "$1" + git reset --hard + shift + popd + done + + popd +} + +declare -A TASK_REGISTER + +function task_register() +{ + TASK_REGISTER[$1]=true +} + +function task_unregister() +{ + # we may simply wipe them but we could benefit from keeping track of + # unregistered tasks too + TASK_REGISTER[$1]=false +} + +function task_logfile() +{ + echo "$REPORT_DIR/$1.log" +} + +function task_run() +{ + mTask="$1" ; shift + + [ "${TASK_REGISTER[$mTask]}" != "true" ] && + { + echo "Attempt to run not registered task $mTask $@" + return -1 + } + + logFile="$(task_logfile $mTask)" + if [ -f "$logFile" ] ; then + echo "Task $mTask already run more details at $logFile" + else + date | tee > "$logFile" + $mTask $@ |& tee --append "$logFile" + mRetval="${PIPESTATUS[0]}" + echo "Task $mTask return ${mRetval} more details at $logFile" + date | tee --append "$logFile" + return ${mRetval} + fi +} + +function task_zap() +{ + rm -f "$(task_logfile $1)" +} + +DUPLICATED_INCLUDES_LIST_FILE="${REPORT_DIR}/duplicated_includes_list" +DUPLICATED_INCLUDES_DIR="${REPORT_DIR}/duplicated_includes/" + +task_register install_android_sdk +install_android_sdk() +{ + tFile="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip" + + verified_download "${tFile}" "${ANDROID_SDK_TOOLS_SHA256}" \ + "https://dl.google.com/android/repository/${tFile}" + + unzip "${tFile}" + mkdir -p "$ANDROID_SDK_PATH" + rm -rf "$ANDROID_SDK_PATH/tools/" + mv --verbose tools/ "$ANDROID_SDK_PATH/tools/" + + # Install Android SDK + yes | $ANDROID_SDK_PATH/tools/bin/sdkmanager --licenses && \ + $ANDROID_SDK_PATH/tools/bin/sdkmanager --update + $ANDROID_SDK_PATH/tools/bin/sdkmanager "platforms;android-$ANDROID_PLATFORM_VER" + $ANDROID_SDK_PATH/tools/bin/sdkmanager "build-tools;$ANDROID_SDK_VERSION" +} + +task_register install_android_ndk +install_android_ndk() +{ + tFile="android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip" + + verified_download "${tFile}" "${ANDROID_NDK_SHA256}" \ + "https://dl.google.com/android/repository/${tFile}" + + unzip "${tFile}" + mkdir -p "$ANDROID_NDK_PATH" + rm -rf "$ANDROID_NDK_PATH" + mv --verbose "android-ndk-${ANDROID_NDK_VERSION}/" "$ANDROID_NDK_PATH/" +} + +## More information available at https://android.googlesource.com/platform/ndk/+/ics-mr0/docs/STANDALONE-TOOLCHAIN.html +task_register bootstrap_toolchain +bootstrap_toolchain() +{ + rm -rf "${NATIVE_LIBS_TOOLCHAIN_PATH}" + ${ANDROID_NDK_PATH}/build/tools/make_standalone_toolchain.py --verbose \ + --arch ${ANDROID_NDK_ARCH} --install-dir ${NATIVE_LIBS_TOOLCHAIN_PATH} \ + --api ${ANDROID_PLATFORM_VER} + + # Avoid problems with arm64 some libraries installing on lib64 + ln -s "${PREFIX}/lib/" "${PREFIX}/lib64" + + pushd "${PREFIX}/include/" + find . -not -type d > "${DUPLICATED_INCLUDES_LIST_FILE}" + popd +} + +## This avoid include errors due to -isystem and -I ordering issue +task_register deduplicate_includes +deduplicate_includes() +{ + while read delFile ; do + mNewPath="${DUPLICATED_INCLUDES_DIR}/$delFile" + mkdir --verbose --parents "$(dirname "$mNewPath")" + mv --verbose "${PREFIX}/include/$delFile" "$mNewPath" + done < "${DUPLICATED_INCLUDES_LIST_FILE}" +} + +task_register reduplicate_includes +reduplicate_includes() +{ + pushd "${DUPLICATED_INCLUDES_DIR}" + find . -not -type d | while read delFile ; do + mv --verbose "${delFile}" "${PREFIX}/include/$delFile" + done + popd +} + +# $1 optional prefix prepended only if return value is not empty +# $2 optional suffix appended only if return value is not empty +task_register get_qt_arch +get_qt_arch() +{ + local QT_VERSION_COMP="$(echo $QT_VERSION | awk -F. '{print $1*1000000+$2*1000+$3}')" + local QT_ARCH="" + + # Qt >= 5.15.0 ships all Android architectures toghether + [ "$QT_VERSION_COMP" -lt "5015000" ] && + { + case "${ANDROID_NDK_ARCH}" in + "arm") + QT_ARCH="armv7" + ;; + "arm64") + QT_ARCH="arm64_v8a" + ;; + "x86") + QT_ARCH="x86" + ;; + esac + + echo "$1$QT_ARCH$2" + } +} + +task_register get_qt_dir +get_qt_dir() +{ + echo "${QT_INSTALL_PATH}/${QT_VERSION}/android$(get_qt_arch _)/" +} + +## More information available at https://wiki.qt.io/Online_Installer_4.x +task_register install_qt_android +install_qt_android() +{ + [ "$QT_INSTALLER_JWT_TOKEN" == "" ] && + { + echo "To run Qt installer QT_INSTALLER_JWT_TOKEN environement variable \ +need to be set to a valid JWT token see https://wiki.qt.io/Online_Installer_4.x" + return -1 + } + + QT_VERSION_CODE="$(echo $QT_VERSION | tr -d .)" + QT_INSTALLER="qt-unified-linux-x86_64-${QT_INSTALLER_VERSION}-online.run" + tMajDotMinVer="$(echo $QT_INSTALLER_VERSION | awk -F. '{print $1"."$2}')" + verified_download $QT_INSTALLER $QT_INSTALLER_SHA256 \ + "https://master.qt.io/archive/online_installers/${tMajDotMinVer}/${QT_INSTALLER}" + + chmod a+x ${QT_INSTALLER} + QT_QPA_PLATFORM=minimal ./${QT_INSTALLER} \ + install qt.qt5.${QT_VERSION_CODE}.android$(get_qt_arch _) \ + --accept-licenses --accept-obligations --confirm-command \ + --default-answer --no-default-installations \ + --root "${QT_INSTALL_PATH}" +} + +## More information available at retroshare://file?name=Android%20Native%20Development%20Kit%20Cookbook.pdf&size=29214468&hash=0123361c1b14366ce36118e82b90faf7c7b1b136 +task_register build_bzlib +build_bzlib() +{ + B_dir="bzip2-${BZIP2_SOURCE_VERSION}" + rm -rf $B_dir + + verified_download $B_dir.tar.gz $BZIP2_SOURCE_SHA256 \ + http://distfiles.gentoo.org/distfiles/bzip2-${BZIP2_SOURCE_VERSION}.tar.gz + + tar -xf $B_dir.tar.gz + cd $B_dir + sed -i "/^CC=.*/d" Makefile + sed -i "/^AR=.*/d" Makefile + sed -i "/^RANLIB=.*/d" Makefile + sed -i "/^LDFLAGS=.*/d" Makefile + sed -i "s/^all: libbz2.a bzip2 bzip2recover test/all: libbz2.a bzip2 bzip2recover/" Makefile + make -j${HOST_NUM_CPU} + make install PREFIX=${PREFIX} +# sed -i "/^CC=.*/d" Makefile-libbz2_so +# make -f Makefile-libbz2_so -j${HOST_NUM_CPU} +# cp libbz2.so.1.0.6 ${SYSROOT}/usr/lib/libbz2.so + cd .. +} + +## More information available at http://doc.qt.io/qt-5/opensslsupport.html +task_register build_openssl +build_openssl() +{ + B_dir="openssl-${OPENSSL_SOURCE_VERSION}" + rm -rf $B_dir + + verified_download $B_dir.tar.gz $OPENSSL_SOURCE_SHA256 \ + https://www.openssl.org/source/$B_dir.tar.gz + + tar -xf $B_dir.tar.gz + cd $B_dir +## We link openssl statically to avoid android silently sneaking in his own +## version of libssl.so (we noticed this because it had some missing symbol +## that made RS crash), the crash in some android version is only one of the +## possible problems the fact that android insert his own binary libssl.so pose +## non neglegible security concerns. + oBits="32" + [[ ${ANDROID_NDK_ARCH} =~ .*64.* ]] && oBits=64 + + ANDROID_NDK="${ANDROID_NDK_PATH}" PATH="${SYSROOT}/bin/:${PATH}" \ + ./Configure linux-generic${oBits} -fPIC --prefix="${PREFIX}" \ + --openssldir="${SYSROOT}/etc/ssl" +# sed -i 's/LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \\/LIBNAME=$$i \\/g' Makefile +# sed -i '/LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \\/d' Makefile + + # Avoid documentation build which is unneded and time consuming + echo "exit 0; " > util/process_docs.pl + + make -j${HOST_NUM_CPU} + make install + rm -f ${PREFIX}/lib/libssl.so* + rm -f ${PREFIX}/lib/libcrypto.so* + cd .. +} + +task_register build_sqlite +build_sqlite() +{ + B_dir="sqlite-autoconf-${SQLITE_SOURCE_VERSION}" + rm -rf $B_dir + + verified_download $B_dir.tar.gz $SQLITE_SOURCE_SHA256 \ + https://www.sqlite.org/${SQLITE_SOURCE_YEAR}/$B_dir.tar.gz + + tar -xf $B_dir.tar.gz + cd $B_dir + ./configure --with-pic --prefix="${PREFIX}" --host=${cArch}-linux + make -j${HOST_NUM_CPU} + make install + rm -f ${PREFIX}/lib/libsqlite3.so* + cd .. +} + +task_register build_sqlcipher +build_sqlcipher() +{ + task_run build_sqlite + + B_dir="sqlcipher-${SQLCIPHER_SOURCE_VERSION}" + rm -rf $B_dir + + T_file="${B_dir}.tar.gz" + + verified_download $T_file $SQLCIPHER_SOURCE_SHA256 \ + https://github.com/sqlcipher/sqlcipher/archive/v${SQLCIPHER_SOURCE_VERSION}.tar.gz + + tar -xf $T_file + cd $B_dir +# case "${ANDROID_NDK_ARCH}" in +# "arm64") +# # SQLCipher config.sub is outdated and doesn't recognize newer architectures +# rm config.sub +# autoreconf --verbose --install --force +# automake --add-missing --copy --force-missing +# ;; +# esac + ./configure --with-pic --build=$(sh ./config.guess) \ + --host=${cArch}-linux \ + --prefix="${PREFIX}" --with-sysroot="${SYSROOT}" \ + --enable-tempstore=yes \ + --disable-tcl --disable-shared \ + CFLAGS="-DSQLITE_HAS_CODEC" LDFLAGS="${PREFIX}/lib/libcrypto.a" + make -j${HOST_NUM_CPU} + make install + cd .. +} + +task_register build_libupnp +build_libupnp() +{ + B_dir="pupnp-release-${LIBUPNP_SOURCE_VERSION}" + B_ext=".tar.gz" + B_file="${B_dir}${B_ext}" + rm -rf $B_dir + + verified_download $B_file $LIBUPNP_SOURCE_SHA256 \ + https://github.com/mrjimenez/pupnp/archive/release-${LIBUPNP_SOURCE_VERSION}${B_ext} + + tar -xf $B_file + cd $B_dir + ./bootstrap +## liupnp must be configured as static library because if not the linker will +## look for libthreadutils.so.6 at runtime that cannot be packaged on android +## as it supports only libname.so format for libraries, thus resulting in a +## crash at startup. + ./configure --with-pic --enable-static --disable-shared --disable-samples \ + --disable-largefile \ + --prefix="${PREFIX}" --host=${cArch}-linux + make -j${HOST_NUM_CPU} + make install + cd .. +} + +task_register build_rapidjson +build_rapidjson() +{ + B_dir="rapidjson-${RAPIDJSON_SOURCE_VERSION}" + D_file="${B_dir}.tar.gz" + verified_download $D_file $RAPIDJSON_SOURCE_SHA256 \ + https://github.com/Tencent/rapidjson/archive/v${RAPIDJSON_SOURCE_VERSION}.tar.gz + tar -xf $D_file + cp -r "${B_dir}/include/rapidjson/" "${PREFIX}/include/rapidjson" +} + +task_register build_restbed +build_restbed() +{ + S_dir="restbed" + B_dir="${S_dir}-build" + git_source_get "$S_dir" "$RESTBED_SOURCE_REPO" "${RESTBED_SOURCE_VERSION}" \ + "dependency/asio" "dependency/catch" + + rm -rf "$B_dir"; mkdir "$B_dir" + pushd "$B_dir" + andro_cmake -DBUILD_TESTS=OFF -DBUILD_SSL=OFF -B. -H../${S_dir} + make -j${HOST_NUM_CPU} + make install + popd +} + +task_register build_udp-discovery-cpp +build_udp-discovery-cpp() +{ + S_dir="udp-discovery-cpp" + [ -d $S_dir ] || git clone $UDP_DISCOVERY_CPP_SOURCE $S_dir + cd $S_dir + git checkout $UDP_DISCOVERY_CPP_VERSION + cd .. + + B_dir="udp-discovery-cpp-build" + rm -rf ${B_dir}; mkdir ${B_dir}; cd ${B_dir} + andro_cmake -B. -H../$S_dir + make -j${HOST_NUM_CPU} + cp libudp-discovery.a "${PREFIX}/lib/" + cp ../$S_dir/*.hpp "${PREFIX}/include/" + cd .. +} + +task_register build_xapian +build_xapian() +{ + B_dir="xapian-core-${XAPIAN_SOURCE_VERSION}" + D_file="$B_dir.tar.xz" + verified_download $D_file $XAPIAN_SOURCE_SHA256 \ + https://oligarchy.co.uk/xapian/${XAPIAN_SOURCE_VERSION}/$D_file + rm -rf $B_dir + tar -xf $D_file + cd $B_dir + B_endiannes_detection_failure_workaround="ac_cv_c_bigendian=no" + B_large_file="" + [ "${ANDROID_PLATFORM_VER}" -lt "24" ] && B_large_file="--disable-largefile" + ./configure ${B_endiannes_detection_failure_workaround} ${B_large_file} \ + --with-pic \ + --disable-backend-inmemory --disable-backend-remote \ + --disable--backend-chert --enable-backend-glass \ + --host=${cArch}-linux --enable-static --disable-shared \ + --prefix="${PREFIX}" --with-sysroot="${SYSROOT}" + make -j${HOST_NUM_CPU} + make install + cd .. +} + +task_register build_miniupnpc +build_miniupnpc() +{ + S_dir="miniupnpc-${MINIUPNPC_SOURCE_VERSION}" + B_dir="miniupnpc-${MINIUPNPC_SOURCE_VERSION}-build" + D_file="$S_dir.tar.gz" + verified_download $D_file $MINIUPNPC_SOURCE_SHA256 \ + http://miniupnp.free.fr/files/${D_file} + rm -rf $S_dir $B_dir + tar -xf $D_file + mkdir $B_dir + cd $B_dir + andro_cmake \ + -DUPNPC_BUILD_STATIC=TRUE \ + -DUPNPC_BUILD_SHARED=FALSE \ + -DUPNPC_BUILD_TESTS=FALSE \ + -DUPNPC_BUILD_SAMPLE=FALSE \ + -B. -S../$S_dir + make -j${HOST_NUM_CPU} + make install + cd .. +} + +task_register build_zlib +build_zlib() +{ + S_dir="zlib-${ZLIB_SOURCE_VERSION}" + B_dir="zlib-${ZLIB_SOURCE_VERSION}-build" + D_file="$S_dir.tar.xz" + verified_download $D_file $ZLIB_SOURCE_SHA256 \ + http://www.zlib.net/${D_file} + rm -rf $S_dir $B_dir + tar -xf $D_file + mkdir $B_dir + cd $B_dir + andro_cmake -B. -S../$S_dir + make -j${HOST_NUM_CPU} + make install + rm -f ${PREFIX}/lib/libz.so* + cd .. +} + +task_register build_libpng +build_libpng() +{ + task_run build_zlib + + S_dir="libpng-${LIBPNG_SOURCE_VERSION}" + B_dir="libpng-${LIBPNG_SOURCE_VERSION}-build" + D_file="$S_dir.tar.xz" + verified_download $D_file $LIBPNG_SOURCE_SHA256 \ + https://download.sourceforge.net/libpng/${D_file} + rm -rf $S_dir $B_dir + tar -xf $D_file + + # libm is part of bionic An android + sed -i -e 's/find_library(M_LIBRARY m)/set(M_LIBRARY "")/' $S_dir/CMakeLists.txt + + # Disable hardware acceleration as they are problematic for Android + # compilation and are not supported by all phones, it is necessary to fiddle + # with CMakeLists.txt as libpng 1.6.37 passing it as cmake options seems not + # working properly + # https://github.com/imagemin/optipng-bin/issues/97 + # https://github.com/opencv/opencv/issues/7600 + echo "add_definitions(-DPNG_ARM_NEON_OPT=0)" >> $S_dir/CMakeLists.txt + + mkdir $B_dir + pushd $B_dir + + HW_OPT="OFF" +# [ "$ANDROID_PLATFORM_VER" -ge "22" ] && HW_OPT="ON" + + andro_cmake \ + -DPNG_SHARED=OFF \ + -DPNG_STATIC=ON \ + -DPNG_TESTS=OFF \ + -DPNG_HARDWARE_OPTIMIZATIONS=$HW_OPT \ + -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \ + -B. -S../$S_dir + make -j${HOST_NUM_CPU} + make install + popd +} + +task_register build_libjpeg +build_libjpeg() +{ + S_dir="jpeg-${LIBJPEG_SOURCE_VERSION}" + D_file="jpegsrc.v${LIBJPEG_SOURCE_VERSION}.tar.gz" + verified_download $D_file $LIBJPEG_SOURCE_SHA256 \ + https://www.ijg.org/files/$D_file + rm -rf $S_dir + tar -xf $D_file + cd $S_dir + ./configure --with-pic --prefix="${PREFIX}" --host=${cArch}-linux + make -j${HOST_NUM_CPU} + make install + rm -f ${PREFIX}/lib/libjpeg.so* + cd .. +} + +task_register build_tiff +build_tiff() +{ + S_dir="tiff-${TIFF_SOURCE_VERSION}" + B_dir="${S_dir}-build" + D_file="tiff-${TIFF_SOURCE_VERSION}.tar.gz" + + verified_download $D_file $TIFF_SOURCE_SHA256 \ + https://download.osgeo.org/libtiff/${D_file} + + rm -rf $S_dir $B_dir + tar -xf $D_file + mkdir $B_dir + + # Disable tools building, not needed for retroshare, and depending on some + # OpenGL headers not available on Android + echo "" > $S_dir/tools/CMakeLists.txt + + # Disable tests building, not needed for retroshare, and causing linker + # errors + echo "" > $S_dir/test/CMakeLists.txt + + # Disable extra tools building, not needed for retroshare, and causing + # linker errors + echo "" > $S_dir/contrib/CMakeLists.txt + + # Disable more unneded stuff + echo "" > $S_dir/build/CMakeLists.txt + echo "" > $S_dir/html/CMakeLists.txt + echo "" > $S_dir/man/CMakeLists.txt + echo "" > $S_dir/port/CMakeLists.txt + + # Change to static library build + sed -i 's\add_library(tiff\add_library(tiff STATIC\' \ + $S_dir/libtiff/CMakeLists.txt + + cd $B_dir + #TODO: build dependecies to support more formats + andro_cmake \ + -Dlibdeflate=OFF -Djbig=OFF -Dlzma=OFF -Dzstd=OFF -Dwebp=OFF \ + -Djpeg12=OFF \ + -Dcxx=OFF \ + -B. -S../$S_dir + make -j${HOST_NUM_CPU} + make install + cd .. +} + +task_register build_cimg +build_cimg() +{ + task_run build_libpng + task_run build_libjpeg + task_run build_tiff + + S_dir="CImg-${CIMG_SOURCE_VERSION}" + D_file="CImg_${CIMG_SOURCE_VERSION}.zip" + + verified_download $D_file $CIMG_SOURCE_SHA256 \ + https://cimg.eu/files/${D_file} + + unzip -o $D_file + + cp --archive --verbose "$S_dir/CImg.h" "$PREFIX/include/" +} + +task_register build_phash +build_phash() +{ + task_run build_cimg + + S_dir="pHash" + B_dir="${S_dir}-build" + + git_source_get "$S_dir" "$PHASH_SOURCE_REPO" "${PHASH_SOURCE_VERSION}" + + rm -rf $B_dir; mkdir $B_dir ; pushd $B_dir + andro_cmake -DPHASH_DYNAMIC=OFF -DPHASH_STATIC=ON -B. -H../pHash + make -j${HOST_NUM_CPU} + make install + popd +} + +task_register fetch_jni_hpp +fetch_jni_hpp() +{ + local rDir="supportlibs/jni.hpp/" + + [ "$(ls "${RS_SRC_DIR}/${rDir}" | wc -l)" -gt "4" ] || + git -C ${RS_SRC_DIR} submodule update --init ${rDir} +} + +task_register build_mvptree +build_mvptree() +{ + S_dir="mvptree" + B_dir="${S_dir}-build" + + git_source_get "$S_dir" "$MVPTREE_SOURCE_REPO" "${MVPTREE_SOURCE_VERSION}" + rm -rf $B_dir; mkdir $B_dir ; pushd $B_dir + andro_cmake -B. -H../${S_dir} + make -j${HOST_NUM_CPU} + make install + popd +} + +task_register build_libretroshare +build_libretroshare() +{ + S_dir="/home/gio/Development/rs-develop/libretroshare" + B_dir="libretroshare-build" + +# -DCMAKE_SYSTEM_NAME="Android" \ +# -DCMAKE_ANDROID_NDK="$ANDROID_NDK_PATH" \ +# -DCMAKE_SYSTEM_VERSION=$ANDROID_PLATFORM_VER \ + +# -D RS_FORUM_DEEP_INDEX=ON -D RS_JSON_API=ON \ + + rm -rf $B_dir; mkdir $B_dir ; pushd $B_dir + andro_cmake -B. -H${S_dir} -DCMAKE_BUILD_TYPE=Release \ + -D RS_ANDROID=ON -D RS_WARN_DEPRECATED=OFF -D RS_WARN_LESS=ON \ + -D RS_LIBRETROSHARE_STATIC=OFF -D RS_LIBRETROSHARE_SHARED=ON \ + -D RS_BRODCAST_DISCOVERY=ON -D RS_EXPORT_JNI_ONLOAD=ON + make -j${HOST_NUM_CPU} + make install + popd +} + +task_register get_native_libs_toolchain_path +get_native_libs_toolchain_path() +{ + echo ${NATIVE_LIBS_TOOLCHAIN_PATH} +} + +task_register build_default_toolchain +build_default_toolchain() +{ + task_run bootstrap_toolchain || return $? + task_run build_bzlib || return $? + task_run build_openssl || return $? + task_run build_sqlcipher || return $? + task_run build_rapidjson || return $? +# task_run build_restbed || return $? +# task_run build_udp-discovery-cpp || return $? + task_run build_xapian || return $? + task_run build_miniupnpc || return $? + task_run build_phash || return $? +# task_run fetch_jni_hpp || return $? + task_run build_libretroshare || return $? +# task_run deduplicate_includes || return $? + task_run get_native_libs_toolchain_path || return $? +} + +if [ "$1" == "" ]; then + rm -rf "$REPORT_DIR" + mkdir -p "$REPORT_DIR" + cat "$0" > "$REPORT_DIR/build_script" + env > "$REPORT_DIR/build_env" + build_default_toolchain +else + # do not delete report directory in this case so we can reuse material + # produced by previous run, like deduplicated includes + mkdir -p "$REPORT_DIR" + while [ "$1" != "" ] ; do + task_zap $1 + task_run $1 || exit $? + shift + done +fi diff --git a/misc/Android/pull_sysroot.sh b/misc/Android/pull_sysroot.sh new file mode 100755 index 000000000..d474610e4 --- /dev/null +++ b/misc/Android/pull_sysroot.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Script to pull debugging sysroot from Android devices +# +# Copyright (C) 2020 Gioacchino Mazzurco +# Copyright (C) 2020 Asociación Civil Altermundi +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with this program. If not, see + +<<"ASCIIDOC" + +Pull files from Android device to prepare a debugging sysroot +Inspired by: +https://fw4spl-org.github.io/fw4spl-blog/2015/07/27/Native-debugging-on-Android-with-QtCreator.html + +The goal is to have a local copy of the sysroot of the connected device. +For that we use the command `adb pull ` + +We will get a copy of: + +- `/system/lib/` +- `/vendor/lib/` +- `libc.so` +- `libcutils.so` + +As well as the binaries `linker` and `app_process` + +IMPORTANT: +from one device to another, the remote file `app_process` can be a binary file +or a symlink to a binary file - which is NOT ok for us. +That's so we will try to pull every known possible variants of `app_process`, +e.g. `app_process32` or `app_process_init` + +ASCIIDOC + + +## Define default value for variable, take two arguments, $1 variable name, +## $2 default variable value, if the variable is not already define define it +## with default value. +function define_default_value() +{ + VAR_NAME="${1}" + DEFAULT_VALUE="${2}" + + [ -z "${!VAR_NAME}" ] && export ${VAR_NAME}="${DEFAULT_VALUE}" +} + +define_default_value ANDROID_SERIAL "$(adb devices | head -n 2 | tail -n 1 | awk '{print $1}')" +define_default_value DEBUG_SYSROOT "${HOME}/Builds/debug_sysroot/${ANDROID_SERIAL}/" + +rm -rf "${DEBUG_SYSROOT}" + +for mDir in "/system/lib/" "/vendor/lib/"; do + mkdir -p "${DEBUG_SYSROOT}/${mDir}" + # adb pull doesn't behave like rsync dirA/ dirB/ so avoid nesting the + # directory by deleting last one before copying + rmdir "${DEBUG_SYSROOT}/${mDir}" + adb pull "${mDir}" "${DEBUG_SYSROOT}/${mDir}" +done + +# Retrieve the specific binaries - some of these adb commands will fail, since +# some files may not exist, but that's ok. + +mkdir -p "${DEBUG_SYSROOT}/system/bin/" +for mBin in "/system/bin/linker" "/system/bin/app_process" \ + "/system/bin/app_process_init" "/system/bin/app_process32" \ + "/system/bin/app_process64" ; do + adb pull "${mBin}" "${DEBUG_SYSROOT}/${mBin}" +done + +# Verify which variants of the specific binaries could be pulled +echo +echo "Found the following specific binaries:" +echo +ls -1 "${DEBUG_SYSROOT}/system/bin/"* +ls -1 "${DEBUG_SYSROOT}/system/lib/libc.so"* +ls -1 "${DEBUG_SYSROOT}/system/lib/libcutils.so"* +echo + +echo DEBUG_SYSROOT="${DEBUG_SYSROOT}" diff --git a/misc/Android/start_gdbserver.sh b/misc/Android/start_gdbserver.sh new file mode 100755 index 000000000..1fd751e67 --- /dev/null +++ b/misc/Android/start_gdbserver.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Script to start gdbserver on Android device and attach to retroshare-service +# +# Copyright (C) 2020 Gioacchino Mazzurco +# Copyright (C) 2020 Asociación Civil Altermundi +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along +# with this program. If not, see + +<<"ASCIIDOC" + +Start gdbserver on Android device and attach it to the retroshare service +process + +Inspired by: +https://fw4spl-org.github.io/fw4spl-blog/2015/07/27/Native-debugging-on-Android-with-QtCreator.html + +ASCIIDOC + + +## Define default value for variable, take two arguments, $1 variable name, +## $2 default variable value, if the variable is not already define define it +## with default value. +function define_default_value() +{ + VAR_NAME="${1}" + DEFAULT_VALUE="${2}" + + [ -z "${!VAR_NAME}" ] && export ${VAR_NAME}="${DEFAULT_VALUE}" || true +} + +define_default_value ANDROID_APK_PACKAGE "org.retroshare.service" +define_default_value ANDROID_PROCESS_NAME "org.retroshare.service:rs" +define_default_value ANDROID_INSTALL_PATH "" +define_default_value LIB_GDB_SERVER_PATH "" +define_default_value ANDROID_SERIAL "$(adb devices | head -n 2 | tail -n 1 | awk '{print $1}')" +define_default_value GDB_SERVER_PORT 5039 + +adb_ushell() +{ + adb shell run-as ${ANDROID_APK_PACKAGE} $@ +} + +[ -z "${ANDROID_INSTALL_PATH}" ] && +{ + ANDROID_INSTALL_PATH="$(adb_ushell pm path "${ANDROID_APK_PACKAGE}")" + ANDROID_INSTALL_PATH="$(dirname ${ANDROID_INSTALL_PATH#"package:"})" + [ -z "${ANDROID_INSTALL_PATH}" ] && + cat < +#include "retroshare/rsturtle.h" #include "util/rstime.h" +#include "rs_android/largefile_retrocompat.hpp" + /* For Thread Behaviour */ const uint32_t DMULTIPLEX_MIN = 10; /* 10 msec sleep */ diff --git a/src/ft/ftfilecreator.cc b/src/ft/ftfilecreator.cc index a5c354092..e38edc571 100644 --- a/src/ft/ftfilecreator.cc +++ b/src/ft/ftfilecreator.cc @@ -19,18 +19,21 @@ * along with this program. If not, see . * * * *******************************************************************************/ -#ifdef WINDOWS_SYS -#include "util/rsstring.h" -#include "util/rswin.h" -#endif + +#include +#include +#include #include "ftfilecreator.h" -#include -#include #include "util/rstime.h" -#include -#include -#include +#include "util/rsdiscspace.h" +#include "util/rsdir.h" +#include "rs_android/largefile_retrocompat.hpp" + +#ifdef WINDOWS_SYS +# include "util/rsstring.h" +# include "util/rswin.h" +#endif /******* * #define FILE_DEBUG 1 diff --git a/src/ft/ftfileprovider.cc b/src/ft/ftfileprovider.cc index 0660a9bca..b789f9057 100644 --- a/src/ft/ftfileprovider.cc +++ b/src/ft/ftfileprovider.cc @@ -19,17 +19,20 @@ * along with this program. If not, see . * * * *******************************************************************************/ -#ifdef WINDOWS_SYS -#include "util/rswin.h" -#endif // WINDOWS_SYS + +#include +#include #include "ftfileprovider.h" #include "ftchunkmap.h" - -#include "util/rsdir.h" -#include -#include #include "util/rstime.h" +#include "util/rsdir.h" +#include "rs_android/largefile_retrocompat.hpp" + +#ifdef WINDOWS_SYS +# include "util/rswin.h" +#endif // WINDOWS_SYS + /******** * #define DEBUG_FT_FILE_PROVIDER 1 diff --git a/src/libretroshare.pro b/src/libretroshare.pro index 413e0b0a8..bbc898934 100644 --- a/src/libretroshare.pro +++ b/src/libretroshare.pro @@ -1093,14 +1093,6 @@ test_bitdht { android-* { lessThan(ANDROID_API_VERSION, 24) { - -## TODO: This probably disable largefile support and maybe is not necessary with -## __ANDROID_API__ >= 24 hence should be made conditional or moved to a -## compatibility header - DEFINES *= "fopen64=fopen" - DEFINES *= "fseeko64=fseeko" - DEFINES *= "ftello64=ftello" - ## @See: rs_android/README-ifaddrs-android.adoc HEADERS += \ rs_android/ifaddrs-android.h \ @@ -1115,6 +1107,7 @@ android-* { PRE_TARGETDEPS += $$pretargetStaticLibs(sLibs) HEADERS += \ + rs_android/largefile_retrocompat.hpp \ rs_android/androidcoutcerrcatcher.hpp \ rs_android/retroshareserviceandroid.hpp \ rs_android/rsjni.hpp diff --git a/src/rs_android/AndroidManifest.xml b/src/rs_android/AndroidManifest.xml new file mode 100644 index 000000000..426c66705 --- /dev/null +++ b/src/rs_android/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/rs_android/largefile_retrocompat.hpp b/src/rs_android/largefile_retrocompat.hpp new file mode 100644 index 000000000..0140011ff --- /dev/null +++ b/src/rs_android/largefile_retrocompat.hpp @@ -0,0 +1,31 @@ +/******************************************************************************* + * * + * libretroshare: retroshare core library * + * * + * Copyright (C) 2016-2021 Gioacchino Mazzurco * + * Copyright (C) 2021 Asociación Civil Altermundi * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ +#pragma once + +#ifdef __ANDROID__ +# include +# if __ANDROID_API__ < 24 +# define fopen64 fopen +# define fseeko64 fseeko +# define ftello64 ftello +# endif +#endif diff --git a/src/rs_android/org/retroshare/service/RetroShareServiceAndroid.java b/src/rs_android/org/retroshare/service/RetroShareServiceAndroid.java index 818f06348..0e3d5ced9 100644 --- a/src/rs_android/org/retroshare/service/RetroShareServiceAndroid.java +++ b/src/rs_android/org/retroshare/service/RetroShareServiceAndroid.java @@ -1,7 +1,8 @@ /* * RetroShare - * Copyright (C) 2016-2021 Gioacchino Mazzurco - * Copyright (C) 2021 Asociación Civil Altermundi + * + * Copyright (C) 2016-2022 Gioacchino Mazzurco + * Copyright (C) 2021-2022 Asociación Civil Altermundi * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -36,7 +37,7 @@ public class RetroShareServiceAndroid extends Service public static final int DEFAULT_JSON_API_PORT = 9092; public static final String DEFAULT_JSON_API_BINDING_ADDRESS = "127.0.0.1"; - static { System.loadLibrary("retroshare-service"); } + static { System.loadLibrary("retroshare"); } public static void start( Context ctx, int jsonApiPort, String jsonApiBindAddress ) diff --git a/src/util/rsdir.cc b/src/util/rsdir.cc index aa7e59299..0f7594f5b 100644 --- a/src/util/rsdir.cc +++ b/src/util/rsdir.cc @@ -27,6 +27,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "util/rsdir.h" #include "util/rsstring.h" @@ -37,16 +46,6 @@ #include "retroshare/rstypes.h" #include "retroshare/rsnotify.h" #include "rsthreads.h" -#include -#include -#include -#include -#include -#include -#include - -#include -#include #if defined(WIN32) || defined(__CYGWIN__) #include "util/rsstring.h" @@ -60,6 +59,8 @@ #define canonicalize_file_name(p) realpath(p, NULL) #endif +#include "rs_android/largefile_retrocompat.hpp" + /**** * #define RSDIR_DEBUG 1 ****/ @@ -580,7 +581,7 @@ bool RsDirUtil::checkCreateDirectory(const std::string& dir) std::string RsDirUtil::removeSymLinks(const std::string& path) { -#if defined(WINDOWS_SYS) || defined(__APPLE__) || defined(__ANDROID__) +#if defined(WINDOWS_SYS) || defined(__APPLE__) //|| defined(__ANDROID__) #warning (Mr.Alice): I don't know how to do this on windows/MacOS/Android. See https://msdn.microsoft.com/en-us/library/windows/desktop/hh707084(v=vs.85).aspx' //if(!S_OK == PathCchCanonicalizeEx(tmp,...) ; return path ;