mirror of
https://github.com/python/cpython.git
synced 2025-07-07 11:25:30 +00:00

Modifies the environment handling and execution arguments of the Android management script to support the compilation of third-party binaries, and the use of the testbed to invoke third-party test code. Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Russell Keith-Magee <russell@keith-magee.com>
268 lines
8.3 KiB
Kotlin
268 lines
8.3 KiB
Kotlin
import com.android.build.api.variant.*
|
|
import kotlin.math.max
|
|
|
|
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
}
|
|
|
|
val ANDROID_DIR = file("../..")
|
|
val PYTHON_DIR = ANDROID_DIR.parentFile!!
|
|
val PYTHON_CROSS_DIR = file("$PYTHON_DIR/cross-build")
|
|
val inSourceTree = (
|
|
ANDROID_DIR.name == "Android" && file("$PYTHON_DIR/pyconfig.h.in").exists()
|
|
)
|
|
|
|
val KNOWN_ABIS = mapOf(
|
|
"aarch64-linux-android" to "arm64-v8a",
|
|
"x86_64-linux-android" to "x86_64",
|
|
)
|
|
|
|
// Discover prefixes.
|
|
val prefixes = ArrayList<File>()
|
|
if (inSourceTree) {
|
|
for ((triplet, _) in KNOWN_ABIS.entries) {
|
|
val prefix = file("$PYTHON_CROSS_DIR/$triplet/prefix")
|
|
if (prefix.exists()) {
|
|
prefixes.add(prefix)
|
|
}
|
|
}
|
|
} else {
|
|
// Testbed is inside a release package.
|
|
val prefix = file("$ANDROID_DIR/prefix")
|
|
if (prefix.exists()) {
|
|
prefixes.add(prefix)
|
|
}
|
|
}
|
|
if (prefixes.isEmpty()) {
|
|
throw GradleException(
|
|
"No Android prefixes found: see README.md for testing instructions"
|
|
)
|
|
}
|
|
|
|
// Detect Python versions and ABIs.
|
|
lateinit var pythonVersion: String
|
|
var abis = HashMap<File, String>()
|
|
for ((i, prefix) in prefixes.withIndex()) {
|
|
val libDir = file("$prefix/lib")
|
|
val version = run {
|
|
for (filename in libDir.list()!!) {
|
|
"""python(\d+\.\d+)""".toRegex().matchEntire(filename)?.let {
|
|
return@run it.groupValues[1]
|
|
}
|
|
}
|
|
throw GradleException("Failed to find Python version in $libDir")
|
|
}
|
|
if (i == 0) {
|
|
pythonVersion = version
|
|
} else if (pythonVersion != version) {
|
|
throw GradleException(
|
|
"${prefixes[0]} is Python $pythonVersion, but $prefix is Python $version"
|
|
)
|
|
}
|
|
|
|
val libPythonDir = file("$libDir/python$pythonVersion")
|
|
val triplet = run {
|
|
for (filename in libPythonDir.list()!!) {
|
|
"""_sysconfigdata__android_(.+).py""".toRegex().matchEntire(filename)?.let {
|
|
return@run it.groupValues[1]
|
|
}
|
|
}
|
|
throw GradleException("Failed to find Python triplet in $libPythonDir")
|
|
}
|
|
abis[prefix] = KNOWN_ABIS[triplet]!!
|
|
}
|
|
|
|
|
|
android {
|
|
val androidEnvFile = file("../../android-env.sh").absoluteFile
|
|
|
|
namespace = "org.python.testbed"
|
|
compileSdk = 34
|
|
|
|
defaultConfig {
|
|
applicationId = "org.python.testbed"
|
|
|
|
minSdk = androidEnvFile.useLines {
|
|
for (line in it) {
|
|
"""ANDROID_API_LEVEL:=(\d+)""".toRegex().find(line)?.let {
|
|
return@useLines it.groupValues[1].toInt()
|
|
}
|
|
}
|
|
throw GradleException("Failed to find API level in $androidEnvFile")
|
|
}
|
|
targetSdk = 34
|
|
|
|
versionCode = 1
|
|
versionName = "1.0"
|
|
|
|
ndk.abiFilters.addAll(abis.values)
|
|
externalNativeBuild.cmake.arguments(
|
|
"-DPYTHON_PREFIX_DIR=" + if (inSourceTree) {
|
|
// AGP uses the ${} syntax for its own purposes, so use a Jinja style
|
|
// placeholder.
|
|
"$PYTHON_CROSS_DIR/{{triplet}}/prefix"
|
|
} else {
|
|
prefixes[0]
|
|
},
|
|
"-DPYTHON_VERSION=$pythonVersion",
|
|
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
|
|
)
|
|
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
}
|
|
|
|
ndkVersion = androidEnvFile.useLines {
|
|
for (line in it) {
|
|
"""ndk_version=(\S+)""".toRegex().find(line)?.let {
|
|
return@useLines it.groupValues[1]
|
|
}
|
|
}
|
|
throw GradleException("Failed to find NDK version in $androidEnvFile")
|
|
}
|
|
externalNativeBuild.cmake {
|
|
path("src/main/c/CMakeLists.txt")
|
|
}
|
|
|
|
// Set this property to something non-empty, otherwise it'll use the default
|
|
// list, which ignores asset directories beginning with an underscore.
|
|
aaptOptions.ignoreAssetsPattern = ".git"
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
targetCompatibility = JavaVersion.VERSION_1_8
|
|
}
|
|
kotlinOptions {
|
|
jvmTarget = "1.8"
|
|
}
|
|
|
|
testOptions {
|
|
managedDevices {
|
|
localDevices {
|
|
create("minVersion") {
|
|
device = "Small Phone"
|
|
|
|
// Managed devices have a minimum API level of 27.
|
|
apiLevel = max(27, defaultConfig.minSdk!!)
|
|
|
|
// ATD devices are smaller and faster, but have a minimum
|
|
// API level of 30.
|
|
systemImageSource = if (apiLevel >= 30) "aosp-atd" else "aosp"
|
|
}
|
|
|
|
create("maxVersion") {
|
|
device = "Small Phone"
|
|
apiLevel = defaultConfig.targetSdk!!
|
|
systemImageSource = "aosp-atd"
|
|
}
|
|
}
|
|
|
|
// If the previous test run succeeded and nothing has changed,
|
|
// Gradle thinks there's no need to run it again. Override that.
|
|
afterEvaluate {
|
|
(localDevices.names + listOf("connected")).forEach {
|
|
tasks.named("${it}DebugAndroidTest") {
|
|
outputs.upToDateWhen { false }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
|
implementation("com.google.android.material:material:1.11.0")
|
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
|
androidTestImplementation("androidx.test:rules:1.5.0")
|
|
}
|
|
|
|
|
|
// Create some custom tasks to copy Python and its standard library from
|
|
// elsewhere in the repository.
|
|
androidComponents.onVariants { variant ->
|
|
val pyPlusVer = "python$pythonVersion"
|
|
generateTask(variant, variant.sources.assets!!) {
|
|
into("python") {
|
|
// Include files such as pyconfig.h are used by some of the tests.
|
|
into("include/$pyPlusVer") {
|
|
for (prefix in prefixes) {
|
|
from("$prefix/include/$pyPlusVer")
|
|
}
|
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
}
|
|
|
|
into("lib/$pyPlusVer") {
|
|
// To aid debugging, the source directory takes priority when
|
|
// running inside a CPython source tree.
|
|
if (inSourceTree) {
|
|
from("$PYTHON_DIR/Lib")
|
|
}
|
|
for (prefix in prefixes) {
|
|
from("$prefix/lib/$pyPlusVer")
|
|
}
|
|
|
|
into("site-packages") {
|
|
from("$projectDir/src/main/python")
|
|
|
|
val sitePackages = findProperty("python.sitePackages") as String?
|
|
if (!sitePackages.isNullOrEmpty()) {
|
|
if (!file(sitePackages).exists()) {
|
|
throw GradleException("$sitePackages does not exist")
|
|
}
|
|
from(sitePackages)
|
|
}
|
|
}
|
|
|
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
exclude("**/__pycache__")
|
|
}
|
|
|
|
into("cwd") {
|
|
val cwd = findProperty("python.cwd") as String?
|
|
if (!cwd.isNullOrEmpty()) {
|
|
if (!file(cwd).exists()) {
|
|
throw GradleException("$cwd does not exist")
|
|
}
|
|
from(cwd)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
generateTask(variant, variant.sources.jniLibs!!) {
|
|
for ((prefix, abi) in abis.entries) {
|
|
into(abi) {
|
|
from("$prefix/lib")
|
|
include("libpython*.*.so")
|
|
include("lib*_python.so")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fun generateTask(
|
|
variant: ApplicationVariant, directories: SourceDirectories,
|
|
configure: GenerateTask.() -> Unit
|
|
) {
|
|
val taskName = "generate" +
|
|
listOf(variant.name, "Python", directories.name)
|
|
.map { it.replaceFirstChar(Char::uppercase) }
|
|
.joinToString("")
|
|
|
|
directories.addGeneratedSourceDirectory(
|
|
tasks.register<GenerateTask>(taskName) {
|
|
into(outputDir)
|
|
configure()
|
|
},
|
|
GenerateTask::outputDir)
|
|
}
|
|
|
|
|
|
// addGeneratedSourceDirectory requires the task to have a DirectoryProperty.
|
|
abstract class GenerateTask: Sync() {
|
|
@get:OutputDirectory
|
|
abstract val outputDir: DirectoryProperty
|
|
}
|