Skip to content

Commit

Permalink
Require apps with native code to include 64-bit libraries
Browse files Browse the repository at this point in the history
Closes #622
  • Loading branch information
lberrymage committed Sep 10, 2024
1 parent be59d67 commit a79c8e8
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package app.accrescent.parcelo.apksparser

import com.android.bundle.Commands.BuildApksResult
import com.android.bundle.Targeting
import java.io.File
import java.io.IOException
import java.security.MessageDigest
Expand Down Expand Up @@ -38,6 +39,12 @@ public class ApkSet private constructor(
* - the app ID of the APKs matches the package name of the BuildApksResult
* - all APKs must not be debuggable
* - all APKs must not be marked test only
* - all modules containing an APK with ABI targeting contain at least one APK targeting at
* least one of the following ABIs:
* - arm64-v8a
* - x86_64
* - all modules containing an APK targeting x86 also contain at least one targeting x86_64,
* and likewise for armeabi-v7a and arm64-v8a respectively
*
* @return metadata describing the APK set and the app it represents
*/
Expand Down Expand Up @@ -72,9 +79,37 @@ public class ApkSet private constructor(

buildApksResult.variantList.forEach { variant ->
variant.apkSetList.forEach { apkSet ->
var moduleHasAbiTargeting = false
var moduleHasX86Targeting = false
var moduleHasX8664Targeting = false
var moduleHasArm32Targeting = false
var moduleHasArm64Targeting = false

apkSet.apkDescriptionList.forEach { apkDescription ->
val apkPath = apkDescription.path
?: return ParseApkSetResult.Error.MissingPathError

// Collect information on ABI targeting for this module
if (apkDescription.targeting.hasAbiTargeting()) {
moduleHasAbiTargeting = true

for (abi in apkDescription.targeting.abiTargeting.valueList) {
when (abi.alias) {
Targeting.Abi.AbiAlias.ARMEABI_V7A -> moduleHasArm32Targeting =
true

Targeting.Abi.AbiAlias.ARM64_V8A -> moduleHasArm64Targeting =
true

Targeting.Abi.AbiAlias.X86 -> moduleHasX86Targeting = true
Targeting.Abi.AbiAlias.X86_64 -> moduleHasX8664Targeting =
true

else -> {}
}
}
}

// Fail if a missing APK is a split APK.
//
// Because bundletool unconditionally generates standalone APKs for apps
Expand Down Expand Up @@ -176,6 +211,27 @@ public class ApkSet private constructor(
targetSdk = it.targetSdkVersion ?: it.minSdkVersion
}
}

if (moduleHasAbiTargeting) {
// Verify that the module supports at least one architecture which is
// both 64-bit and in our set of supported architectures
if (!(moduleHasArm64Targeting || moduleHasX8664Targeting)) {
return ParseApkSetResult.Error.NoSupportedArchitectures(
apkSet.moduleMetadata.name,
)
}

// Verify that the module contains 64-bit counterparts for every
// targeted 32-bit architecture in our set of supported architectures
if (
moduleHasArm32Targeting && !moduleHasArm64Targeting ||
moduleHasX86Targeting && !moduleHasX8664Targeting
) {
return ParseApkSetResult.Error.Missing64BitLibraries(
apkSet.moduleMetadata.name,
)
}
}
}
}

Expand Down Expand Up @@ -312,6 +368,23 @@ public sealed class ParseApkSetResult {
public data object MissingVersionCodeError : Error() {
override val message: String = "no version code found"
}

/**
* The module supports a specific ABI but doesn't include support for a required
* architecture
*/
public data class NoSupportedArchitectures(val module: String) : Error() {
override val message: String =
"module \"$module\" does not support a required architecture"
}

/**
* The module includes 32-bit native code but not corresponding 64-bit native code
*/
public data class Missing64BitLibraries(val module: String) : Error() {
override val message: String =
"module \"$module\" includes 32-bit native code but does not include 64-bit native code"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ class ApiError private constructor(

fun ioError(message: String) = ApiError(49, "I/O error", message)
fun apkSetFormat(message: String) = ApiError(50, "Invalid APK set format", message)
fun missing64BitLibraries(message: String) =
ApiError(51, "Missing 64-bit libraries", message)

fun noSupportedArchitectures(message: String) =
ApiError(52, "No supported architectures", message)
}
}

Expand All @@ -193,9 +198,15 @@ fun toApiError(error: ParseApkSetResult.Error): ApiError = with(error) {
message
)

is ParseApkSetResult.Error.Missing64BitLibraries -> ApiError.missing64BitLibraries(message)

is ParseApkSetResult.Error.MissingApkError -> ApiError.apkSetFormat(message)
ParseApkSetResult.Error.MissingPathError -> ApiError.apkSetFormat(message)
ParseApkSetResult.Error.MissingVersionCodeError -> ApiError.apkSetFormat(message)
is ParseApkSetResult.Error.NoSupportedArchitectures -> ApiError.noSupportedArchitectures(
message,
)

ParseApkSetResult.Error.SigningCertMismatchError -> ApiError.signingCertMismatch(message)
ParseApkSetResult.Error.TargetSdkNotFoundError -> ApiError.targetSdkNotFound(message)
ParseApkSetResult.Error.TestOnlyError -> ApiError.testOnly(message)
Expand Down

0 comments on commit a79c8e8

Please sign in to comment.