mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 19:34:08 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									45a3ab5a81
								
							
						
					
					
						commit
						fe5c4c53e7
					
				
					 7 changed files with 287 additions and 124 deletions
				
			
		| 
						 | 
					@ -1,19 +1,22 @@
 | 
				
			||||||
# Python for Android
 | 
					# Python for Android
 | 
				
			||||||
 | 
					
 | 
				
			||||||
These instructions are only needed if you're planning to compile Python for
 | 
					If you obtained this README as part of a release package, then the only
 | 
				
			||||||
Android yourself. Most users should *not* need to do this. Instead, use one of
 | 
					applicable sections are "Prerequisites", "Testing", and "Using in your own app".
 | 
				
			||||||
the tools listed in `Doc/using/android.rst`, which will provide a much easier
 | 
					
 | 
				
			||||||
experience.
 | 
					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
 | 
					## Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
First, make sure you have all the usual tools and libraries needed to build
 | 
					If you already have an Android SDK installed, export the `ANDROID_HOME`
 | 
				
			||||||
Python for your development machine.
 | 
					environment variable to point at its location. Otherwise, here's how to install
 | 
				
			||||||
 | 
					it:
 | 
				
			||||||
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:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Download the "Command line tools" from <https://developer.android.com/studio>.
 | 
					* Download the "Command line tools" from <https://developer.android.com/studio>.
 | 
				
			||||||
