diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 719fcd72..a9cd4441 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -273,21 +273,10 @@ jobs: name: 'Build-Android' runs-on: ubuntu-latest - strategy: - matrix: - include: - - abi: 'x86_64' - qt_arch: 'android_x86_64' - - abi: 'x86' - qt_arch: 'android_x86' - - abi: 'armeabi-v7a' - qt_arch: 'android_armv7' - - abi: 'arm64-v8a' - qt_arch: 'android_arm64_v8a' - env: - QT_VERSION: 6.5.2 - ANDROID_BUILD_PLATFORM: android-33 + ANDROID_BUILD_PLATFORM: android-34 + QT_VERSION: 6.5.3 + QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' steps: - name: 'Install desktop Qt' @@ -297,29 +286,58 @@ jobs: host: 'linux' target: 'desktop' arch: 'gcc_64' - modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - setup-python: 'true' - set-env: 'true' extra: '--external 7z --base ${{ env.QT_MIRROR }}' - - name: 'Install android Qt' + - name: 'Install android_x86_64 Qt' uses: jurplel/install-qt-action@v3 with: version: ${{ env.QT_VERSION }} host: 'linux' target: 'android' - arch: ${{ matrix.qt_arch }} - modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + arch: 'android_x86_64' + modules: ${{ env.QT_MODULES }} + dir: ${{ runner.temp }} + extra: '--external 7z --base ${{ env.QT_MIRROR }}' + + - name: 'Install android_x86 Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_x86' + modules: ${{ env.QT_MODULES }} + dir: ${{ runner.temp }} + extra: '--external 7z --base ${{ env.QT_MIRROR }}' + + - name: 'Install android_armv7 Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_armv7' + modules: ${{ env.QT_MODULES }} + dir: ${{ runner.temp }} + extra: '--external 7z --base ${{ env.QT_MIRROR }}' + + - name: 'Install android_arm64_v8a Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_arm64_v8a' + modules: ${{ env.QT_MODULES }} dir: ${{ runner.temp }} - setup-python: 'true' - set-env: 'true' extra: '--external 7z --base ${{ env.QT_MIRROR }}' - name: 'Grant execute permission for qt-cmake' shell: bash run: | - chmod +x ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/${{ matrix.qt_arch }}/bin/qt-cmake + chmod +x ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_x86_64/bin/qt-cmake - name: 'Get sources' uses: actions/checkout@v3 @@ -333,14 +351,14 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: 'gradle' - name: 'Setup Android NDK' id: setup-ndk uses: nttld/setup-ndk@v1 with: - ndk-version: 'r25c' + ndk-version: 'r26b' local-cache: 'true' - name: 'Decode keystore secret to file' @@ -354,16 +372,36 @@ jobs: env: ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} QT_HOST_PATH: ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64 - QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore - QT_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }} - QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} - QT_ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} + ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore + ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }} + ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} shell: bash - run: ./deploy/build_android.sh --apk ${{ matrix.abi }} --platform ${{ env.ANDROID_BUILD_PLATFORM }} + run: ./deploy/build_android.sh --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} - - name: 'Upload apk' + - name: 'Upload x86_64 apk' uses: actions/upload-artifact@v3 with: - name: AmneziaVPN-android-${{ matrix.abi }} - path: deploy/build/AmneziaVPN-${{ matrix.abi }}-release-signed.apk + name: AmneziaVPN-android-x86_64 + path: deploy/build/AmneziaVPN-x86_64-release.apk + retention-days: 7 + + - name: 'Upload x86 apk' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN-android-x86 + path: deploy/build/AmneziaVPN-x86-release.apk + retention-days: 7 + + - name: 'Upload arm64-v8a apk' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN-android-arm64-v8a + path: deploy/build/AmneziaVPN-arm64-v8a-release.apk + retention-days: 7 + + - name: 'Upload armeabi-v7a apk' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN-android-armeabi-v7a + path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk retention-days: 7 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d38a422..084ff3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) +set(APP_ANDROID_VERSION_CODE 39) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/android/build.gradle b/client/android/build.gradle index 3819a4fe..d768000e 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -1,153 +1,5 @@ -apply plugin: 'com.github.ben-manes.versions' - -buildscript { - ext{ - kotlin_version = "1.7.22" - // for libwg - appcompatVersion = '1.1.0' - annotationsVersion = '1.0.1' - databindingVersion = '3.3.1' - jsr305Version = '3.0.2' - streamsupportVersion = '1.7.0' - threetenabpVersion = '1.1.1' - groupName = 'org.amnezia.vpn' - minSdkVer = '24' - cmakeMinVersion = "3.25.0+" - } - - repositories { - google() - jcenter() - mavenCentral() - maven { url = uri("https://jitpack.io") } - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0' - classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlinx-serialization' -apply plugin: 'kotlin-kapt' - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation group: 'org.json', name: 'json', version: '20220924' - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - - implementation "androidx.security:security-crypto:1.1.0-alpha03" - implementation "androidx.security:security-identity-credential:1.0.0-alpha02" - - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" - - coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" - - implementation project(path: ':shadowsocks') - - // CameraX core library using the camera2 implementation - def camerax_version = "1.2.1" - implementation("androidx.camera:camera-core:${camerax_version}") - implementation("androidx.camera:camera-camera2:${camerax_version}") - implementation("androidx.camera:camera-lifecycle:${camerax_version}") - implementation("androidx.camera:camera-view:${camerax_version}") - implementation("androidx.camera:camera-extensions:${camerax_version}") - - def camerax_ml_version = "1.2.0-beta02" - def ml_kit_version = "17.0.3" - implementation("androidx.camera:camera-mlkit-vision:${camerax_ml_version}") - implementation("com.google.mlkit:barcode-scanning:${ml_kit_version}") -} - -androidExtensions { - experimental = true -} - -android { - /******************************************************* - * The following variables: - * - androidBuildToolsVersion, - * - androidCompileSdkVersion - * - qtAndroidDir - holds the path to qt android files - * needed to build any Qt application - * on Android. - * - * are defined in gradle.properties file. This file is - * updated by QtCreator and androiddeployqt tools. - * Changing them manually might break the compilation! - *******************************************************/ - - compileSdkVersion androidCompileSdkVersion.toInteger() - - buildToolsVersion androidBuildToolsVersion - ndkVersion androidNdkVersion - - // Extract native libraries from the APK - packagingOptions.jniLibs.useLegacyPackaging true - - dexOptions { - javaMaxHeapSize "3g" - } - - 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 = ['resources'] - renderscript.srcDirs = ['src'] - assets.srcDirs = ['assets'] - jniLibs.srcDirs = ['libs'] - androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString()) - } - } - - tasks.withType(JavaCompile) { - options.incremental = true - } - - compileOptions { - // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled true - - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 - - lintOptions { - abortOnError false - } - - // Do not compress Qt binary resources file - aaptOptions { - noCompress 'rcc' - } - - defaultConfig { - resConfig "en" - minSdkVersion = 24 - targetSdkVersion = 34 - versionCode 39 // Change to a higher number - versionName "4.1.0" // Change to a higher number - - javaCompileOptions.annotationProcessorOptions.arguments = [ - "room.schemaLocation": "${qtAndroidDir}/schemas".toString() - ] - } - - -} - - +// dummy file for androiddeployqt +// android.bundle.enableUncompressedNativeLibs is deprecated +// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt +useLegacyPackaging diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts new file mode 100644 index 00000000..e7786620 --- /dev/null +++ b/client/android/build.gradle.kts @@ -0,0 +1,92 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + id("property-delegate") +} + +kotlin { + jvmToolchain(17) +} + +// get values from gradle or local properties +val qtTargetSdkVersion: String by gradleProperties +val qtTargetAbiList: String by gradleProperties +val qtAndroidDir: String by gradleProperties + +android { + namespace = "org.amnezia.vpn" + + buildFeatures { + buildConfig = true + viewBinding = true + } + + androidResources { + // don't compress Qt binary resources file + noCompress += "rcc" + } + + packaging { + // compress .so binary libraries + jniLibs.useLegacyPackaging = true + } + + defaultConfig { + applicationId = "org.amnezia.vpn" + targetSdk = qtTargetSdkVersion.toInt() + + // keeps language resources for only the locales specified below + resourceConfigurations += listOf("en", "ru", "b+zh+Hans") + } + + sourceSets { + getByName("main") { + manifest.srcFile("AndroidManifest.xml") + java.setSrcDirs(listOf("$qtAndroidDir/src", "src")) + res.setSrcDirs(listOf("$qtAndroidDir/res", "res")) + assets.setSrcDirs(listOf("assets")) + jniLibs.setSrcDirs(listOf("libs")) + } + } + + signingConfigs { + register("release") { + storeFile = providers.environmentVariable("ANDROID_KEYSTORE_PATH").orNull?.let { file(it) } + storePassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull + keyAlias = providers.environmentVariable("ANDROID_KEYSTORE_KEY_ALIAS").orNull + keyPassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull + } + } + + buildTypes { + release { + // exclude coroutine debug resource from release build + packaging { + resources.excludes += "DebugProbesKt.bin" + } + signingConfig = signingConfigs["release"] + } + } + + splits { + abi { + isEnable = true + reset() + include(*qtTargetAbiList.split(',').toTypedArray()) + isUniversalApk = false + } + } +} + +dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar")))) + implementation(libs.androidx.core) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.security.crypto) + implementation(libs.kotlinx.coroutines) + implementation(libs.bundles.androidx.camera) + implementation(libs.google.mlkit) + implementation(project(":shadowsocks")) + // todo: remove after finish refactoring + implementation(libs.androidx.constraintlayout) +} diff --git a/client/android/gradle.properties b/client/android/gradle.properties index fd9155bc..5a27838c 100644 --- a/client/android/gradle.properties +++ b/client/android/gradle.properties @@ -1,27 +1,46 @@ -# Project-wide Gradle settings. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# Gradle caching allows reusing the build artifacts from a previous -# build with the same inputs. However, over time, the cache size will -# grow. Uncomment the following line to enable it. -#org.gradle.caching=true +# Specifies the JVM arguments used for the daemon process +org.gradle.jvmargs=-Xms512m -Xmx4g -XX:+UseParallelGC -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError \ + -Dfile.encoding=UTF-8 +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.configureondemand=true +# Use AndroidX library instead of a Support Library android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true +# Disable adding android:testOnly attribute to the manifest +android.injected.testOnly=false # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -android.bundle.enableUncompressedNativeLibs=false -androidBuildToolsVersion=30.0.2 -androidCompileSdkVersion=30 -org.gradle.caching=true -org.gradle.parallel=true -android.enableJetifier=true -android.injected.testOnly=false -kapt.use.worker.api=false -kapt.incremental.apt=false +# Disable providing custom values to resources from buildscript by default +android.defaults.buildfeatures.resvalues=false +# Disable compileShaders tasks by default +android.defaults.buildfeatures.shaders=false +# Disable Android resource processing for libraries by default +android.library.defaults.buildfeatures.androidresources=false + +# Qt variables +# At build time androiddeployqt replaces these values with: +# androidCompileSdkVersion - androiddeployqt --android-platform parameter +# androidBuildToolsVersion - QT_ANDROID_SDK_BUILD_TOOLS_REVISION cmake target parameter +# qtMinSdkVersion - QT_ANDROID_MIN_SDK_VERSION cmake target parameter +# qtTargetSdkVersion - QT_ANDROID_TARGET_SDK_VERSION cmake target parameter +# androidNdkVersion - version from ANDROID_NDK_ROOT environment variable +# qtTargetAbiList - qt-cmake QT_ANDROID_ABIS parameter +# qtAndroidDir - path to qt binding java source code +# buildDir - hardcoded "build" value in androiddeployqt + +# For development copy and set local values for these parameters in local.properties +#androidCompileSdkVersion=android-34 +#androidBuildToolsVersion=34.0.0 +#qtMinSdkVersion=24 +#qtTargetSdkVersion=34 +#androidNdkVersion=26.1.10909125 +#qtTargetAbiList=x86_64 +#qtAndroidDir=/QT_BASE/android_ABI/src/android/java +#buildDir=build + +# Note about qtAndroidDir: +# Some IDEs (for example, IntelliJ IDEA) may index all data from a common root of the project and qtAndroidDir. +# Therefore, it's recommended to copy qt android files to a directory inside the project +# and specify the path to that directory in qtAndroidDir. diff --git a/client/android/gradle/libs.versions.toml b/client/android/gradle/libs.versions.toml new file mode 100644 index 00000000..d806b393 --- /dev/null +++ b/client/android/gradle/libs.versions.toml @@ -0,0 +1,35 @@ +[versions] +agp = "8.1.3" +kotlin = "1.9.20" +androidx-core = "1.12.0" +androidx-appcompat = "1.6.1" +androidx-camera = "1.2.3" +androidx-security-crypto = "1.1.0-alpha06" +kotlinx-coroutines = "1.7.3" +google-mlkit = "17.2.0" + +[libraries] +androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" } +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" } +androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" } +androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" } +# todo: remove after finish refactoring +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.1.4" } + +[bundles] +androidx-camera = [ + "androidx-camera-core", + "androidx-camera-lifecycle", + "androidx-camera-view", + "androidx-camera-camera2" +] + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/client/android/gradle/plugins/build.gradle.kts b/client/android/gradle/plugins/build.gradle.kts new file mode 100644 index 00000000..24dcb1ff --- /dev/null +++ b/client/android/gradle/plugins/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} + +kotlin { + jvmToolchain(17) +} + +gradlePlugin { + plugins { + register("settingsGradlePropertyDelegate") { + id = "settings-property-delegate" + implementationClass = "SettingsPropertyDelegate" + } + + register("projectGradlePropertyDelegate") { + id = "property-delegate" + implementationClass = "ProjectPropertyDelegate" + } + } +} + +// stop Gradle running by androiddeployqt +gradle.taskGraph.whenReady { + if (providers.environmentVariable("ANDROIDDEPLOYQT_RUN").isPresent + && !providers.systemProperty("explicitRun").isPresent) { + tasks.forEach { it.enabled = false } + } +} diff --git a/client/android/gradle/plugins/src/main/kotlin/PropertyDelegate.kt b/client/android/gradle/plugins/src/main/kotlin/PropertyDelegate.kt new file mode 100644 index 00000000..c5e721d5 --- /dev/null +++ b/client/android/gradle/plugins/src/main/kotlin/PropertyDelegate.kt @@ -0,0 +1,49 @@ +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader +import java.util.Properties +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.provider.ProviderFactory + +private fun localProperties(rootDir: File) = Properties().apply { + val localProperties = File(rootDir, "local.properties") + if (localProperties.isFile) { + InputStreamReader(FileInputStream(localProperties), Charsets.UTF_8).use { + load(it) + } + } +} + +private class PropertyDelegate( + rootDir: File, + private val providers: ProviderFactory, + private val localProperties: Properties = localProperties(rootDir) +) : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): String = + providers.gradleProperty(property.name).orNull ?: localProperties.getProperty(property.name) +} + +private lateinit var settingsPropertyDelegate: ReadOnlyProperty +private lateinit var projectPropertyDelegate: ReadOnlyProperty + +class SettingsPropertyDelegate : Plugin { + override fun apply(settings: Settings) { + settingsPropertyDelegate = PropertyDelegate(settings.rootDir, settings.providers) + } +} + +class ProjectPropertyDelegate : Plugin { + override fun apply(project: Project) { + projectPropertyDelegate = PropertyDelegate(project.rootDir, project.providers) + } +} + +val Settings.gradleProperties: ReadOnlyProperty + get() = settingsPropertyDelegate + +val Project.gradleProperties: ReadOnlyProperty + get() = projectPropertyDelegate diff --git a/client/android/settings.gradle b/client/android/settings.gradle deleted file mode 100644 index d672e296..00000000 --- a/client/android/settings.gradle +++ /dev/null @@ -1,21 +0,0 @@ -pluginManagement { - repositories { - google() - mavenCentral() - jcenter() - gradlePluginPortal() - maven { url 'https://jitpack.io' } - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - jcenter() - maven { url 'https://jitpack.io' } - } -} - -include ':shadowsocks' diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts new file mode 100644 index 00000000..ace266a1 --- /dev/null +++ b/client/android/settings.gradle.kts @@ -0,0 +1,58 @@ +import com.android.build.api.dsl.SettingsExtension + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + // for jsocks todo: remove after finish refactoring + maven("https://jitpack.io") + } + + includeBuild("./gradle/plugins") +} + +@Suppress("UnstableApiUsage") +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + // for jsocks todo: remove after finish refactoring + maven("https://jitpack.io") + } +} + +includeBuild("./gradle/plugins") + +plugins { + id("com.android.settings") version "8.1.3" + id("settings-property-delegate") +} + +rootProject.name = "AmneziaVPN" +rootProject.buildFileName = "build.gradle.kts" + +include(":shadowsocks") + +// get values from gradle or local properties +val androidBuildToolsVersion: String by gradleProperties +val androidCompileSdkVersion: String by gradleProperties +val androidNdkVersion: String by gradleProperties +val qtMinSdkVersion: String by gradleProperties + +// set default values for all modules +configure { + buildToolsVersion = androidBuildToolsVersion + compileSdk = androidCompileSdkVersion.substringAfter('-').toInt() + minSdk = qtMinSdkVersion.toInt() + ndkVersion = androidNdkVersion +} + +// stop Gradle running by androiddeployqt +gradle.taskGraph.whenReady { + if (providers.environmentVariable("ANDROIDDEPLOYQT_RUN").isPresent + && !providers.systemProperty("explicitRun").isPresent) { + allTasks.forEach { it.enabled = false } + } +} diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index 9440ad10..7511690d 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -1,4 +1,20 @@ message("Client android ${CMAKE_ANDROID_ARCH_ABI} build") + +set(APP_ANDROID_MIN_SDK 24) +set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING + "The minimum API level supported by the application or library" FORCE) + +set_target_properties(${PROJECT} PROPERTIES + QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION} + QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE} + QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK} + QT_ANDROID_TARGET_SDK_VERSION 34 + QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0 + QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android +) + +set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE_BUILD_TYPE") + # We need to include qtprivate api's # As QAndroidBinder is not yet implemented with a public api set(LIBS ${LIBS} Qt6::CorePrivate) @@ -23,39 +39,6 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ) -add_custom_command( - TARGET ${PROJECT} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml - ${CMAKE_CURRENT_SOURCE_DIR}/android/build.gradle - ${CMAKE_CURRENT_SOURCE_DIR}/android/gradle/wrapper/gradle-wrapper.jar - ${CMAKE_CURRENT_SOURCE_DIR}/android/gradle/wrapper/gradle-wrapper.properties - ${CMAKE_CURRENT_SOURCE_DIR}/android/gradlew - ${CMAKE_CURRENT_SOURCE_DIR}/android/gradlew.bat - ${CMAKE_CURRENT_SOURCE_DIR}/android/gradle.properties - ${CMAKE_CURRENT_SOURCE_DIR}/android/res/values/libs.xml - ${CMAKE_CURRENT_SOURCE_DIR}/android/res/xml/fileprovider.xml - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/AuthHelper.java - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/IPCContract.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/NotificationUtil.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/Prefs.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/VPNLogger.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/VPNService.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/VPNServiceBinder.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/qt/AmneziaApp.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/qt/VPNActivity.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt - ${CMAKE_CURRENT_SOURCE_DIR}/android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt - ${CMAKE_CURRENT_BINARY_DIR} -) - -set_property(TARGET ${PROJECT} PROPERTY - QT_ANDROID_PACKAGE_SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/android -) - foreach(abi IN ITEMS ${QT_ANDROID_ABIS}) set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/android/${abi}/libwg.so diff --git a/deploy/build_android.sh b/deploy/build_android.sh index 55da33f1..0c5a80c8 100755 --- a/deploy/build_android.sh +++ b/deploy/build_android.sh @@ -12,13 +12,14 @@ Usage: Build AmneziaVPN android client. By default, a signed Android App Bundle (AAB) is built. Options: - -d, --debug Build debug version - -a, --apk Build APK for the specified ABI - Available ABIs: 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' - -p, --platform The SDK platform used for building the Java code of the application - By default, the latest available platform is used - -m, --move Move the build result to the root of the build directory - -h, --help Display this help + -d, --debug Build debug version + -a, --apk ( | all) Build APKs for the specified ABIs or for all available ABIs + Available ABIs: 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + - list of ABIs delimited by ';' + -b, --build-platform The SDK platform used for building the Java code of the application + By default, the latest available platform is used + -m, --move Move the build result to the root of the build directory + -h, --help Display this help EOT } @@ -26,21 +27,25 @@ EOT BUILD_TYPE="release" AAB=1 -opts=$(getopt -l debug,apk:,platform:,move,help -o "da:p:mh" -- "$@") +opts=$(getopt -l debug,apk:,build-platform:,move,help -o "da:b:mh" -- "$@") eval set -- "$opts" while true; do case "$1" in -d | --debug) BUILD_TYPE="debug"; shift;; - -a | --apk) ABI=$2; unset AAB; shift 2;; - -p | --platform) ANDROID_PLATFORM=$2; shift 2;; + -a | --apk) ABIS=$2; unset AAB; shift 2;; + -b | --build-platform) ANDROID_BUILD_PLATFORM=$2; shift 2;; -m | --move) MOVE_RESULT=1; shift;; -h | --help) usage; exit 0;; --) shift; break;; esac done -if [[ -v ABI && ! "$ABI" =~ ^(x86|x86_64|armeabi-v7a|arm64-v8a)$ ]]; then - echo "The 'abi' option must be one of ['x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'], but is '$ABI'" +# Validate ABIS parameter +if [[ -v ABIS && \ + ! "$ABIS" = "all" && \ + ! "$ABIS" =~ ^((x86|x86_64|armeabi-v7a|arm64-v8a);)*(x86|x86_64|armeabi-v7a|arm64-v8a)$ ]]; then + echo "The 'apk' option must be a list of ['x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a']" \ + "delimited by ';' or 'all', but is '$ABIS'" exit 1 fi @@ -56,13 +61,19 @@ OUT_APP_DIR=$BUILD_DIR/client echo "Project dir: $PROJECT_DIR" echo "Build dir: $BUILD_DIR" -if [ -v AAB ]; then +# Determine path to qt bin folder with qt-cmake +if [[ -v AAB || "$ABIS" = "all" ]]; then qt_bin_dir_suffix="x86_64" else - case $ABI in + if [[ $ABIS = *";"* ]]; then + oneOf=$(echo $ABIS | cut -d';' -f 1) + else + oneOf=$ABIS + fi + case $oneOf in "armeabi-v7a") qt_bin_dir_suffix="armv7";; "arm64-v8a") qt_bin_dir_suffix="arm64_v8a";; - *) qt_bin_dir_suffix=$ABI;; + *) qt_bin_dir_suffix=$oneOf;; esac fi # get real path @@ -79,10 +90,10 @@ echo "Using Android NDK in $ANDROID_NDK_ROOT" # Run qt-cmake to configure build qt_cmake_opts=() -if [ -v AAB ]; then +if [[ -v AAB || "$ABIS" = "all" ]]; then qt_cmake_opts+=(-DQT_ANDROID_BUILD_ALL_ABIS=ON) else - qt_cmake_opts+=(-DQT_ANDROID_ABIS="$ABI") + qt_cmake_opts+=(-DQT_ANDROID_ABIS="$ABIS") fi # QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL=ON - Skip building apks as part of the default 'ALL' target @@ -95,7 +106,7 @@ $QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -B $BUILD_DIR \ # Build app cmake --build $BUILD_DIR --config $BUILD_TYPE -# Build and package APK or AAB. If this is a release, then additionally sign the result. +# Build and package APK or AAB echo "Building APK/AAB..." deployqt_opts=() @@ -104,32 +115,52 @@ if [ -v AAB ]; then deployqt_opts+=(--aab) fi -if [ -v ANDROID_PLATFORM ]; then - deployqt_opts+=(--android-platform "$ANDROID_PLATFORM") +if [ -v ANDROID_BUILD_PLATFORM ]; then + deployqt_opts+=(--android-platform "$ANDROID_BUILD_PLATFORM") fi if [ "$BUILD_TYPE" = "release" ]; then - deployqt_opts+=(--release --sign) + deployqt_opts+=(--release) fi +# for gradle to skip all tasks when it is executed by androiddeployqt +# gradle is started later explicitly +export ANDROIDDEPLOYQT_RUN=1 + $QT_HOST_PATH/bin/androiddeployqt \ --input $OUT_APP_DIR/android-AmneziaVPN-deployment-settings.json \ --output $OUT_APP_DIR/android-build \ - --gradle \ "${deployqt_opts[@]}" +# run gradle +gradle_opts=() + +if [ -v AAB ]; then + gradle_opts+=(bundle"${BUILD_TYPE^}") +else + gradle_opts+=(assemble"${BUILD_TYPE^}") +fi + +$OUT_APP_DIR/android-build/gradlew \ + --project-dir $OUT_APP_DIR/android-build \ + -DexplicitRun=1 \ + "${gradle_opts[@]}" + if [[ -v CI || -v MOVE_RESULT ]]; then echo "Moving APK/AAB..." if [ -v AAB ]; then - mv -u $OUT_APP_DIR/android-build/build/outputs/bundle/$BUILD_TYPE/android-build-$BUILD_TYPE.aab \ - $PROJECT_DIR/deploy/build/AmneziaVPN-$BUILD_TYPE.aab + mv -u $OUT_APP_DIR/android-build/build/outputs/bundle/$BUILD_TYPE/AmneziaVPN-$BUILD_TYPE.aab \ + $PROJECT_DIR/deploy/build/ else - if [ "$BUILD_TYPE" = "release" ]; then - build_suffix="release-signed" - else - build_suffix=$BUILD_TYPE + if [ "$ABIS" = "all" ]; then + ABIS="x86;x86_64;armeabi-v7a;arm64-v8a" fi - mv -u $OUT_APP_DIR/android-build/build/outputs/apk/$BUILD_TYPE/android-build-$build_suffix.apk \ - $PROJECT_DIR/deploy/build/AmneziaVPN-$ABI-$build_suffix.apk + + IFS=';' read -r -a abi_array <<< "$ABIS" + for ABI in "${abi_array[@]}" + do + mv -u $OUT_APP_DIR/android-build/build/outputs/apk/$BUILD_TYPE/AmneziaVPN-$ABI-$BUILD_TYPE.apk \ + $PROJECT_DIR/deploy/build/ + done fi fi