22
33import asyncio
44import argparse
5- from glob import glob
5+ import json
66import os
77import re
88import shlex
1313import sysconfig
1414from asyncio import wait_for
1515from contextlib import asynccontextmanager
16+ from datetime import datetime , timezone
17+ from glob import glob
1618from os .path import basename , relpath
1719from pathlib import Path
1820from subprocess import CalledProcessError
1921from tempfile import TemporaryDirectory
2022
2123
2224SCRIPT_NAME = Path (__file__ ).name
23- CHECKOUT = Path (__file__ ).resolve (). parent .parent
24- ANDROID_DIR = CHECKOUT / "Android"
25+ ANDROID_DIR = Path (__file__ ).resolve ().parent
26+ CHECKOUT = ANDROID_DIR . parent
2527BUILD_DIR = ANDROID_DIR / "build"
2628DIST_DIR = ANDROID_DIR / "dist"
2729PREFIX_DIR = ANDROID_DIR / "prefix"
@@ -544,6 +546,67 @@ async def run_testbed(context):
544546 raise e .exceptions [0 ]
545547
546548
549+ def package_version (prefix_subdir ):
550+ vars_glob = f"{ prefix_subdir } /lib/python*/_sysconfig_vars__android_*.json"
551+ vars_paths = glob (vars_glob )
552+ if len (vars_paths ) != 1 :
553+ sys .exit (f"{ vars_glob } matched { len (vars_paths )} paths." )
554+ with open (vars_paths [0 ]) as vars_file :
555+ version = json .load (vars_file )["py_version" ]
556+
557+ # If not building against a tagged commit, add a timestamp to the version.
558+ # Follow the PyPA version number rules, as this will make it easier to
559+ # process with other tools.
560+ if version .endswith ("+" ):
561+ version += datetime .now (timezone .utc ).strftime ("%Y%m%d.%H%M%S" )
562+
563+ return version
564+
565+
566+ def package (context ):
567+ prefix_subdir = subdir (PREFIX_DIR , context .host )
568+ version = package_version (prefix_subdir )
569+
570+ with TemporaryDirectory (prefix = SCRIPT_NAME ) as temp_dir :
571+ temp_dir = Path (temp_dir )
572+
573+ # All tracked files in the Android directory.
574+ for line in run (
575+ ["git" , "ls-files" ],
576+ cwd = ANDROID_DIR , capture_output = True , text = True , log = False ,
577+ ).stdout .splitlines ():
578+ src = ANDROID_DIR / line
579+ dst = temp_dir / line
580+ dst .parent .mkdir (parents = True , exist_ok = True )
581+ shutil .copy2 (src , dst , follow_symlinks = False )
582+
583+ # Anything in the prefix directory which could be useful either for
584+ # apps embedding Python, or packages built against it.
585+ for rel_dir , patterns in [
586+ ("include" , ["openssl*" , "python*" , "sqlite*" ]),
587+ ("lib" , ["engines-3" , "libcrypto*.so" , "libpython*" , "libsqlite*" ,
588+ "libssl*.so" , "ossl-modules" , "python*" ]),
589+ ("lib/pkgconfig" , ["*crypto*" , "*ssl*" , "*python*" , "*sqlite*" ]),
590+ ]:
591+ for pattern in patterns :
592+ for src in glob (f"{ prefix_subdir } /{ rel_dir } /{ pattern } " ):
593+ dst = temp_dir / relpath (src , ANDROID_DIR )
594+ dst .parent .mkdir (parents = True , exist_ok = True )
595+ if Path (src ).is_dir ():
596+ shutil .copytree (
597+ src , dst , symlinks = True ,
598+ ignore = lambda * args : ["__pycache__" ]
599+ )
600+ else :
601+ shutil .copy2 (src , dst , follow_symlinks = False )
602+
603+ DIST_DIR .mkdir (exist_ok = True )
604+ package_path = shutil .make_archive (
605+ f"{ DIST_DIR } /python-{ version } -{ context .host } " , "gztar" , temp_dir
606+ )
607+ print (f"Wrote { package_path } " )
608+
609+
547610# Handle SIGTERM the same way as SIGINT. This ensures that if we're terminated
548611# by the buildbot worker, we'll make an attempt to clean up our subprocesses.
549612def install_signal_handler ():
@@ -556,6 +619,8 @@ def signal_handler(*args):
556619def parse_args ():
557620 parser = argparse .ArgumentParser ()
558621 subcommands = parser .add_subparsers (dest = "subcommand" )
622+
623+ # Subcommands
559624 build = subcommands .add_parser ("build" , help = "Build everything" )
560625 configure_build = subcommands .add_parser ("configure-build" ,
561626 help = "Run `configure` for the "
@@ -567,25 +632,27 @@ def parse_args():
567632 make_host = subcommands .add_parser ("make-host" ,
568633 help = "Run `make` for Android" )
569634 subcommands .add_parser (
570- "clean" , help = "Delete the cross-build directory" )
635+ "clean" , help = "Delete all build and prefix directories" )
636+ subcommands .add_parser (
637+ "build-testbed" , help = "Build the testbed app" )
638+ test = subcommands .add_parser (
639+ "test" , help = "Run the test suite" )
640+ package = subcommands .add_parser ("package" , help = "Make a release package" )
571641
642+ # Common arguments
572643 for subcommand in build , configure_build , configure_host :
573644 subcommand .add_argument (
574645 "--clean" , action = "store_true" , default = False , dest = "clean" ,
575- help = "Delete any relevant directories before building " )
576- for subcommand in build , configure_host , make_host :
646+ help = "Delete the relevant build and prefix directories first " )
647+ for subcommand in [ build , configure_host , make_host , package ] :
577648 subcommand .add_argument (
578- "host" , metavar = "HOST" ,
579- choices = ["aarch64-linux-android" , "x86_64-linux-android" ],
649+ "host" , metavar = "HOST" , choices = HOSTS ,
580650 help = "Host triplet: choices=[%(choices)s]" )
581651 for subcommand in build , configure_build , configure_host :
582652 subcommand .add_argument ("args" , nargs = "*" ,
583653 help = "Extra arguments to pass to `configure`" )
584654
585- subcommands .add_parser (
586- "build-testbed" , help = "Build the testbed app" )
587- test = subcommands .add_parser (
588- "test" , help = "Run the test suite" )
655+ # Test arguments
589656 test .add_argument (
590657 "-v" , "--verbose" , action = "count" , default = 0 ,
591658 help = "Show Gradle output, and non-Python logcat messages. "
@@ -614,14 +681,17 @@ def main():
614681 stream .reconfigure (line_buffering = True )
615682
616683 context = parse_args ()
617- dispatch = {"configure-build" : configure_build_python ,
618- "make-build" : make_build_python ,
619- "configure-host" : configure_host_python ,
620- "make-host" : make_host_python ,
621- "build" : build_all ,
622- "clean" : clean_all ,
623- "build-testbed" : build_testbed ,
624- "test" : run_testbed }
684+ dispatch = {
685+ "configure-build" : configure_build_python ,
686+ "make-build" : make_build_python ,
687+ "configure-host" : configure_host_python ,
688+ "make-host" : make_host_python ,
689+ "build" : build_all ,
690+ "clean" : clean_all ,
691+ "build-testbed" : build_testbed ,
692+ "test" : run_testbed ,
693+ "package" : package ,
694+ }
625695
626696 try :
627697 result = dispatch [context .subcommand ](context )
0 commit comments