2024-05-01 08:36:45 +02:00
|
|
|
import com.android.build.api.variant.*
|
2024-08-16 07:00:29 +02:00
|
|
|
import kotlin.math.max
|
2024-05-01 08:36:45 +02:00
|
|
|
|
|
|
|
plugins {
|
|
|
|
id("com.android.application")
|
|
|
|
id("org.jetbrains.kotlin.android")
|
|
|
|
}
|
|
|
|
|
2024-08-16 07:00:29 +02:00
|
|
|
val PYTHON_DIR = file("../../..").canonicalPath
|
2024-05-01 08:36:45 +02:00
|
|
|
val PYTHON_CROSS_DIR = "$PYTHON_DIR/cross-build"
|
2024-07-31 02:21:43 +02:00
|
|
|
|
2024-05-01 08:36:45 +02:00
|
|
|
val ABIS = mapOf(
|
|
|
|
"arm64-v8a" to "aarch64-linux-android",
|
|
|
|
"x86_64" to "x86_64-linux-android",
|
2024-08-16 07:00:29 +02:00
|
|
|
).filter { file("$PYTHON_CROSS_DIR/${it.value}").exists() }
|
2024-07-31 02:21:43 +02:00
|
|
|
if (ABIS.isEmpty()) {
|
|
|
|
throw GradleException(
|
|
|
|
"No Android ABIs found in $PYTHON_CROSS_DIR: see Android/README.md " +
|
|
|
|
"for building instructions."
|
|
|
|
)
|
|
|
|
}
|
2024-05-01 08:36:45 +02:00
|
|
|
|
2024-08-16 07:00:29 +02:00
|
|
|
val PYTHON_VERSION = file("$PYTHON_DIR/Include/patchlevel.h").useLines {
|
2024-05-01 08:36:45 +02:00
|
|
|
for (line in it) {
|
|
|
|
val match = """#define PY_VERSION\s+"(\d+\.\d+)""".toRegex().find(line)
|
|
|
|
if (match != null) {
|
|
|
|
return@useLines match.groupValues[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw GradleException("Failed to find Python version")
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
android {
|
2024-10-25 02:41:07 +02:00
|
|
|
val androidEnvFile = file("../../android-env.sh").absoluteFile
|
|
|
|
|
2024-05-01 08:36:45 +02:00
|
|
|
namespace = "org.python.testbed"
|
|
|
|
compileSdk = 34
|
|
|
|
|
|
|
|
defaultConfig {
|
|
|
|
applicationId = "org.python.testbed"
|
2024-10-25 02:41:07 +02:00
|
|
|
|
|
|
|
minSdk = androidEnvFile.useLines {
|
|
|
|
for (line in it) {
|
|
|
|
"""api_level:=(\d+)""".toRegex().find(line)?.let {
|
|
|
|
return@useLines it.groupValues[1].toInt()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw GradleException("Failed to find API level in $androidEnvFile")
|
|
|
|
}
|
2024-05-01 08:36:45 +02:00
|
|
|
targetSdk = 34
|
2024-10-25 02:41:07 +02:00
|
|
|
|
2024-05-01 08:36:45 +02:00
|
|
|
versionCode = 1
|
|
|
|
versionName = "1.0"
|
|
|
|
|
|
|
|
ndk.abiFilters.addAll(ABIS.keys)
|
|
|
|
externalNativeBuild.cmake.arguments(
|
|
|
|
"-DPYTHON_CROSS_DIR=$PYTHON_CROSS_DIR",
|
2024-10-25 01:51:16 +02:00
|
|
|
"-DPYTHON_VERSION=$PYTHON_VERSION",
|
|
|
|
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
|
|
|
|
)
|
2024-08-16 07:00:29 +02:00
|
|
|
|
|
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
2024-05-01 08:36:45 +02:00
|
|
|
}
|
|
|
|
|
2024-10-25 01:51:16 +02:00
|
|
|
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")
|
|
|
|
}
|
2024-05-01 08:36:45 +02:00
|
|
|
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"
|
|
|
|
}
|
2024-08-16 07:00:29 +02:00
|
|
|
|
|
|
|
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 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-05-01 08:36:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dependencies {
|
|
|
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
|
|
|
implementation("com.google.android.material:material:1.11.0")
|
|
|
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
2024-08-16 07:00:29 +02:00
|
|
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
|
|
|
androidTestImplementation("androidx.test:rules:1.5.0")
|
2024-05-01 08:36:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create some custom tasks to copy Python and its standard library from
|
|
|
|
// elsewhere in the repository.
|
|
|
|
androidComponents.onVariants { variant ->
|
2024-08-16 07:00:29 +02:00
|
|
|
val pyPlusVer = "python$PYTHON_VERSION"
|
2024-05-01 08:36:45 +02:00
|
|
|
generateTask(variant, variant.sources.assets!!) {
|
|
|
|
into("python") {
|
2024-08-16 07:00:29 +02:00
|
|
|
into("include/$pyPlusVer") {
|
|
|
|
for (triplet in ABIS.values) {
|
|
|
|
from("$PYTHON_CROSS_DIR/$triplet/prefix/include/$pyPlusVer")
|
2024-05-01 08:36:45 +02:00
|
|
|
}
|
2024-08-16 07:00:29 +02:00
|
|
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
2024-05-01 08:36:45 +02:00
|
|
|
}
|
2024-08-16 07:00:29 +02:00
|
|
|
|
|
|
|
into("lib/$pyPlusVer") {
|
|
|
|
// To aid debugging, the source directory takes priority.
|
|
|
|
from("$PYTHON_DIR/Lib")
|
|
|
|
|
|
|
|
// The cross-build directory provides ABI-specific files such as
|
|
|
|
// sysconfigdata.
|
|
|
|
for (triplet in ABIS.values) {
|
|
|
|
from("$PYTHON_CROSS_DIR/$triplet/prefix/lib/$pyPlusVer")
|
|
|
|
}
|
2024-05-01 08:36:45 +02:00
|
|
|
|
|
|
|
into("site-packages") {
|
|
|
|
from("$projectDir/src/main/python")
|
|
|
|
}
|
2024-08-16 07:00:29 +02:00
|
|
|
|
|
|
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
|
|
exclude("**/__pycache__")
|
2024-05-01 08:36:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
generateTask(variant, variant.sources.jniLibs!!) {
|
|
|
|
for ((abi, triplet) in ABIS.entries) {
|
|
|
|
into(abi) {
|
|
|
|
from("$PYTHON_CROSS_DIR/$triplet/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
|
|
|
|
}
|