https://github.com/python/cpython/commit/38943bef251330a50a6c8c6aecdf892471d9d1aa commit: 38943bef251330a50a6c8c6aecdf892471d9d1aa branch: 3.13 author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com> committer: freakboy3742 <russ...@keith-magee.com> date: 2025-04-01T10:05:39+08:00 summary:
[3.13] gh-131531: Add `android.py package` command (GH-131532) (#131960) Adds a `package` entry point to the `android.py` build script to support creating an Android distribution artefact. (cherry picked from commit fe5c4c53e7bc6d780686013eaab17de2237b2176) Co-authored-by: Malcolm Smith <sm...@chaquo.com> 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 3daa545cc93746..2c55d94a07b619 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 6c17406c8724dc..3ad062d9d51a14 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]!! } @@ -42,10 +85,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", ) @@ -123,24 +172,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") { @@ -154,9 +204,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