From e55fabdc3252a21daaff049350eee7403277bcb1 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 4 Jan 2024 00:49:32 +0530 Subject: [PATCH] fix: remove product flavors and use APK splits --- .github/workflows/build.yml | 28 ++- .nyx.yml | 6 +- app/release/output-metadata.json | 20 -- .../androidide/activities/AboutActivity.kt | 2 +- .../activities/OnboardingActivity.kt | 6 +- .../IDEBuildConfigProviderImpl.kt | 37 ++-- .../itsaky/androidide/utils/BuildInfoUtils.kt | 2 +- .../app/IDEBuildConfigProviderImplTest.kt | 105 +++++----- build-logic/ide/src/main/java/FDroidConfig.kt | 16 +- .../plugins/AndroidIDEAssetsPlugin.kt | 82 ++------ .../plugins/conf/AndroidModuleConf.kt | 49 ++--- .../plugins/tasks/SetupAapt2Task.kt | 77 +++---- .../configuration/IDEBuildConfigProvider.kt | 17 +- .../androidide/managers/ToolsManager.java | 70 +++---- .../androidide/utils/BootstrapInstaller.java | 194 ------------------ .../androidide/stats/AndroidIDEStats.kt | 2 +- 16 files changed, 200 insertions(+), 513 deletions(-) delete mode 100644 app/release/output-metadata.json delete mode 100644 common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 718dde657..266ce07dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,6 @@ jobs: strategy: matrix: build_type: ['debug', 'release'] - flavor: ['arm64-v8a', 'armeabi-v7a', 'x86_64'] name: Build APK runs-on: ubuntu-latest steps: @@ -74,20 +73,29 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} restore-keys: | ${{ runner.os }}-gradle- - - name: Assemble ${{ matrix.flavor }} ${{ matrix.build_type }} APK + - name: Assemble ${{ matrix.build_type }} APK run: | build_type=${{ matrix.build_type }} - flavor=${{ matrix.flavor }} - ./gradlew :app:assemble${flavor^}${build_type^} - - name: Copy ${{ matrix.flavor }} ${{ matrix.build_type }} APK + ./gradlew :app:assemble${build_type^} + - name: Copy ${{ matrix.build_type }} APK run: | - cp app/build/outputs/apk/${{ matrix.flavor }}/${{ matrix.build_type }}/app-${{ matrix.flavor }}-${{ matrix.flavor }}-${{ matrix.build_type }}.apk ./ - - name: Upload ${{ matrix.flavor }} ${{ matrix.build_type }} APK + cp app/build/outputs/apk/${{ matrix.build_type }}/app-*.apk ./ + - name: Upload arm64-v8a ${{ matrix.build_type }} APK uses: actions/upload-artifact@v3 with: - name: apk-${{ matrix.flavor }}-${{ matrix.build_type }} - path: app-${{ matrix.flavor }}-${{ matrix.flavor }}-${{ matrix.build_type }}.apk - - name: Upload ${{ matrix.flavor }} ${{ matrix.build_type }} mappings + name: apk-arm64-v8a-${{ matrix.build_type }} + path: app-arm64-v8a-${{ matrix.build_type }}.apk + - name: Upload armeabi-v7a ${{ matrix.build_type }} APK + uses: actions/upload-artifact@v3 + with: + name: apk-armeabi-v7a-${{ matrix.build_type }} + path: app-armeabi-v7a-${{ matrix.build_type }}.apk + - name: Upload x86_64 ${{ matrix.build_type }} APK + uses: actions/upload-artifact@v3 + with: + name: apk-x86_64-${{ matrix.build_type }} + path: app-x86_64-${{ matrix.build_type }}.apk + - name: Upload ${{ matrix.build_type }} mappings if: ${{ matrix.build_type == 'release' }} uses: actions/upload-artifact@v3 with: diff --git a/.nyx.yml b/.nyx.yml index 5232d21b0..f0542539b 100644 --- a/.nyx.yml +++ b/.nyx.yml @@ -17,17 +17,17 @@ releaseAssets: apk_arm64: fileName: "androidide-{{version}}-arm64-v8a.apk" description: "AndroidIDE {{version}} arm64-v8a release APK" - path: app-arm64-v8a-arm64-v8a-release.apk + path: app-arm64-v8a-release.apk type: application/octet-stream apk_x86_64: fileName: "androidide-{{version}}-x86_64.apk" description: "AndroidIDE {{version}} x86_64 release APK" - path: app-x86_64-x86_64-release.apk + path: app-x86_64-release.apk type: application/octet-stream apk_arm: fileName: "androidide-{{version}}-armeabi-v7a.apk" description: "AndroidIDE {{version}} armeabi-v7a release APK" - path: app-armeabi-v7a-armeabi-v7a-release.apk + path: app-armeabi-v7a-release.apk type: application/octet-stream releaseTypes: diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json deleted file mode 100644 index 8d7a2b9df..000000000 --- a/app/release/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "com.itsaky.androidide", - "variantName": "release", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "versionCode": 230, - "versionName": "v2.3.0-beta.internal.1+branch.main.commit.9ef92d5.timestamp.20230315180430", - "outputFile": "app-release.apk" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/activities/AboutActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/AboutActivity.kt index 85901fb40..d45221cc8 100755 --- a/app/src/main/java/com/itsaky/androidide/activities/AboutActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/AboutActivity.kt @@ -204,7 +204,7 @@ class AboutActivity : LimitlessIDEActivity() { builder.append("v") builder.append(BuildInfo.VERSION_NAME_SIMPLE) builder.append("-") - builder.append(BuildConfig.FLAVOR) + builder.append(BuildConfig.ABI_CURRENT) builder.append(" ") val colorPositive = ContextCompat.getColor(this, R.color.color_success) diff --git a/app/src/main/java/com/itsaky/androidide/activities/OnboardingActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/OnboardingActivity.kt index 0efe4dc7e..232cbddae 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/OnboardingActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/OnboardingActivity.kt @@ -177,7 +177,7 @@ class OnboardingActivity : AppIntro2() { getString(string.title_unsupported_device), getString( string.msg_unsupported_device, - configProvider.flavorArch.abi, + configProvider.cpuArch.abi, configProvider.deviceArch.abi ), R.drawable.ic_alert, @@ -186,14 +186,14 @@ class OnboardingActivity : AppIntro2() { return false } - if (configProvider.flavorArch != configProvider.deviceArch) { + if (configProvider.cpuArch != configProvider.deviceArch) { // IDE's build flavor is NOT the primary arch of the device // warn the user if (!archConfigExperimentalWarningIsShown()) { addSlide(OnboardingInfoFragment.newInstance( getString(string.title_experiment_flavor), getString(string.msg_experimental_flavor, - configProvider.flavorArch.abi, + configProvider.cpuArch.abi, configProvider.deviceArch.abi ), R.drawable.ic_alert, diff --git a/app/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProviderImpl.kt b/app/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProviderImpl.kt index c8daa0c69..67a955698 100644 --- a/app/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProviderImpl.kt +++ b/app/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProviderImpl.kt @@ -20,6 +20,7 @@ package com.itsaky.androidide.app.configuration import android.os.Build import com.google.auto.service.AutoService import com.itsaky.androidide.BuildConfig +import com.itsaky.androidide.app.IDEApplication /** * @author Akash Yadav @@ -28,35 +29,23 @@ import com.itsaky.androidide.BuildConfig @Suppress("UNUSED") open class IDEBuildConfigProviderImpl : IDEBuildConfigProvider { - override val buildFlavor: String - get() = BuildConfig.FLAVOR + override val cpuAbiName: String by lazy { + val applicationInfo = IDEApplication.instance.applicationInfo!! - override val flavorArch: CpuArch - get() = CpuArch.forAbi(buildFlavor)!! + // transform to valid ABI names + return@lazy when (val abi = applicationInfo.nativeLibraryDir!!.substringAfterLast('/')) { + "arm64" -> BuildConfig.ABI_ARM64_V8A + "arm" -> BuildConfig.ABI_ARMEABI_V7A + else -> abi + } + } + + override val cpuArch: CpuArch + get() = CpuArch.forAbi(cpuAbiName)!! override val deviceArch: CpuArch get() = CpuArch.forAbi(supportedAbis[0])!! override val supportedAbis: Array get() = Build.SUPPORTED_ABIS - - override val supportedBuildFlavors: Array by lazy { - val flavors = mutableListOf() - if (supportedAbis.contains(BuildConfig.FLAVOR_ARM64_V8A)) { - flavors.add(BuildConfig.FLAVOR_ARM64_V8A) - } - if (supportedAbis.contains(BuildConfig.FLAVOR_X86_64)) { - flavors.add(BuildConfig.FLAVOR_X86_64) - } - if (supportedAbis.contains(BuildConfig.FLAVOR_ARMEABI_V7A)) { - flavors.add(BuildConfig.FLAVOR_ARMEABI_V7A) - } - return@lazy flavors.toTypedArray() - } - - companion object { - private fun Array?.checkIsPrimaryArch(arch: String): Boolean { - return this?.isNotEmpty() == true && this[0] == arch - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/utils/BuildInfoUtils.kt b/app/src/main/java/com/itsaky/androidide/utils/BuildInfoUtils.kt index 1f1d921c4..577b43de2 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/BuildInfoUtils.kt +++ b/app/src/main/java/com/itsaky/androidide/utils/BuildInfoUtils.kt @@ -39,7 +39,7 @@ object BuildInfoUtils { "CI Build" to BuildInfo.CI_BUILD, "Branch" to BuildInfo.CI_GIT_BRANCH, "Commit" to BuildInfo.CI_GIT_COMMIT_HASH, - "Variant" to "${BuildConfig.FLAVOR} (${BuildConfig.BUILD_TYPE})", + "Variant" to "${BuildConfig.ABI_CURRENT} (${BuildConfig.BUILD_TYPE})", "Build type" to getBuildType(), "SDK Version" to Build.VERSION.SDK_INT, "Supported ABIs" to "[${Build.SUPPORTED_ABIS.joinToString(separator = ", ")}]", diff --git a/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt b/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt index 14e85b2df..61f2400c7 100644 --- a/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt +++ b/app/src/test/java/com/itsaky/androidide/app/IDEBuildConfigProviderImplTest.kt @@ -18,10 +18,9 @@ package com.itsaky.androidide.app import com.google.common.truth.Truth.assertThat -import com.itsaky.androidide.BuildConfig -import com.itsaky.androidide.BuildConfig.FLAVOR_ARM64_V8A -import com.itsaky.androidide.BuildConfig.FLAVOR_ARMEABI_V7A -import com.itsaky.androidide.BuildConfig.FLAVOR_X86_64 +import com.itsaky.androidide.BuildConfig.ABI_ARM64_V8A +import com.itsaky.androidide.BuildConfig.ABI_ARMEABI_V7A +import com.itsaky.androidide.BuildConfig.ABI_X86_64 import com.itsaky.androidide.app.configuration.CpuArch import com.itsaky.androidide.app.configuration.IDEBuildConfigProviderImpl import org.junit.Test @@ -31,18 +30,18 @@ import org.junit.Test */ class IDEBuildConfigProviderImplTest { - class TestBuildConfigProvider(override val buildFlavor: String, + class TestBuildConfigProvider(override val cpuAbiName: String, override val supportedAbis: Array) : IDEBuildConfigProviderImpl() @Test fun `test aarch64 build on aarch64-only device`() { - TestBuildConfigProvider(FLAVOR_ARM64_V8A, arrayOf(FLAVOR_ARM64_V8A)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARM64_V8A) - assertThat(flavorArch).isEqualTo(CpuArch.AARCH64) + TestBuildConfigProvider(ABI_ARM64_V8A, arrayOf(ABI_ARM64_V8A)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARM64_V8A) + assertThat(cpuArch).isEqualTo(CpuArch.AARCH64) assertThat(deviceArch).isEqualTo(CpuArch.AARCH64) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_ARM64_V8A) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_ARM64_V8A) + assertThat(supportedAbis.toList()).containsExactly(ABI_ARM64_V8A) + assertThat(isArm64v8aBuild()).isTrue() assertThat(isX86_64Build()).isFalse() @@ -53,19 +52,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isFalse() assertThat(supportsBuildFlavor()).isTrue() - assertThat(flavorArch).isEqualTo(deviceArch) + assertThat(cpuArch).isEqualTo(deviceArch) } } @Test fun `test arm build on arm-only device`() { - TestBuildConfigProvider(FLAVOR_ARMEABI_V7A, arrayOf(FLAVOR_ARMEABI_V7A)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARMEABI_V7A) - assertThat(flavorArch).isEqualTo(CpuArch.ARM) + TestBuildConfigProvider(ABI_ARMEABI_V7A, arrayOf(ABI_ARMEABI_V7A)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARMEABI_V7A) + assertThat(cpuArch).isEqualTo(CpuArch.ARM) assertThat(deviceArch).isEqualTo(CpuArch.ARM) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_ARMEABI_V7A) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_ARMEABI_V7A) + assertThat(supportedAbis.toList()).containsExactly(ABI_ARMEABI_V7A) + assertThat(isArm64v8aBuild()).isFalse() assertThat(isX86_64Build()).isFalse() @@ -76,19 +75,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isTrue() assertThat(supportsBuildFlavor()).isTrue() - assertThat(flavorArch).isEqualTo(deviceArch) + assertThat(cpuArch).isEqualTo(deviceArch) } } @Test fun `test x86_64 build on x86_64-only device`() { - TestBuildConfigProvider(FLAVOR_X86_64, arrayOf(FLAVOR_X86_64)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_X86_64) - assertThat(flavorArch).isEqualTo(CpuArch.X86_64) + TestBuildConfigProvider(ABI_X86_64, arrayOf(ABI_X86_64)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_X86_64) + assertThat(cpuArch).isEqualTo(CpuArch.X86_64) assertThat(deviceArch).isEqualTo(CpuArch.X86_64) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_X86_64) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_X86_64) + assertThat(supportedAbis.toList()).containsExactly(ABI_X86_64) + assertThat(isArm64v8aBuild()).isFalse() assertThat(isX86_64Build()).isTrue() @@ -99,19 +98,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isFalse() assertThat(supportsBuildFlavor()).isTrue() - assertThat(flavorArch).isEqualTo(deviceArch) + assertThat(cpuArch).isEqualTo(deviceArch) } } @Test fun `test arm build on (aarch64,arm) device`() { - TestBuildConfigProvider(FLAVOR_ARMEABI_V7A, arrayOf(FLAVOR_ARM64_V8A, FLAVOR_ARMEABI_V7A)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARMEABI_V7A) - assertThat(flavorArch).isEqualTo(CpuArch.ARM) + TestBuildConfigProvider(ABI_ARMEABI_V7A, arrayOf(ABI_ARM64_V8A, ABI_ARMEABI_V7A)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARMEABI_V7A) + assertThat(cpuArch).isEqualTo(CpuArch.ARM) assertThat(deviceArch).isEqualTo(CpuArch.AARCH64) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_ARM64_V8A, FLAVOR_ARMEABI_V7A) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_ARM64_V8A, FLAVOR_ARMEABI_V7A) + assertThat(supportedAbis.toList()).containsExactly(ABI_ARM64_V8A, ABI_ARMEABI_V7A) + assertThat(isArm64v8aBuild()).isFalse() assertThat(isX86_64Build()).isFalse() @@ -122,19 +121,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isFalse() assertThat(supportsBuildFlavor()).isTrue() - assertThat(flavorArch).isNotEqualTo(deviceArch) + assertThat(cpuArch).isNotEqualTo(deviceArch) } } @Test fun `test arm build on (x86_64,arm) device`() { - TestBuildConfigProvider(FLAVOR_ARMEABI_V7A, arrayOf(FLAVOR_X86_64, FLAVOR_ARMEABI_V7A)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARMEABI_V7A) - assertThat(flavorArch).isEqualTo(CpuArch.ARM) + TestBuildConfigProvider(ABI_ARMEABI_V7A, arrayOf(ABI_X86_64, ABI_ARMEABI_V7A)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARMEABI_V7A) + assertThat(cpuArch).isEqualTo(CpuArch.ARM) assertThat(deviceArch).isEqualTo(CpuArch.X86_64) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_X86_64, FLAVOR_ARMEABI_V7A) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_X86_64, FLAVOR_ARMEABI_V7A) + assertThat(supportedAbis.toList()).containsExactly(ABI_X86_64, ABI_ARMEABI_V7A) + assertThat(isArm64v8aBuild()).isFalse() assertThat(isX86_64Build()).isFalse() @@ -145,19 +144,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isFalse() assertThat(supportsBuildFlavor()).isTrue() - assertThat(flavorArch).isNotEqualTo(deviceArch) + assertThat(cpuArch).isNotEqualTo(deviceArch) } } @Test fun `test aarch64 build on arm-only device`() { - TestBuildConfigProvider(FLAVOR_ARM64_V8A, arrayOf(FLAVOR_ARMEABI_V7A)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARM64_V8A) - assertThat(flavorArch).isEqualTo(CpuArch.AARCH64) + TestBuildConfigProvider(ABI_ARM64_V8A, arrayOf(ABI_ARMEABI_V7A)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARM64_V8A) + assertThat(cpuArch).isEqualTo(CpuArch.AARCH64) assertThat(deviceArch).isEqualTo(CpuArch.ARM) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_ARMEABI_V7A) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_ARMEABI_V7A) + assertThat(supportedAbis.toList()).containsExactly(ABI_ARMEABI_V7A) + assertThat(isArm64v8aBuild()).isTrue() assertThat(isX86_64Build()).isFalse() @@ -168,19 +167,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isTrue() assertThat(supportsBuildFlavor()).isFalse() - assertThat(flavorArch).isNotEqualTo(deviceArch) + assertThat(cpuArch).isNotEqualTo(deviceArch) } } @Test fun `test aarch64 build on x86_64-only device`() { - TestBuildConfigProvider(FLAVOR_ARM64_V8A, arrayOf(FLAVOR_X86_64)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARM64_V8A) - assertThat(flavorArch).isEqualTo(CpuArch.AARCH64) + TestBuildConfigProvider(ABI_ARM64_V8A, arrayOf(ABI_X86_64)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARM64_V8A) + assertThat(cpuArch).isEqualTo(CpuArch.AARCH64) assertThat(deviceArch).isEqualTo(CpuArch.X86_64) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_X86_64) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_X86_64) + assertThat(supportedAbis.toList()).containsExactly(ABI_X86_64) + assertThat(isArm64v8aBuild()).isTrue() assertThat(isX86_64Build()).isFalse() @@ -191,19 +190,19 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isFalse() assertThat(supportsBuildFlavor()).isFalse() - assertThat(flavorArch).isNotEqualTo(deviceArch) + assertThat(cpuArch).isNotEqualTo(deviceArch) } } @Test fun `test aarch64 build on (x86_64,aarch64) device`() { - TestBuildConfigProvider(FLAVOR_ARM64_V8A, arrayOf(FLAVOR_X86_64, FLAVOR_ARM64_V8A)).apply { - assertThat(buildFlavor).isEqualTo(FLAVOR_ARM64_V8A) - assertThat(flavorArch).isEqualTo(CpuArch.AARCH64) + TestBuildConfigProvider(ABI_ARM64_V8A, arrayOf(ABI_X86_64, ABI_ARM64_V8A)).apply { + assertThat(cpuAbiName).isEqualTo(ABI_ARM64_V8A) + assertThat(cpuArch).isEqualTo(CpuArch.AARCH64) assertThat(deviceArch).isEqualTo(CpuArch.X86_64) - assertThat(supportedAbis.toList()).containsExactly(FLAVOR_X86_64, FLAVOR_ARM64_V8A) - assertThat(supportedBuildFlavors.toList()).containsExactly(FLAVOR_X86_64, FLAVOR_ARM64_V8A) + assertThat(supportedAbis.toList()).containsExactly(ABI_X86_64, ABI_ARM64_V8A) + assertThat(isArm64v8aBuild()).isTrue() assertThat(isX86_64Build()).isFalse() @@ -214,7 +213,7 @@ class IDEBuildConfigProviderImplTest { assertThat(isArmeabiv7aDevice()).isFalse() assertThat(supportsBuildFlavor()).isTrue() - assertThat(flavorArch).isNotEqualTo(deviceArch) + assertThat(cpuArch).isNotEqualTo(deviceArch) } } } \ No newline at end of file diff --git a/build-logic/ide/src/main/java/FDroidConfig.kt b/build-logic/ide/src/main/java/FDroidConfig.kt index 58f116da4..6f14a7938 100644 --- a/build-logic/ide/src/main/java/FDroidConfig.kt +++ b/build-logic/ide/src/main/java/FDroidConfig.kt @@ -39,14 +39,16 @@ object FDroidConfig { var fDroidVersionCode: Int? = null private set - var fDroidAapt2File: String? = null + var aapt2Files: Map = emptyMap() private set const val PROP_FDROID_BUILD = "ide.build.fdroid" const val PROP_FDROID_BUILD_ARCH = "ide.build.fdroid.arch" const val PROP_FDROID_BUILD_VERSION = "ide.build.fdroid.version" const val PROP_FDROID_BUILD_VERCODE = "ide.build.fdroid.vercode" - const val PROP_FDROID_AAPT2FILE = "ide.build.fdroid.aapt2File" + const val PROP_FDROID_AAPT2FILE_ARM64 = "ide.build.fdroid.aapt2File.arm64" + const val PROP_FDROID_AAPT2FILE_ARM = "ide.build.fdroid.aapt2File.arm" + const val PROP_FDROID_AAPT2FILE_X86_64 = "ide.build.fdroid.aapt2File.x86_64" fun load(project: Project) { val propsFile = File(project.rootDir, "fdroid.properties") @@ -67,7 +69,13 @@ object FDroidConfig { fDroidBuildArch = properties.getProperty(PROP_FDROID_BUILD_ARCH, null) fDroidVersionName = properties.getProperty(PROP_FDROID_BUILD_VERSION, null) - fDroidVersionCode = properties.getProperty(PROP_FDROID_BUILD_VERCODE, null)?.toInt() - fDroidAapt2File = properties.getProperty(PROP_FDROID_AAPT2FILE, null) + fDroidVersionCode = properties.getProperty(PROP_FDROID_BUILD_VERCODE, null)?.toInt() + + aapt2Files = mutableMapOf().also { files -> + files[PROP_FDROID_AAPT2FILE_ARM64] = properties.getProperty(PROP_FDROID_AAPT2FILE_ARM64, null) + files[PROP_FDROID_AAPT2FILE_ARM] = properties.getProperty(PROP_FDROID_AAPT2FILE_ARM, null) + files[PROP_FDROID_AAPT2FILE_X86_64] = properties.getProperty(PROP_FDROID_AAPT2FILE_X86_64, + null) + } } } \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt index 2c3612482..e266e0393 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt @@ -19,8 +19,7 @@ package com.itsaky.androidide.plugins import BuildConfig import VersionUtils -import com.android.build.api.variant.AndroidComponentsExtension -import com.android.build.gradle.BaseExtension +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.itsaky.androidide.plugins.tasks.AddAndroidJarToAssetsTask import com.itsaky.androidide.plugins.tasks.AddFileToAssetsTask import com.itsaky.androidide.plugins.tasks.GenerateInitScriptTask @@ -30,7 +29,6 @@ import com.itsaky.androidide.plugins.util.SdkUtils.getAndroidJar import downloadVersion import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.tasks.TaskProvider import org.gradle.configurationcache.extensions.capitalized /** @@ -40,25 +38,15 @@ import org.gradle.configurationcache.extensions.capitalized */ class AndroidIDEAssetsPlugin : Plugin { - companion object { - - private val AAPT2_CHECKSUMS = mapOf( - "arm64-v8a" to "be2cea61814678f7a9e61bf818a6666e6097a7d67d6c19498a4d7aa690bc4151", - "armeabi-v7a" to "ba3413c680933dffd3c3d35da8d450c474ff5ccab95c4b9db28841c53b7a3cdf", - "x86_64" to "4861171c1efcffe41f4466937e6a392b243ffb014813b4e60f0b77bb46ab254d" - ) - } - override fun apply(target: Project) { target.run { val wrapperGeneratorTaskProvider = tasks.register("generateGradleWrapper", - GradleWrapperGeneratorTask::class.java - ) + GradleWrapperGeneratorTask::class.java) - val androidComponentsExtension = extensions.getByType(AndroidComponentsExtension::class.java) - val baseExtension = extensions.getByType(BaseExtension::class.java) + val androidComponentsExtension = extensions.getByType( + ApplicationAndroidComponentsExtension::class.java) - val aapt2Tasks = getAapt2SetupTasks(baseExtension) + val setupAapt2TaskTaskProvider = tasks.register("setupAapt2", SetupAapt2Task::class.java) val addAndroidJarTaskProvider = tasks.register("addAndroidJarToAssets", AddAndroidJarToAssetsTask::class.java) { @@ -67,15 +55,11 @@ class AndroidIDEAssetsPlugin : Plugin { androidComponentsExtension.onVariants { variant -> - val aapt2SetupTaskProvider = aapt2Tasks[variant.flavorName] - ?: throw IllegalStateException( - "'aapt2' task not registered for flavor '${variant.flavorName}'") - - variant.sources.jniLibs?.addGeneratedSourceDirectory(aapt2SetupTaskProvider, - SetupAapt2Task::outputDirectory) - val variantNameCapitalized = variant.name.capitalized() + variant.sources.jniLibs?.addGeneratedSourceDirectory(setupAapt2TaskTaskProvider, + SetupAapt2Task::outputDirectory) + variant.sources.assets?.addGeneratedSourceDirectory(wrapperGeneratorTaskProvider, GradleWrapperGeneratorTask::outputDirectory) @@ -83,64 +67,32 @@ class AndroidIDEAssetsPlugin : Plugin { AddAndroidJarToAssetsTask::outputDirectory) // Init script generator - val generateInitScript = tasks.register( - "generate${variantNameCapitalized}InitScript", - GenerateInitScriptTask::class.java - ) { + val generateInitScript = tasks.register("generate${variantNameCapitalized}InitScript", + GenerateInitScriptTask::class.java) { mavenGroupId.set(BuildConfig.packageName) snapshotsRepository.set(VersionUtils.SNAPSHOTS_REPO) downloadVersion.set(this@run.downloadVersion) } - variant.sources.assets?.addGeneratedSourceDirectory( - generateInitScript, - GenerateInitScriptTask::outputDir - ) + variant.sources.assets?.addGeneratedSourceDirectory(generateInitScript, + GenerateInitScriptTask::outputDir) // Tooling API JAR copier - val copyToolingApiJar = tasks.register( - "copy${variantNameCapitalized}ToolingApiJar", - AddFileToAssetsTask::class.java - ) { + val copyToolingApiJar = tasks.register("copy${variantNameCapitalized}ToolingApiJar", + AddFileToAssetsTask::class.java) { val toolingApi = rootProject.findProject(":subprojects:tooling-api-impl")!! dependsOn(toolingApi.tasks.getByName("copyJar")) - val toolingApiJar = toolingApi.layout.buildDirectory.file( - "libs/tooling-api-all.jar") + val toolingApiJar = toolingApi.layout.buildDirectory.file("libs/tooling-api-all.jar") inputFile.set(toolingApiJar) baseAssetsPath.set("data/common") } - variant.sources.assets?.addGeneratedSourceDirectory( - copyToolingApiJar, - AddFileToAssetsTask::outputDirectory - ) + variant.sources.assets?.addGeneratedSourceDirectory(copyToolingApiJar, + AddFileToAssetsTask::outputDirectory) } } } - - private fun Project.getAapt2SetupTasks( - baseExtension: BaseExtension - ): Map> { - - val aapt2Tasks = mutableMapOf>() - - baseExtension.productFlavors.forEach { flavor -> - - val task = tasks.register( - "setupAapt2${flavor.name.capitalized()}", - SetupAapt2Task::class.java - ) { - arch.set(flavor.name) - checksum.set(AAPT2_CHECKSUMS[flavor.name] ?: throw IllegalStateException( - "Checksum for aapt2-${flavor.name} not found!")) - } - - aapt2Tasks[flavor.name] = task - } - - return aapt2Tasks - } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt index bb81ea4a4..2cf803de6 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt @@ -19,9 +19,11 @@ package com.itsaky.androidide.plugins.conf import BuildConfig import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.api.variant.FilterConfiguration +import com.android.build.api.variant.impl.getFilter import com.android.build.gradle.BaseExtension import com.itsaky.androidide.plugins.util.SdkUtils.getAndroidJar -import isFDroidBuild import org.gradle.api.Project import org.gradle.api.artifacts.Dependency import projectVersionCode @@ -36,6 +38,7 @@ import projectVersionCode * flavor, the version code will be `100 * 270 + 1` i.e. `2701` */ internal val flavorsAbis = mapOf("arm64-v8a" to 1, "armeabi-v7a" to 2, "x86_64" to 3) +private const val BUILD_CONFIG_ABI_FIELD_BASE_NAME = "ABI_" private val disableCoreLibDesugaringForModules = arrayOf(":logsender", ":logger") fun Project.configureAndroidModule( @@ -78,21 +81,28 @@ fun Project.configureAndroidModule( configureCoreLibDesugaring(this, coreLibDesugDep) if (":app" == project.path) { - flavorDimensions("default") - flavorsAbis.forEach { (abi, _) -> // the common defaultConfig, not the flavor-specific defaultConfig.buildConfigField("String", - "FLAVOR_${abi.replace('-', '_').uppercase()}", + "${BUILD_CONFIG_ABI_FIELD_BASE_NAME}${abi.replace('-', '_').uppercase()}", "\"${abi}\"") } - productFlavors { - val fdroidSuffix = if (isFDroidBuild) "-fdroid" else "" - flavorsAbis.forEach { (abi, verCodeIncrement) -> - val flavor = create(abi) - flavor.versionNameSuffix = "-${abi}${fdroidSuffix}" - flavor.versionCode = 100 * projectVersionCode + verCodeIncrement + splits { + abi { + reset() + isEnable = true + isUniversalApk = true + include(*flavorsAbis.keys.toTypedArray()) + } + } + + extensions.getByType(ApplicationAndroidComponentsExtension::class.java).apply { + onVariants { variant -> + variant.outputs.forEach { output -> + val verCodeIncr = flavorsAbis[output.getFilter(FilterConfiguration.FilterType.ABI)?.identifier] ?: 0 + output.versionCode.set(100 * projectVersionCode + verCodeIncr) + } } } } else { @@ -104,25 +114,6 @@ fun Project.configureAndroidModule( } } - // configure split APKs for ':app' module only - if (this@configureAndroidModule == rootProject.findProject(":app")) { - splits { - abi { - reset() - - isEnable = true - isUniversalApk = false - - // TODO: Find a way to enable split APKs in product flavors. If this is possible, we can configure - // each flavor to include only a single ABI. For example, for the 'arm64-v8a' flavor, - // we can configure it to generate APK only for 'arm64-v8a'. - // - // See the contribution guidelines for more information. - include(*flavorsAbis.keys.toTypedArray()) - } - } - } - buildTypes.getByName("debug") { isMinifyEnabled = false } buildTypes.getByName("release") { isMinifyEnabled = true diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/SetupAapt2Task.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/SetupAapt2Task.kt index 6960ae97a..8949fe9ae 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/SetupAapt2Task.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/SetupAapt2Task.kt @@ -17,11 +17,10 @@ package com.itsaky.androidide.plugins.tasks +import FDroidConfig import com.itsaky.androidide.plugins.util.DownloadUtils import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import java.io.File @@ -31,73 +30,49 @@ import java.io.File */ abstract class SetupAapt2Task : DefaultTask() { - /** - * The CPU architecture for which the `aapt2` should be downloaded. - */ - @get:Input - abstract val arch: Property - - /** - * The version of the file, basically the tag name of the release. - */ - @get:Input - abstract val version: Property - - /** - * The SHA-256 checksum of the file to download. This will be used to verify the file's - * integrity. - */ - @get:Input - abstract val checksum: Property - - /** - * The path of the static `aapt2` file which should be used instead of downloading it from GitHub. - */ - @get:Input - abstract val staticAapt2: Property - /** * The output directory. */ @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - init { - run { - version.convention(DEFAULT_VERSION) - staticAapt2.convention("") - } - } - companion object { + private val AAPT2_CHECKSUMS = mapOf( + "arm64-v8a" to "be2cea61814678f7a9e61bf818a6666e6097a7d67d6c19498a4d7aa690bc4151", + "armeabi-v7a" to "ba3413c680933dffd3c3d35da8d450c474ff5ccab95c4b9db28841c53b7a3cdf", + "x86_64" to "4861171c1efcffe41f4466937e6a392b243ffb014813b4e60f0b77bb46ab254d" + ) + private const val DEFAULT_VERSION = "34.0.4" private const val AAPT2_DOWNLOAD_URL = "https://github.com/AndroidIDEOfficial/platform-tools/releases/download/v%1\$s/aapt2-%2\$s" } @TaskAction fun setupAapt2() { - val file = outputDirectory.file("${arch.get()}/libaapt2.so").get().asFile - file.parentFile.deleteRecursively() - file.parentFile.mkdirs() - if (staticAapt2.getOrElse("").isNotBlank()) { - val aapt2 = File(staticAapt2.get()) + AAPT2_CHECKSUMS.forEach { (arch, checksum) -> + val file = outputDirectory.file("${arch}/libaapt2.so").get().asFile + file.parentFile.deleteRecursively() + file.parentFile.mkdirs() - require(aapt2.exists() && aapt2.isFile) { - "F-Droid AAPT2 file does not exist or is not a file: $aapt2" + if (FDroidConfig.isFDroidBuild) { + val aapt2File = requireNotNull(FDroidConfig.aapt2Files[arch]) { + "F-Droid build is enabled but path to AAPT2 file for $arch is not set." + } + + val aapt2 = File(aapt2File) + + require(aapt2.exists() && aapt2.isFile) { + "F-Droid AAPT2 file does not exist or is not a file: $aapt2" + } + + aapt2.copyTo(file, overwrite = true) + return } - aapt2.copyTo(file, overwrite = true) - return + val remoteUrl = AAPT2_DOWNLOAD_URL.format(DEFAULT_VERSION, arch) + DownloadUtils.doDownload(file, remoteUrl, checksum, logger) } - - val arch = this.arch.get() - val version = this.version.get() - val expectedChecksum = this.checksum.get() - - val remoteUrl = AAPT2_DOWNLOAD_URL.format(version, arch) - - DownloadUtils.doDownload(file, remoteUrl, expectedChecksum, logger) } } \ No newline at end of file diff --git a/common/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProvider.kt b/common/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProvider.kt index 2685648e2..0d2410b73 100644 --- a/common/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProvider.kt +++ b/common/src/main/java/com/itsaky/androidide/app/configuration/IDEBuildConfigProvider.kt @@ -29,17 +29,12 @@ interface IDEBuildConfigProvider { /** * The current product flavor of the IDE. */ - val buildFlavor: String + val cpuAbiName: String /** * Get the [CpuArch] for the build flavor. */ - val flavorArch: CpuArch - - /** - * The build flavors which can be installed on the current device. - */ - val supportedBuildFlavors: Array + val cpuArch: CpuArch /** * Get the primary CPU architecture of the device. @@ -54,7 +49,7 @@ interface IDEBuildConfigProvider { /** * @return Whether this build is the `arm64-v8a` variant of the IDE. */ - fun isArm64v8aBuild(): Boolean = flavorArch == CpuArch.AARCH64 + fun isArm64v8aBuild(): Boolean = cpuArch == CpuArch.AARCH64 /** * @return Whether the device supports `arm64-v8a` instructions. @@ -64,7 +59,7 @@ interface IDEBuildConfigProvider { /** * @return Whether this build is the 32-bit variant of the IDE. */ - fun isArmeabiv7aBuild(): Boolean = flavorArch == CpuArch.ARM + fun isArmeabiv7aBuild(): Boolean = cpuArch == CpuArch.ARM /** * @return Whether the device supports `armeabi-v7a` instructions. @@ -74,7 +69,7 @@ interface IDEBuildConfigProvider { /** * @return Whether this build is the `x86_64` variant of the IDE. */ - fun isX86_64Build(): Boolean = flavorArch == CpuArch.X86_64 + fun isX86_64Build(): Boolean = cpuArch == CpuArch.X86_64 /** * @return Whether the device supports `x86_64` instructions. @@ -84,7 +79,7 @@ interface IDEBuildConfigProvider { /** * @return Whether the IDE can be run on the device's CPU arch. */ - fun supportsBuildFlavor(): Boolean = supportedBuildFlavors.contains(buildFlavor) + fun supportsBuildFlavor(): Boolean = supportedAbis.contains(cpuAbiName) companion object { diff --git a/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java b/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java index 7590ea8e8..68ca8ce4c 100755 --- a/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java +++ b/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java @@ -23,7 +23,6 @@ import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; import com.itsaky.androidide.app.BaseApplication; import com.itsaky.androidide.app.configuration.IDEBuildConfigProvider; -import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.TaskExecutorKt; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.ILogger; @@ -43,8 +42,6 @@ public class ToolsManager { private static final ILogger LOG = ILogger.newInstance("ToolsManager"); - public static String ARCH_SPECIFIC_ASSET_DATA_DIR = - "data/" + IDEBuildConfigProvider.getInstance().getBuildFlavor(); public static String COMMON_ASSET_DATA_DIR = "data/common"; public static void init(@NonNull BaseApplication app, Runnable onFinish) { @@ -54,27 +51,24 @@ public class ToolsManager { return; } - CompletableFuture.runAsync( - () -> { - writeNoMediaFile(); - extractAapt2(); - extractToolingApi(); - extractAndroidJar(); - extractColorScheme(app); - writeInitScript(); + CompletableFuture.runAsync(() -> { + writeNoMediaFile(); + extractAapt2(); + extractToolingApi(); + extractAndroidJar(); + extractColorScheme(app); + writeInitScript(); - deleteIdeenv(); - }) - .whenComplete( - (__, error) -> { - if (error != null) { - LOG.error("Error extracting tools", error); - } + deleteIdeenv(); + }).whenComplete((__, error) -> { + if (error != null) { + LOG.error("Error extracting tools", error); + } - if (onFinish != null) { - onFinish.run(); - } - }); + if (onFinish != null) { + onFinish.run(); + } + }); } private static void extractColorScheme(final BaseApplication app) { @@ -84,8 +78,8 @@ public class ToolsManager { for (final String asset : app.getAssets().list(defPath)) { final var prop = new File(dir, asset + "/" + "scheme.prop"); - if (prop.exists() - && !shouldExtractScheme(app, new File(dir, asset), defPath + "/" + asset)) { + if (prop.exists() && !shouldExtractScheme(app, new File(dir, asset), + defPath + "/" + asset)) { continue; } @@ -101,8 +95,8 @@ public class ToolsManager { } } - private static boolean shouldExtractScheme( - final BaseApplication app, final File dir, final String path) throws IOException { + private static boolean shouldExtractScheme(final BaseApplication app, final File dir, + final String path) throws IOException { final var schemePropFile = new File(dir, "scheme.prop"); if (!schemePropFile.exists()) { @@ -159,8 +153,8 @@ public class ToolsManager { private static void extractAndroidJar() { if (!Environment.ANDROID_JAR.exists()) { - ResourceUtils.copyFileFromAssets( - getCommonAsset("android.jar"), Environment.ANDROID_JAR.getAbsolutePath()); + ResourceUtils.copyFileFromAssets(getCommonAsset("android.jar"), + Environment.ANDROID_JAR.getAbsolutePath()); } } @@ -171,12 +165,6 @@ public class ToolsManager { } } - @NonNull - @Contract(pure = true) - public static String getArchSpecificAsset(String name) { - return ARCH_SPECIFIC_ASSET_DATA_DIR + "/" + name; - } - @NonNull @Contract(pure = true) public static String getCommonAsset(String name) { @@ -190,7 +178,9 @@ public class ToolsManager { final var sourceAapt2 = new File(nativeLibraryDir, "libaapt2.so"); if (!(sourceAapt2.exists() && sourceAapt2.isFile())) { // halt the application - TaskExecutorKt.runOnUiThread(() -> {throw new IllegalStateException(sourceAapt2 + " does not exist!");}); + TaskExecutorKt.runOnUiThread(() -> { + throw new IllegalStateException(sourceAapt2 + " does not exist!"); + }); } FilesKt.copyTo(sourceAapt2, Environment.AAPT2, true, ConstantsKt.DEFAULT_BUFFER_SIZE); } @@ -205,8 +195,8 @@ public class ToolsManager { FileUtils.delete(Environment.TOOLING_API_JAR); } - ResourceUtils.copyFileFromAssets( - getCommonAsset("tooling-api-all.jar"), Environment.TOOLING_API_JAR.getAbsolutePath()); + ResourceUtils.copyFileFromAssets(getCommonAsset("tooling-api-all.jar"), + Environment.TOOLING_API_JAR.getAbsolutePath()); } private static void writeInitScript() { @@ -221,10 +211,4 @@ public class ToolsManager { return ResourceUtils.readAssets2String(getCommonAsset("androidide.init.gradle")); } - public static void extractLibHooks() { - if (!Environment.LIB_HOOK.exists()) { - ResourceUtils.copyFileFromAssets( - getArchSpecificAsset("libhook.so"), Environment.LIB_HOOK.getAbsolutePath()); - } - } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java b/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java deleted file mode 100644 index d932400d2..000000000 --- a/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.androidide.utils; - -import static com.itsaky.androidide.utils.Environment.PREFIX; - -import android.content.Context; -import android.system.ErrnoException; -import android.system.Os; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.util.Pair; - -import com.blankj.utilcode.util.FileUtils; -import com.itsaky.androidide.resources.R; -import com.itsaky.androidide.managers.ToolsManager; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** - * Installs the bootstraps zip into {@code $SYSROOT}. - * - *

This class in very much based on Termux's TermuxInstaller.java. - * - * @author Akash Yadav - */ -public class BootstrapInstaller { - - private static final ILogger LOG = ILogger.newInstance("BootstrapInstaller"); - - @NonNull - public static CompletableFuture doInstall( - final Context context, @Nullable final UpdateListener listener) { - return CompletableFuture.runAsync( - () -> { - var msg = "Opening bootstrap zip input stream..."; - LOG.debug(msg); - notify(listener, msg); - try (final var assetIn = - context.getAssets().open(ToolsManager.getArchSpecificAsset("bootstrap.zip")); - final var zip = new ZipInputStream(assetIn)) { - - final var buffer = new byte[8096]; - final var symlinks = new ArrayList>(50); - - ZipEntry entry; - while ((entry = zip.getNextEntry()) != null) { - msg = "Reading zip entry " + entry.getName(); - notify(listener, msg); - LOG.verbose(msg); - if (entry.getName().equals("SYMLINKS.txt")) { - msg = "Reading SYMLINKS.txt..."; - notify(listener, msg); - LOG.debug(msg); - final var symlinksReader = new BufferedReader(new InputStreamReader(zip)); - String line; - while ((line = symlinksReader.readLine()) != null) { - String[] parts = line.split("←"); - if (parts.length != 2) { - final var err = "Malformed symlink line: " + line; - LOG.error(err); - throw new CompletionException(new InstallationException(err)); - } - String oldPath = parts[0]; - String newPath = PREFIX.getAbsolutePath() + "/" + parts[1]; - symlinks.add(Pair.create(oldPath, newPath)); - - final var parentFile = new File(newPath).getParentFile(); - if (!FileUtils.createOrExistsDir(parentFile)) { - LOG.error("Cannot create symlink parent directory."); - throw new CompletionException( - new InstallationException("Unable to create directory: " + parentFile)); - } - } - } else { - String zipEntryName = entry.getName(); - File targetFile = new File(PREFIX, zipEntryName); - boolean isDirectory = entry.isDirectory(); - - final var dir = isDirectory ? targetFile : targetFile.getParentFile(); - if (dir != null && !dir.exists() && !dir.mkdirs()) { - LOG.error("Cannot create target file parent directory"); - throw new CompletionException( - new InstallationException("Unable to create directory: " + dir)); - } - - // If the file exists and it is not a directory - // Delete that file - final var targetFilePath = targetFile.toPath(); - if (Files.exists(targetFilePath) && !Files.isDirectory(targetFilePath)) { - try { - Files.delete(targetFilePath); - } catch (Throwable th) { - throw new CompletionException(th); - } - } - - if (!isDirectory) { - try (final var outStream = new FileOutputStream(targetFile)) { - int readBytes; - while ((readBytes = zip.read(buffer)) != -1) - outStream.write(buffer, 0, readBytes); - } - - if (zipEntryName.startsWith("bin/") - || zipEntryName.startsWith("libexec") - || zipEntryName.startsWith("lib/apt/apt-helper") - || zipEntryName.startsWith("lib/apt/methods")) { - - notify(listener, context.getString(R.string.msg_chmod_exec)); - LOG.verbose("chmod 0700", zipEntryName); - - //noinspection OctalInteger - Os.chmod(targetFile.getAbsolutePath(), 0700); - } - } - } - } - - if (symlinks.isEmpty()) { - LOG.error("No SYMLINKS.txt file encountered..."); - throw new CompletionException( - new InstallationException("No SYMLINKS.txt encountered")); - } - - for (Pair symlink : symlinks) { - notify( - listener, context.getString(R.string.msg_linking, symlink.second, symlink.first)); - - final var target = Paths.get(symlink.second); - if (Files.exists(target) && !Files.isDirectory(target)) { - try { - Files.delete(target); - } catch (Throwable throwable) { - throw new CompletionException(throwable); - } - } - Os.symlink(symlink.first, symlink.second); - } - - notify(listener, context.getString(R.string.msg_extracting_hooks)); - ToolsManager.extractLibHooks(); - - LOG.info("Bootstrap packages installed successfully."); - } catch (IOException | ErrnoException e) { - LOG.error("Failed to extract bootstrap", e); - throw new RuntimeException(e); - } - }); - } - - private static void notify(@Nullable UpdateListener listener, String message) { - if (listener != null) { - listener.onUpdate(message); - } - } - - public static class InstallationException extends RuntimeException { - public InstallationException(String message) { - super(message); - } - } - - public interface UpdateListener { - void onUpdate(String message); - } -} diff --git a/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt b/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt index 0c23545bd..c30b9cf6f 100644 --- a/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt +++ b/idestats/src/main/java/com/itsaky/androidide/stats/AndroidIDEStats.kt @@ -59,7 +59,7 @@ object AndroidIDEStats { } val cpuArch by lazy { - IDEBuildConfigProvider.getInstance().buildFlavor + IDEBuildConfigProvider.getInstance().cpuAbiName } val statData by lazy {