* Create a directory `android-sdk/cmdline-tools`, and unzip the command line
 | 
					* 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`
 | 
					* `curl`
 | 
				
			||||||
* `java` (or set the `JAVA_HOME` environment variable)
 | 
					* `java` (or set the `JAVA_HOME` environment variable)
 | 
				
			||||||
* `tar`
 | 
					* `tar`
 | 
				
			||||||
* `unzip`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Building
 | 
					## Building
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Python can be built for Android on any POSIX platform supported by the Android
 | 
					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
 | 
					development tools, which currently means Linux or macOS.
 | 
				
			||||||
cross-build where you use a "build" Python (for your development machine) to
 | 
					
 | 
				
			||||||
help produce a "host" Python for Android.
 | 
					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
 | 
					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
 | 
					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
 | 
					./android.py build HOST
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In the end you should have a build Python in `cross-build/build`, and an Android
 | 
					In the end you should have a build Python in `cross-build/build`, and a host
 | 
				
			||||||
build in `cross-build/HOST`.
 | 
					Python in `cross-build/HOST`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can use `--` as a separator for any of the `configure`-related commands –
 | 
					You can use `--` as a separator for any of the `configure`-related commands –
 | 
				
			||||||
including `build` itself – to pass arguments to the underlying `configure`
 | 
					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
 | 
					## 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
 | 
					* On Linux, the emulator needs access to the KVM virtualization interface, and
 | 
				
			||||||
  a DISPLAY environment variable pointing at an X server.
 | 
					  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
 | 
					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
 | 
					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
 | 
					manually to the same value, or use the Android Studio Device Manager, which will
 | 
				
			||||||
update both files.
 | 
					update both files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Before running the test suite, follow the instructions in the previous section
 | 
					You can run the test suite either:
 | 
				
			||||||
to build the architecture you want to test. Then run the test script in one of
 | 
					
 | 
				
			||||||
the following modes:
 | 
					* 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
 | 
					* In `--connected` mode, it runs on a device or emulator you have already
 | 
				
			||||||
  connected to the build machine. List the available devices with
 | 
					  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
 | 
					## Using in your own app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See `Doc/using/android.rst`.
 | 
					See https://docs.python.org/3/using/android.html.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import asyncio
 | 
					import asyncio
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
from glob import glob
 | 
					 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import shlex
 | 
					import shlex
 | 
				
			||||||
| 
						 | 
					@ -13,6 +12,8 @@ import sys
 | 
				
			||||||
import sysconfig
 | 
					import sysconfig
 | 
				
			||||||
from asyncio import wait_for
 | 
					from asyncio import wait_for
 | 
				
			||||||
from contextlib import asynccontextmanager
 | 
					from contextlib import asynccontextmanager
 | 
				
			||||||
 | 
					from datetime import datetime, timezone
 | 
				
			||||||
 | 
					from glob import glob
 | 
				
			||||||
from os.path import basename, relpath
 | 
					from os.path import basename, relpath
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from subprocess import CalledProcessError
 | 
					from subprocess import CalledProcessError
 | 
				
			||||||
| 
						 | 
					@ -20,11 +21,12 @@ from tempfile import TemporaryDirectory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SCRIPT_NAME = Path(__file__).name
 | 
					SCRIPT_NAME = Path(__file__).name
 | 
				
			||||||
CHECKOUT = Path(__file__).resolve().parent.parent
 | 
					ANDROID_DIR = Path(__file__).resolve().parent
 | 
				
			||||||
ANDROID_DIR = CHECKOUT / "Android"
 | 
					CHECKOUT = ANDROID_DIR.parent
 | 
				
			||||||
TESTBED_DIR = ANDROID_DIR / "testbed"
 | 
					TESTBED_DIR = ANDROID_DIR / "testbed"
 | 
				
			||||||
CROSS_BUILD_DIR = CHECKOUT / "cross-build"
 | 
					CROSS_BUILD_DIR = CHECKOUT / "cross-build"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HOSTS = ["aarch64-linux-android", "x86_64-linux-android"]
 | 
				
			||||||
APP_ID = "org.python.testbed"
 | 
					APP_ID = "org.python.testbed"
 | 
				
			||||||
DECODE_ARGS = ("UTF-8", "backslashreplace")
 | 
					DECODE_ARGS = ("UTF-8", "backslashreplace")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,12 +60,10 @@ def delete_glob(pattern):
 | 
				
			||||||
            path.unlink()
 | 
					            path.unlink()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def subdir(name, *, clean=None):
 | 
					def subdir(*parts, create=False):
 | 
				
			||||||
    path = CROSS_BUILD_DIR / name
 | 
					    path = CROSS_BUILD_DIR.joinpath(*parts)
 | 
				
			||||||
    if clean:
 | 
					 | 
				
			||||||
        delete_glob(path)
 | 
					 | 
				
			||||||
    if not path.exists():
 | 
					    if not path.exists():
 | 
				
			||||||
        if clean is None:
 | 
					        if not create:
 | 
				
			||||||
            sys.exit(
 | 
					            sys.exit(
 | 
				
			||||||
                f"{path} does not exist. Create it by running the appropriate "
 | 
					                f"{path} does not exist. Create it by running the appropriate "
 | 
				
			||||||
                f"`configure` subcommand of {SCRIPT_NAME}.")
 | 
					                f"`configure` subcommand of {SCRIPT_NAME}.")
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,9 @@ def build_python_path():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def configure_build_python(context):
 | 
					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")]
 | 
					    command = [relpath(CHECKOUT / "configure")]
 | 
				
			||||||
    if context.args:
 | 
					    if context.args:
 | 
				
			||||||
| 
						 | 
					@ -153,18 +155,17 @@ def download(url, target_dir="."):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def configure_host_python(context):
 | 
					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"
 | 
					    prefix_dir = host_dir / "prefix"
 | 
				
			||||||
    if not prefix_dir.exists():
 | 
					    if not prefix_dir.exists():
 | 
				
			||||||
        prefix_dir.mkdir()
 | 
					        prefix_dir.mkdir()
 | 
				
			||||||
        os.chdir(prefix_dir)
 | 
					        os.chdir(prefix_dir)
 | 
				
			||||||
        unpack_deps(context.host)
 | 
					        unpack_deps(context.host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_dir = host_dir / "build"
 | 
					    os.chdir(host_dir)
 | 
				
			||||||
    build_dir.mkdir(exist_ok=True)
 | 
					 | 
				
			||||||
    os.chdir(build_dir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    command = [
 | 
					    command = [
 | 
				
			||||||
        # Basic cross-compiling configuration
 | 
					        # Basic cross-compiling configuration
 | 
				
			||||||
        relpath(CHECKOUT / "configure"),
 | 
					        relpath(CHECKOUT / "configure"),
 | 
				
			||||||
| 
						 | 
					@ -193,11 +194,10 @@ def make_host_python(context):
 | 
				
			||||||
    # the build.
 | 
					    # the build.
 | 
				
			||||||
    host_dir = subdir(context.host)
 | 
					    host_dir = subdir(context.host)
 | 
				
			||||||
    prefix_dir = host_dir / "prefix"
 | 
					    prefix_dir = host_dir / "prefix"
 | 
				
			||||||
    delete_glob(f"{prefix_dir}/include/python*")
 | 
					    for pattern in ("include/python*", "lib/libpython*", "lib/python*"):
 | 
				
			||||||
    delete_glob(f"{prefix_dir}/lib/libpython*")
 | 
					        delete_glob(f"{prefix_dir}/{pattern}")
 | 
				
			||||||
    delete_glob(f"{prefix_dir}/lib/python*")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    os.chdir(host_dir / "build")
 | 
					    os.chdir(host_dir)
 | 
				
			||||||
    run(["make", "-j", str(os.cpu_count())], host=context.host)
 | 
					    run(["make", "-j", str(os.cpu_count())], host=context.host)
 | 
				
			||||||
    run(["make", "install", f"prefix={prefix_dir}"], host=context.host)
 | 
					    run(["make", "install", f"prefix={prefix_dir}"], host=context.host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -209,8 +209,13 @@ def build_all(context):
 | 
				
			||||||
        step(context)
 | 
					        step(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def clean(host):
 | 
				
			||||||
 | 
					    delete_glob(CROSS_BUILD_DIR / host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def clean_all(context):
 | 
					def clean_all(context):
 | 
				
			||||||
    delete_glob(CROSS_BUILD_DIR)
 | 
					    for host in HOSTS + ["build"]:
 | 
				
			||||||
 | 
					        clean(host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def setup_sdk():
 | 
					def setup_sdk():
 | 
				
			||||||
| 
						 | 
					@ -234,31 +239,27 @@ def setup_sdk():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# To avoid distributing compiled artifacts without corresponding source code,
 | 
					# To avoid distributing compiled artifacts without corresponding source code,
 | 
				
			||||||
# the Gradle wrapper is not included in the CPython repository. Instead, we
 | 
					# 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():
 | 
					def setup_testbed():
 | 
				
			||||||
    if all((TESTBED_DIR / path).exists() for path in [
 | 
					    # The Gradle version used for the build is specified in
 | 
				
			||||||
        "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.jar",
 | 
					    # 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
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ver_long = "8.7.0"
 | 
					    for path in paths:
 | 
				
			||||||
    ver_short = ver_long.removesuffix(".0")
 | 
					        out_path = TESTBED_DIR / path
 | 
				
			||||||
 | 
					        out_path.parent.mkdir(exist_ok=True)
 | 
				
			||||||
    for filename in ["gradlew", "gradlew.bat"]:
 | 
					        download(
 | 
				
			||||||
        out_path = download(
 | 
					            f"https://raw.githubusercontent.com/gradle/gradle/v{version}/{path}",
 | 
				
			||||||
            f"https://raw.githubusercontent.com/gradle/gradle/v{ver_long}/{filename}",
 | 
					            out_path.parent,
 | 
				
			||||||
            TESTBED_DIR)
 | 
					        )
 | 
				
			||||||
        os.chmod(out_path, 0o755)
 | 
					        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
 | 
					# 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.
 | 
					# 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]
 | 
					        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
 | 
					# 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.
 | 
					# by the buildbot worker, we'll make an attempt to clean up our subprocesses.
 | 
				
			||||||
def install_signal_handler():
 | 
					def install_signal_handler():
 | 
				
			||||||
| 
						 | 
					@ -550,6 +618,8 @@ def install_signal_handler():
 | 
				
			||||||
def parse_args():
 | 
					def parse_args():
 | 
				
			||||||
    parser = argparse.ArgumentParser()
 | 
					    parser = argparse.ArgumentParser()
 | 
				
			||||||
    subcommands = parser.add_subparsers(dest="subcommand")
 | 
					    subcommands = parser.add_subparsers(dest="subcommand")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Subcommands
 | 
				
			||||||
    build = subcommands.add_parser("build", help="Build everything")
 | 
					    build = subcommands.add_parser("build", help="Build everything")
 | 
				
			||||||
    configure_build = subcommands.add_parser("configure-build",
 | 
					    configure_build = subcommands.add_parser("configure-build",
 | 
				
			||||||
                                             help="Run `configure` for the "
 | 
					                                             help="Run `configure` for the "
 | 
				
			||||||
| 
						 | 
					@ -561,25 +631,27 @@ def parse_args():
 | 
				
			||||||
    make_host = subcommands.add_parser("make-host",
 | 
					    make_host = subcommands.add_parser("make-host",
 | 
				
			||||||
                                       help="Run `make` for Android")
 | 
					                                       help="Run `make` for Android")
 | 
				
			||||||
    subcommands.add_parser(
 | 
					    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:
 | 
					    for subcommand in build, configure_build, configure_host:
 | 
				
			||||||
        subcommand.add_argument(
 | 
					        subcommand.add_argument(
 | 
				
			||||||
            "--clean", action="store_true", default=False, dest="clean",
 | 
					            "--clean", action="store_true", default=False, dest="clean",
 | 
				
			||||||
            help="Delete any relevant directories before building")
 | 
					            help="Delete the relevant build and prefix directories first")
 | 
				
			||||||
    for subcommand in build, configure_host, make_host:
 | 
					    for subcommand in [build, configure_host, make_host, package]:
 | 
				
			||||||
        subcommand.add_argument(
 | 
					        subcommand.add_argument(
 | 
				
			||||||
            "host", metavar="HOST",
 | 
					            "host", metavar="HOST", choices=HOSTS,
 | 
				
			||||||
            choices=["aarch64-linux-android", "x86_64-linux-android"],
 | 
					 | 
				
			||||||
            help="Host triplet: choices=[%(choices)s]")
 | 
					            help="Host triplet: choices=[%(choices)s]")
 | 
				
			||||||
    for subcommand in build, configure_build, configure_host:
 | 
					    for subcommand in build, configure_build, configure_host:
 | 
				
			||||||
        subcommand.add_argument("args", nargs="*",
 | 
					        subcommand.add_argument("args", nargs="*",
 | 
				
			||||||
                                help="Extra arguments to pass to `configure`")
 | 
					                                help="Extra arguments to pass to `configure`")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subcommands.add_parser(
 | 
					    # Test arguments
 | 
				
			||||||
        "build-testbed", help="Build the testbed app")
 | 
					 | 
				
			||||||
    test = subcommands.add_parser(
 | 
					 | 
				
			||||||
        "test", help="Run the test suite")
 | 
					 | 
				
			||||||
    test.add_argument(
 | 
					    test.add_argument(
 | 
				
			||||||
        "-v", "--verbose", action="count", default=0,
 | 
					        "-v", "--verbose", action="count", default=0,
 | 
				
			||||||
        help="Show Gradle output, and non-Python logcat messages. "
 | 
					        help="Show Gradle output, and non-Python logcat messages. "
 | 
				
			||||||
| 
						 | 
					@ -608,14 +680,17 @@ def main():
 | 
				
			||||||
        stream.reconfigure(line_buffering=True)
 | 
					        stream.reconfigure(line_buffering=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context = parse_args()
 | 
					    context = parse_args()
 | 
				
			||||||
    dispatch = {"configure-build": configure_build_python,
 | 
					    dispatch = {
 | 
				
			||||||
 | 
					        "configure-build": configure_build_python,
 | 
				
			||||||
        "make-build": make_build_python,
 | 
					        "make-build": make_build_python,
 | 
				
			||||||
        "configure-host": configure_host_python,
 | 
					        "configure-host": configure_host_python,
 | 
				
			||||||
        "make-host": make_host_python,
 | 
					        "make-host": make_host_python,
 | 
				
			||||||
        "build": build_all,
 | 
					        "build": build_all,
 | 
				
			||||||
        "clean": clean_all,
 | 
					        "clean": clean_all,
 | 
				
			||||||
        "build-testbed": build_testbed,
 | 
					        "build-testbed": build_testbed,
 | 
				
			||||||
                "test": run_testbed}
 | 
					        "test": run_testbed,
 | 
				
			||||||
 | 
					        "package": package,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        result = dispatch[context.subcommand](context)
 | 
					        result = dispatch[context.subcommand](context)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								Android/testbed/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								Android/testbed/.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -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
 | 
				
			||||||
/gradlew.bat
 | 
					/gradlew.bat
 | 
				
			||||||
/gradle/wrapper/gradle-wrapper.jar
 | 
					/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
 | 
					*.iml
 | 
				
			||||||
.gradle
 | 
					.gradle
 | 
				
			||||||
/local.properties
 | 
					/local.properties
 | 
				
			||||||
/.idea/caches
 | 
					 | 
				
			||||||
/.idea/deploymentTargetDropdown.xml
 | 
					 | 
				
			||||||
/.idea/libraries
 | 
					 | 
				
			||||||
/.idea/modules.xml
 | 
					 | 
				
			||||||
/.idea/workspace.xml
 | 
					 | 
				
			||||||
/.idea/navEditor.xml
 | 
					 | 
				
			||||||
/.idea/assetWizardSettings.xml
 | 
					 | 
				
			||||||
.DS_Store
 | 
					.DS_Store
 | 
				
			||||||
/build
 | 
					/build
 | 
				
			||||||
/captures
 | 
					/captures
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								Android/testbed/.idea/inspectionProfiles/Project_Default.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Android/testbed/.idea/inspectionProfiles/Project_Default.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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>
 | 
				
			||||||
| 
						 | 
					@ -6,28 +6,71 @@ plugins {
 | 
				
			||||||
    id("org.jetbrains.kotlin.android")
 | 
					    id("org.jetbrains.kotlin.android")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val PYTHON_DIR = file("../../..").canonicalPath
 | 
					val ANDROID_DIR = file("../..")
 | 
				
			||||||
val PYTHON_CROSS_DIR = "$PYTHON_DIR/cross-build"
 | 
					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 ABIS = mapOf(
 | 
					val KNOWN_ABIS = mapOf(
 | 
				
			||||||
    "arm64-v8a" to "aarch64-linux-android",
 | 
					    "aarch64-linux-android" to "arm64-v8a",
 | 
				
			||||||
    "x86_64" to "x86_64-linux-android",
 | 
					    "x86_64-linux-android" to "x86_64",
 | 
				
			||||||
).filter { file("$PYTHON_CROSS_DIR/${it.value}").exists() }
 | 
					)
 | 
				
			||||||
if (ABIS.isEmpty()) {
 | 
					
 | 
				
			||||||
 | 
					// 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(
 | 
					    throw GradleException(
 | 
				
			||||||
        "No Android ABIs found in $PYTHON_CROSS_DIR: see Android/README.md " +
 | 
					        "No Android prefixes found: see README.md for testing instructions"
 | 
				
			||||||
        "for building instructions."
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val PYTHON_VERSION = file("$PYTHON_DIR/Include/patchlevel.h").useLines {
 | 
					// Detect Python versions and ABIs.
 | 
				
			||||||
    for (line in it) {
 | 
					lateinit var pythonVersion: String
 | 
				
			||||||
        val match = """#define PY_VERSION\s+"(\d+\.\d+)""".toRegex().find(line)
 | 
					var abis = HashMap<File, String>()
 | 
				
			||||||
        if (match != null) {
 | 
					for ((i, prefix) in prefixes.withIndex()) {
 | 
				
			||||||
            return@useLines match.groupValues[1]
 | 
					    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")
 | 
					        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]!!
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,10 +96,16 @@ android {
 | 
				
			||||||
        versionCode = 1
 | 
					        versionCode = 1
 | 
				
			||||||
        versionName = "1.0"
 | 
					        versionName = "1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ndk.abiFilters.addAll(ABIS.keys)
 | 
					        ndk.abiFilters.addAll(abis.values)
 | 
				
			||||||
        externalNativeBuild.cmake.arguments(
 | 
					        externalNativeBuild.cmake.arguments(
 | 
				
			||||||
            "-DPYTHON_CROSS_DIR=$PYTHON_CROSS_DIR",
 | 
					            "-DPYTHON_PREFIX_DIR=" + if (inSourceTree) {
 | 
				
			||||||
            "-DPYTHON_VERSION=$PYTHON_VERSION",
 | 
					                // 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",
 | 
					            "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,24 +182,25 @@ dependencies {
 | 
				
			||||||
// Create some custom tasks to copy Python and its standard library from
 | 
					// Create some custom tasks to copy Python and its standard library from
 | 
				
			||||||
// elsewhere in the repository.
 | 
					// elsewhere in the repository.
 | 
				
			||||||
androidComponents.onVariants { variant ->
 | 
					androidComponents.onVariants { variant ->
 | 
				
			||||||
    val pyPlusVer = "python$PYTHON_VERSION"
 | 
					    val pyPlusVer = "python$pythonVersion"
 | 
				
			||||||
    generateTask(variant, variant.sources.assets!!) {
 | 
					    generateTask(variant, variant.sources.assets!!) {
 | 
				
			||||||
        into("python") {
 | 
					        into("python") {
 | 
				
			||||||
 | 
					            // Include files such as pyconfig.h are used by some of the tests.
 | 
				
			||||||
            into("include/$pyPlusVer") {
 | 
					            into("include/$pyPlusVer") {
 | 
				
			||||||
                for (triplet in ABIS.values) {
 | 
					                for (prefix in prefixes) {
 | 
				
			||||||
                    from("$PYTHON_CROSS_DIR/$triplet/prefix/include/$pyPlusVer")
 | 
					                    from("$prefix/include/$pyPlusVer")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                duplicatesStrategy = DuplicatesStrategy.EXCLUDE
 | 
					                duplicatesStrategy = DuplicatesStrategy.EXCLUDE
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            into("lib/$pyPlusVer") {
 | 
					            into("lib/$pyPlusVer") {
 | 
				
			||||||
                // To aid debugging, the source directory takes priority.
 | 
					                // To aid debugging, the source directory takes priority when
 | 
				
			||||||
 | 
					                // running inside a CPython source tree.
 | 
				
			||||||
 | 
					                if (inSourceTree) {
 | 
				
			||||||
                    from("$PYTHON_DIR/Lib")
 | 
					                    from("$PYTHON_DIR/Lib")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                // The cross-build directory provides ABI-specific files such as
 | 
					                for (prefix in prefixes) {
 | 
				
			||||||
                // sysconfigdata.
 | 
					                    from("$prefix/lib/$pyPlusVer")
 | 
				
			||||||
                for (triplet in ABIS.values) {
 | 
					 | 
				
			||||||
                    from("$PYTHON_CROSS_DIR/$triplet/prefix/lib/$pyPlusVer")
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                into("site-packages") {
 | 
					                into("site-packages") {
 | 
				
			||||||
| 
						 | 
					@ -164,9 +214,9 @@ androidComponents.onVariants { variant ->
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    generateTask(variant, variant.sources.jniLibs!!) {
 | 
					    generateTask(variant, variant.sources.jniLibs!!) {
 | 
				
			||||||
        for ((abi, triplet) in ABIS.entries) {
 | 
					        for ((prefix, abi) in abis.entries) {
 | 
				
			||||||
            into(abi) {
 | 
					            into(abi) {
 | 
				
			||||||
                from("$PYTHON_CROSS_DIR/$triplet/prefix/lib")
 | 
					                from("$prefix/lib")
 | 
				
			||||||
                include("libpython*.*.so")
 | 
					                include("libpython*.*.so")
 | 
				
			||||||
                include("lib*_python.so")
 | 
					                include("lib*_python.so")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,14 @@
 | 
				
			||||||
cmake_minimum_required(VERSION 3.4.1)
 | 
					cmake_minimum_required(VERSION 3.4.1)
 | 
				
			||||||
project(testbed)
 | 
					project(testbed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(PREFIX_DIR ${PYTHON_CROSS_DIR}/${CMAKE_LIBRARY_ARCHITECTURE}/prefix)
 | 
					# Resolve variables from the command line.
 | 
				
			||||||
include_directories(${PREFIX_DIR}/include/python${PYTHON_VERSION})
 | 
					string(
 | 
				
			||||||
link_directories(${PREFIX_DIR}/lib)
 | 
					    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})
 | 
					link_libraries(log python${PYTHON_VERSION})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_library(main_activity SHARED main_activity.c)
 | 
					add_library(main_activity SHARED main_activity.c)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,9 +27,8 @@ details.
 | 
				
			||||||
Adding Python to an Android app
 | 
					Adding Python to an Android app
 | 
				
			||||||
-------------------------------
 | 
					-------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
These instructions are only needed if you're planning to compile Python for
 | 
					Most app developers should use one of the following tools, which will provide a
 | 
				
			||||||
Android yourself. Most users should *not* need to do this. Instead, use one of
 | 
					much easier experience:
 | 
				
			||||||
the following tools, which will provide a much easier experience:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `Briefcase <https://briefcase.readthedocs.io>`__, from the BeeWare project
 | 
					* `Briefcase <https://briefcase.readthedocs.io>`__, from the BeeWare project
 | 
				
			||||||
* `Buildozer <https://buildozer.readthedocs.io>`__, from the Kivy 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.
 | 
					link to the relevant file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Build Python by following the instructions in :source:`Android/README.md`.
 | 
					* 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>`
 | 
					* 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
 | 
					  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:
 | 
					  * In your JNI libraries:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue