https://github.com/python/cpython/commit/fe5c4c53e7bc6d780686013eaab17de2237b2176
commit: fe5c4c53e7bc6d780686013eaab17de2237b2176
branch: main
author: Malcolm Smith <sm...@chaquo.com>
committer: freakboy3742 <russ...@keith-magee.com>
date: 2025-04-01T08:46:29+08:00
summary:

gh-131531: Add `android.py package` command (#131532)

Adds a `package` entry point to the `android.py` build script to support
creating an Android distribution artefact.

files:
A Android/testbed/.idea/inspectionProfiles/Project_Default.xml
M Android/README.md
M Android/android.py
M Android/testbed/.gitignore
M Android/testbed/app/build.gradle.kts
M Android/testbed/app/src/main/c/CMakeLists.txt
M Doc/using/android.rst

diff --git a/Android/README.md b/Android/README.md
index 28d48917e4e5a8..789bcbe5edff44 100644
--- a/Android/README.md
+++ b/Android/README.md
@@ -1,19 +1,22 @@
 # Python for Android
 
-These instructions are only needed if you're planning to compile Python for
-Android yourself. Most users should *not* need to do this. Instead, use one of
-the tools listed in `Doc/using/android.rst`, which will provide a much easier
-experience.
+If you obtained this README as part of a release package, then the only
+applicable sections are "Prerequisites", "Testing", and "Using in your own 
app".
 
+If you obtained this README as part of the CPython source tree, then you can
+also follow the other sections to compile Python for Android yourself.
+
+However, most app developers should not need to do any of these things 
manually.
+Instead, use one of the tools listed
+[here](https://docs.python.org/3/using/android.html), which will provide a much
+easier experience.
 
-## Prerequisites
 
-First, make sure you have all the usual tools and libraries needed to build
-Python for your development machine.
+## Prerequisites
 
-Second, you'll need an Android SDK. If you already have the SDK installed,
-export the `ANDROID_HOME` environment variable to point at its location.
-Otherwise, here's how to install it:
+If you already have an Android SDK installed, export the `ANDROID_HOME`
+environment variable to point at its location. Otherwise, here's how to install
+it:
 
 * Download the "Command line tools" from 
<https://developer.android.com/studio>.
 * Create a directory `android-sdk/cmdline-tools`, and unzip the command line
@@ -27,15 +30,16 @@ The `android.py` script also requires the following 
commands to be on the `PATH`
 * `curl`
 * `java` (or set the `JAVA_HOME` environment variable)
 * `tar`
-* `unzip`
 
 
 ## Building
 
 Python can be built for Android on any POSIX platform supported by the Android
-development tools, which currently means Linux or macOS. This involves doing a
-cross-build where you use a "build" Python (for your development machine) to
-help produce a "host" Python for Android.
+development tools, which currently means Linux or macOS.
+
+First we'll make a "build" Python (for your development machine), then use it 
to
+help produce a "host" Python for Android. So make sure you have all the usual
+tools and libraries needed to build Python for your development machine.
 
 The easiest way to do a build is to use the `android.py` script. You can either
 have it perform the entire build process from start to finish in one step, or
@@ -60,8 +64,8 @@ To do all steps in a single command, run:
 ./android.py build HOST
 ```
 
-In the end you should have a build Python in `cross-build/build`, and an 
Android
-build in `cross-build/HOST`.
+In the end you should have a build Python in `cross-build/build`, and a host
+Python in `cross-build/HOST`.
 
 You can use `--` as a separator for any of the `configure`-related commands –
 including `build` itself – to pass arguments to the underlying `configure`
@@ -73,14 +77,27 @@ call. For example, if you want a pydebug build that also 
caches the results from
 ```
 
 
+## Packaging
+
+After building an architecture as described in the section above, you can
+package it for release with this command:
+
+```sh
+./android.py package HOST
+```
+
+`HOST` is defined in the section above.
+
+This will generate a tarball in `cross-build/HOST/dist`, whose structure is
+similar to the `Android` directory of the CPython source tree.
+
+
 ## Testing
 
-The test suite can be run on Linux, macOS, or Windows:
+The Python test suite can be run on Linux, macOS, or Windows:
 
 * On Linux, the emulator needs access to the KVM virtualization interface, and
   a DISPLAY environment variable pointing at an X server.
-* On Windows, you won't be able to do the build on the same machine, so you'll
-  have to copy the `cross-build/HOST` directory from somewhere else.
 
 The test suite can usually be run on a device with 2 GB of RAM, but this is
 borderline, so you may need to increase it to 4 GB. As of Android
@@ -90,9 +107,16 @@ and find `hw.ramSize` in both config.ini and 
hardware-qemu.ini. Either set these
 manually to the same value, or use the Android Studio Device Manager, which 
will
 update both files.
 
-Before running the test suite, follow the instructions in the previous section
-to build the architecture you want to test. Then run the test script in one of
-the following modes:
+You can run the test suite either:
+
+* Within the CPython repository, after doing a build as described above. On
+  Windows, you won't be able to do the build on the same machine, so you'll 
have
+  to copy the `cross-build/HOST/prefix` directory from somewhere else.
+
+* Or by taking a release package built using the `package` command, extracting
+  it wherever you want, and using its own copy of `android.py`.
+
+The test script supports the following modes:
 
 * In `--connected` mode, it runs on a device or emulator you have already
   connected to the build machine. List the available devices with
@@ -133,4 +157,4 @@ until you re-run `android.py make-host` or `build`.
 
 ## Using in your own app
 
-See `Doc/using/android.rst`.
+See https://docs.python.org/3/using/android.html.
diff --git a/Android/android.py b/Android/android.py
index a2a31b23f6403b..1b20820b784371 100755
--- a/Android/android.py
+++ b/Android/android.py
@@ -2,7 +2,6 @@
 
 import asyncio
 import argparse
-from glob import glob
 import os
 import re
 import shlex
@@ -13,6 +12,8 @@
 import sysconfig
 from asyncio import wait_for
 from contextlib import asynccontextmanager
+from datetime import datetime, timezone
+from glob import glob
 from os.path import basename, relpath
 from pathlib import Path
 from subprocess import CalledProcessError
@@ -20,11 +21,12 @@
 
 
 SCRIPT_NAME = Path(__file__).name
-CHECKOUT = Path(__file__).resolve().parent.parent
-ANDROID_DIR = CHECKOUT / "Android"
+ANDROID_DIR = Path(__file__).resolve().parent
+CHECKOUT = ANDROID_DIR.parent
 TESTBED_DIR = ANDROID_DIR / "testbed"
 CROSS_BUILD_DIR = CHECKOUT / "cross-build"
 
+HOSTS = ["aarch64-linux-android", "x86_64-linux-android"]
 APP_ID = "org.python.testbed"
 DECODE_ARGS = ("UTF-8", "backslashreplace")
 
@@ -58,12 +60,10 @@ def delete_glob(pattern):
             path.unlink()
 
 
-def subdir(name, *, clean=None):
-    path = CROSS_BUILD_DIR / name
-    if clean:
-        delete_glob(path)
+def subdir(*parts, create=False):
+    path = CROSS_BUILD_DIR.joinpath(*parts)
     if not path.exists():
-        if clean is None:
+        if not create:
             sys.exit(
                 f"{path} does not exist. Create it by running the appropriate "
                 f"`configure` subcommand of {SCRIPT_NAME}.")
@@ -123,7 +123,9 @@ def build_python_path():
 
 
 def configure_build_python(context):
-    os.chdir(subdir("build", clean=context.clean))
+    if context.clean:
+        clean("build")
+    os.chdir(subdir("build", create=True))
 
     command = [relpath(CHECKOUT / "configure")]
     if context.args:
@@ -153,18 +155,17 @@ def download(url, target_dir="."):
 
 
 def configure_host_python(context):
-    host_dir = subdir(context.host, clean=context.clean)
+    if context.clean:
+        clean(context.host)
 
+    host_dir = subdir(context.host, create=True)
     prefix_dir = host_dir / "prefix"
     if not prefix_dir.exists():
         prefix_dir.mkdir()
         os.chdir(prefix_dir)
         unpack_deps(context.host)
 
-    build_dir = host_dir / "build"
-    build_dir.mkdir(exist_ok=True)
-    os.chdir(build_dir)
-
+    os.chdir(host_dir)
     command = [
         # Basic cross-compiling configuration
         relpath(CHECKOUT / "configure"),
@@ -193,11 +194,10 @@ def make_host_python(context):
     # the build.
     host_dir = subdir(context.host)
     prefix_dir = host_dir / "prefix"
-    delete_glob(f"{prefix_dir}/include/python*")
-    delete_glob(f"{prefix_dir}/lib/libpython*")
-    delete_glob(f"{prefix_dir}/lib/python*")
+    for pattern in ("include/python*", "lib/libpython*", "lib/python*"):
+        delete_glob(f"{prefix_dir}/{pattern}")
 
-    os.chdir(host_dir / "build")
+    os.chdir(host_dir)
     run(["make", "-j", str(os.cpu_count())], host=context.host)
     run(["make", "install", f"prefix={prefix_dir}"], host=context.host)
 
@@ -209,8 +209,13 @@ def build_all(context):
         step(context)
 
 
+def clean(host):
+    delete_glob(CROSS_BUILD_DIR / host)
+
+
 def clean_all(context):
-    delete_glob(CROSS_BUILD_DIR)
+    for host in HOSTS + ["build"]:
+        clean(host)
 
 
 def setup_sdk():
@@ -234,31 +239,27 @@ def setup_sdk():
 
 # To avoid distributing compiled artifacts without corresponding source code,
 # the Gradle wrapper is not included in the CPython repository. Instead, we
-# extract it from the Gradle release.
+# extract it from the Gradle GitHub repository.
 def setup_testbed():
-    if all((TESTBED_DIR / path).exists() for path in [
-        "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.jar",
-    ]):
+    # The Gradle version used for the build is specified in
+    # testbed/gradle/wrapper/gradle-wrapper.properties. This wrapper version
+    # doesn't need to match, as any version of the wrapper can download any
+    # version of Gradle.
+    version = "8.9.0"
+    paths = ["gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.jar"]
+
+    if all((TESTBED_DIR / path).exists() for path in paths):
         return
 
-    ver_long = "8.7.0"
-    ver_short = ver_long.removesuffix(".0")
-
-    for filename in ["gradlew", "gradlew.bat"]:
-        out_path = download(
-            
f"https://raw.githubusercontent.com/gradle/gradle/v{ver_long}/{filename}";,
-            TESTBED_DIR)
+    for path in paths:
+        out_path = TESTBED_DIR / path
+        out_path.parent.mkdir(exist_ok=True)
+        download(
+            
f"https://raw.githubusercontent.com/gradle/gradle/v{version}/{path}";,
+            out_path.parent,
+        )
         os.chmod(out_path, 0o755)
 
-    with TemporaryDirectory(prefix=SCRIPT_NAME) as temp_dir:
-        bin_zip = download(
-            
f"https://services.gradle.org/distributions/gradle-{ver_short}-bin.zip";,
-            temp_dir)
-        outer_jar = 
f"gradle-{ver_short}/lib/plugins/gradle-wrapper-{ver_short}.jar"
-        run(["unzip", "-d", temp_dir, bin_zip, outer_jar])
-        run(["unzip", "-o", "-d", f"{TESTBED_DIR}/gradle/wrapper",
-             f"{temp_dir}/{outer_jar}", "gradle-wrapper.jar"])
-
 
 # run_testbed will build the app automatically, but it's useful to have this as
 # a separate command to allow running the app outside of this script.
@@ -538,6 +539,73 @@ async def run_testbed(context):
         raise e.exceptions[0]
 
 
+def package_version(prefix_dir):
+    patchlevel_glob = f"{prefix_dir}/include/python*/patchlevel.h"
+    patchlevel_paths = glob(patchlevel_glob)
+    if len(patchlevel_paths) != 1:
+        sys.exit(f"{patchlevel_glob} matched {len(patchlevel_paths)} paths.")
+
+    for line in open(patchlevel_paths[0]):
+        if match := re.fullmatch(r'\s*#define\s+PY_VERSION\s+"(.+)"\s*', line):
+            version = match[1]
+            break
+    else:
+        sys.exit(f"Failed to find Python version in {patchlevel_paths[0]}.")
+
+    # If not building against a tagged commit, add a timestamp to the version.
+    # Follow the PyPA version number rules, as this will make it easier to
+    # process with other tools.
+    if version.endswith("+"):
+        version += datetime.now(timezone.utc).strftime("%Y%m%d.%H%M%S")
+
+    return version
+
+
+def package(context):
+    prefix_dir = subdir(context.host, "prefix")
+    version = package_version(prefix_dir)
+
+    with TemporaryDirectory(prefix=SCRIPT_NAME) as temp_dir:
+        temp_dir = Path(temp_dir)
+
+        # Include all tracked files from the Android directory.
+        for line in run(
+            ["git", "ls-files"],
+            cwd=ANDROID_DIR, capture_output=True, text=True, log=False,
+        ).stdout.splitlines():
+            src = ANDROID_DIR / line
+            dst = temp_dir / line
+            dst.parent.mkdir(parents=True, exist_ok=True)
+            shutil.copy2(src, dst, follow_symlinks=False)
+
+        # Include anything from the prefix directory which could be useful
+        # either for embedding Python in an app, or building third-party
+        # packages against it.
+        for rel_dir, patterns in [
+            ("include", ["openssl*", "python*", "sqlite*"]),
+            ("lib", ["engines-3", "libcrypto*.so", "libpython*", "libsqlite*",
+                     "libssl*.so", "ossl-modules", "python*"]),
+            ("lib/pkgconfig", ["*crypto*", "*ssl*", "*python*", "*sqlite*"]),
+        ]:
+            for pattern in patterns:
+                for src in glob(f"{prefix_dir}/{rel_dir}/{pattern}"):
+                    dst = temp_dir / relpath(src, prefix_dir.parent)
+                    dst.parent.mkdir(parents=True, exist_ok=True)
+                    if Path(src).is_dir():
+                        shutil.copytree(
+                            src, dst, symlinks=True,
+                            ignore=lambda *args: ["__pycache__"]
+                        )
+                    else:
+                        shutil.copy2(src, dst, follow_symlinks=False)
+
+        dist_dir = subdir(context.host, "dist", create=True)
+        package_path = shutil.make_archive(
+            f"{dist_dir}/python-{version}-{context.host}", "gztar", temp_dir
+        )
+        print(f"Wrote {package_path}")
+
+
 # Handle SIGTERM the same way as SIGINT. This ensures that if we're terminated
 # by the buildbot worker, we'll make an attempt to clean up our subprocesses.
 def install_signal_handler():
@@ -550,6 +618,8 @@ def signal_handler(*args):
 def parse_args():
     parser = argparse.ArgumentParser()
     subcommands = parser.add_subparsers(dest="subcommand")
+
+    # Subcommands
     build = subcommands.add_parser("build", help="Build everything")
     configure_build = subcommands.add_parser("configure-build",
                                              help="Run `configure` for the "
@@ -561,25 +631,27 @@ def parse_args():
     make_host = subcommands.add_parser("make-host",
                                        help="Run `make` for Android")
     subcommands.add_parser(
-        "clean", help="Delete the cross-build directory")
+        "clean", help="Delete all build and prefix directories")
+    subcommands.add_parser(
+        "build-testbed", help="Build the testbed app")
+    test = subcommands.add_parser(
+        "test", help="Run the test suite")
+    package = subcommands.add_parser("package", help="Make a release package")
 
+    # Common arguments
     for subcommand in build, configure_build, configure_host:
         subcommand.add_argument(
             "--clean", action="store_true", default=False, dest="clean",
-            help="Delete any relevant directories before building")
-    for subcommand in build, configure_host, make_host:
+            help="Delete the relevant build and prefix directories first")
+    for subcommand in [build, configure_host, make_host, package]:
         subcommand.add_argument(
-            "host", metavar="HOST",
-            choices=["aarch64-linux-android", "x86_64-linux-android"],
+            "host", metavar="HOST", choices=HOSTS,
             help="Host triplet: choices=[%(choices)s]")
     for subcommand in build, configure_build, configure_host:
         subcommand.add_argument("args", nargs="*",
                                 help="Extra arguments to pass to `configure`")
 
-    subcommands.add_parser(
-        "build-testbed", help="Build the testbed app")
-    test = subcommands.add_parser(
-        "test", help="Run the test suite")
+    # Test arguments
     test.add_argument(
         "-v", "--verbose", action="count", default=0,
         help="Show Gradle output, and non-Python logcat messages. "
@@ -608,14 +680,17 @@ def main():
         stream.reconfigure(line_buffering=True)
 
     context = parse_args()
-    dispatch = {"configure-build": configure_build_python,
-                "make-build": make_build_python,
-                "configure-host": configure_host_python,
-                "make-host": make_host_python,
-                "build": build_all,
-                "clean": clean_all,
-                "build-testbed": build_testbed,
-                "test": run_testbed}
+    dispatch = {
+        "configure-build": configure_build_python,
+        "make-build": make_build_python,
+        "configure-host": configure_host_python,
+        "make-host": make_host_python,
+        "build": build_all,
+        "clean": clean_all,
+        "build-testbed": build_testbed,
+        "test": run_testbed,
+        "package": package,
+    }
 
     try:
         result = dispatch[context.subcommand](context)
diff --git a/Android/testbed/.gitignore b/Android/testbed/.gitignore
index b9a7d611c943cf..7c57aee58c160a 100644
--- a/Android/testbed/.gitignore
+++ b/Android/testbed/.gitignore
@@ -1,18 +1,19 @@
-# The Gradle wrapper should be downloaded by running `../android.py 
setup-testbed`.
+# The Gradle wrapper can be downloaded by running the `test` or `build-testbed`
+# commands of android.py.
 /gradlew
 /gradlew.bat
 /gradle/wrapper/gradle-wrapper.jar
 
+# The repository's top-level .gitignore file ignores all .idea directories, but
+# we want to keep any files which can't be regenerated from the Gradle
+# configuration.
+!.idea/
+/.idea/*
+!/.idea/inspectionProfiles
+
 *.iml
 .gradle
 /local.properties
-/.idea/caches
-/.idea/deploymentTargetDropdown.xml
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
 .DS_Store
 /build
 /captures
diff --git a/Android/testbed/.idea/inspectionProfiles/Project_Default.xml 
b/Android/testbed/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000000000..220d9ed4ef20f7
--- /dev/null
+++ b/Android/testbed/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="AndroidLintGradleDependency" enabled="true" 
level="WEAK WARNING" enabled_by_default="true" 
editorAttributes="INFO_ATTRIBUTES" />
+    <inspection_tool class="AndroidLintOldTargetApi" enabled="true" 
level="WEAK WARNING" enabled_by_default="true" 
editorAttributes="INFO_ATTRIBUTES" />
+    <inspection_tool class="UnstableApiUsage" enabled="true" level="WEAK 
WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/Android/testbed/app/build.gradle.kts 
b/Android/testbed/app/build.gradle.kts
index 211b5bbfadf64d..c627cb1b0e0b22 100644
--- a/Android/testbed/app/build.gradle.kts
+++ b/Android/testbed/app/build.gradle.kts
@@ -6,28 +6,71 @@ plugins {
     id("org.jetbrains.kotlin.android")
 }
 
-val PYTHON_DIR = file("../../..").canonicalPath
-val PYTHON_CROSS_DIR = "$PYTHON_DIR/cross-build"
-
-val ABIS = mapOf(
-    "arm64-v8a" to "aarch64-linux-android",
-    "x86_64" to "x86_64-linux-android",
-).filter { file("$PYTHON_CROSS_DIR/${it.value}").exists() }
-if (ABIS.isEmpty()) {
+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 ABIs found in $PYTHON_CROSS_DIR: see Android/README.md " +
-        "for building instructions."
+        "No Android prefixes found: see README.md for testing instructions"
     )
 }
 
-val PYTHON_VERSION = file("$PYTHON_DIR/Include/patchlevel.h").useLines {
-    for (line in it) {
-        val match = """#define PY_VERSION\s+"(\d+\.\d+)""".toRegex().find(line)
-        if (match != null) {
-            return@useLines match.groupValues[1]
+// 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"
+        )
     }
-    throw GradleException("Failed to find 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]!!
 }
 
 
@@ -53,10 +96,16 @@ android {
         versionCode = 1
         versionName = "1.0"
 
-        ndk.abiFilters.addAll(ABIS.keys)
+        ndk.abiFilters.addAll(abis.values)
         externalNativeBuild.cmake.arguments(
-            "-DPYTHON_CROSS_DIR=$PYTHON_CROSS_DIR",
-            "-DPYTHON_VERSION=$PYTHON_VERSION",
+            "-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",
         )
 
@@ -133,24 +182,25 @@ dependencies {
 // Create some custom tasks to copy Python and its standard library from
 // elsewhere in the repository.
 androidComponents.onVariants { variant ->
-    val pyPlusVer = "python$PYTHON_VERSION"
+    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 (triplet in ABIS.values) {
-                    
from("$PYTHON_CROSS_DIR/$triplet/prefix/include/$pyPlusVer")
+                for (prefix in prefixes) {
+                    from("$prefix/include/$pyPlusVer")
                 }
                 duplicatesStrategy = DuplicatesStrategy.EXCLUDE
             }
 
             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")
+                // 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") {
@@ -164,9 +214,9 @@ androidComponents.onVariants { variant ->
     }
 
     generateTask(variant, variant.sources.jniLibs!!) {
-        for ((abi, triplet) in ABIS.entries) {
+        for ((prefix, abi) in abis.entries) {
             into(abi) {
-                from("$PYTHON_CROSS_DIR/$triplet/prefix/lib")
+                from("$prefix/lib")
                 include("libpython*.*.so")
                 include("lib*_python.so")
             }
diff --git a/Android/testbed/app/src/main/c/CMakeLists.txt 
b/Android/testbed/app/src/main/c/CMakeLists.txt
index 1d5df9a73465b6..6d5ccd96f8ae29 100644
--- a/Android/testbed/app/src/main/c/CMakeLists.txt
+++ b/Android/testbed/app/src/main/c/CMakeLists.txt
@@ -1,9 +1,14 @@
 cmake_minimum_required(VERSION 3.4.1)
 project(testbed)
 
-set(PREFIX_DIR ${PYTHON_CROSS_DIR}/${CMAKE_LIBRARY_ARCHITECTURE}/prefix)
-include_directories(${PREFIX_DIR}/include/python${PYTHON_VERSION})
-link_directories(${PREFIX_DIR}/lib)
+# Resolve variables from the command line.
+string(
+    REPLACE {{triplet}} ${CMAKE_LIBRARY_ARCHITECTURE}
+    PYTHON_PREFIX_DIR ${PYTHON_PREFIX_DIR}
+)
+
+include_directories(${PYTHON_PREFIX_DIR}/include/python${PYTHON_VERSION})
+link_directories(${PYTHON_PREFIX_DIR}/lib)
 link_libraries(log python${PYTHON_VERSION})
 
 add_library(main_activity SHARED main_activity.c)
diff --git a/Doc/using/android.rst b/Doc/using/android.rst
index 957705f7f5e189..65bf23dc994856 100644
--- a/Doc/using/android.rst
+++ b/Doc/using/android.rst
@@ -27,9 +27,8 @@ details.
 Adding Python to an Android app
 -------------------------------
 
-These instructions are only needed if you're planning to compile Python for
-Android yourself. Most users should *not* need to do this. Instead, use one of
-the following tools, which will provide a much easier experience:
+Most app developers should use one of the following tools, which will provide a
+much easier experience:
 
 * `Briefcase <https://briefcase.readthedocs.io>`__, from the BeeWare project
 * `Buildozer <https://buildozer.readthedocs.io>`__, from the Kivy project
@@ -42,10 +41,11 @@ If you're sure you want to do all of this manually, read 
on. You can use the
 link to the relevant file.
 
 * Build Python by following the instructions in :source:`Android/README.md`.
+  This will create the directory ``cross-build/HOST/prefix``.
 
 * Add code to your :source:`build.gradle 
<Android/testbed/app/build.gradle.kts>`
   file to copy the following items into your project. All except your own 
Python
-  code can be copied from ``cross-build/HOST/prefix/lib``:
+  code can be copied from ``prefix/lib``:
 
   * In your JNI libraries:
 

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to