mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix: remove product flavors and use APK splits
This commit is contained in:
+18
-10
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
+13
-24
@@ -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<String>
|
||||
get() = Build.SUPPORTED_ABIS
|
||||
|
||||
override val supportedBuildFlavors: Array<String> by lazy {
|
||||
val flavors = mutableListOf<String>()
|
||||
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<String>?.checkIsPrimaryArch(arch: String): Boolean {
|
||||
return this?.isNotEmpty() == true && this[0] == arch
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = ", ")}]",
|
||||
|
||||
@@ -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<String>) : 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,16 @@ object FDroidConfig {
|
||||
var fDroidVersionCode: Int? = null
|
||||
private set
|
||||
|
||||
var fDroidAapt2File: String? = null
|
||||
var aapt2Files: Map<String, String?> = 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<String, String?>().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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-65
@@ -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<Project> {
|
||||
|
||||
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<Project> {
|
||||
|
||||
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<Project> {
|
||||
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<String, TaskProvider<SetupAapt2Task>> {
|
||||
|
||||
val aapt2Tasks = mutableMapOf<String, TaskProvider<SetupAapt2Task>>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-29
@@ -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
|
||||
|
||||
+26
-51
@@ -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<String>
|
||||
|
||||
/**
|
||||
* The version of the file, basically the tag name of the release.
|
||||
*/
|
||||
@get:Input
|
||||
abstract val version: Property<String>
|
||||
|
||||
/**
|
||||
* 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<String>
|
||||
|
||||
/**
|
||||
* The path of the static `aapt2` file which should be used instead of downloading it from GitHub.
|
||||
*/
|
||||
@get:Input
|
||||
abstract val staticAapt2: Property<String>
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
+6
-11
@@ -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<String>
|
||||
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 {
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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}.
|
||||
*
|
||||
* <p>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<Void> 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<Pair<String, String>>(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<String, String> 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);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ object AndroidIDEStats {
|
||||
}
|
||||
|
||||
val cpuArch by lazy {
|
||||
IDEBuildConfigProvider.getInstance().buildFlavor
|
||||
IDEBuildConfigProvider.getInstance().cpuAbiName
|
||||
}
|
||||
|
||||
val statData by lazy {
|
||||
|
||||
Reference in New Issue
Block a user