From e1eec55f627ee93bd56cf1d41e2dfecdb16be754 Mon Sep 17 00:00:00 2001 From: albexk Date: Tue, 14 Nov 2023 22:58:47 +0300 Subject: [PATCH] Refactoring build Move to gradle kotlin DSL Use gradle version catalog All android build parameters are set via cmake files Use gradle abi split to build APKs Improve local development in the android project folder --- .github/workflows/deploy.yml | 104 ++++++++---- CMakeLists.txt | 1 + client/android/build.gradle | 156 +----------------- client/android/build.gradle.kts | 92 +++++++++++ client/android/gradle.properties | 63 ++++--- client/android/gradle/libs.versions.toml | 35 ++++ .../android/gradle/plugins/build.gradle.kts | 33 ++++ .../src/main/kotlin/PropertyDelegate.kt | 49 ++++++ client/android/settings.gradle | 21 --- client/android/settings.gradle.kts | 58 +++++++ client/cmake/android.cmake | 49 ++---- deploy/build_android.sh | 91 ++++++---- 12 files changed, 461 insertions(+), 291 deletions(-) create mode 100644 client/android/build.gradle.kts create mode 100644 client/android/gradle/libs.versions.toml create mode 100644 client/android/gradle/plugins/build.gradle.kts create mode 100644 client/android/gradle/plugins/src/main/kotlin/PropertyDelegate.kt delete mode 100644 client/android/settings.gradle create mode 100644 client/android/settings.gradle.kts 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