11#!/usr/bin/env python3
22
3+ from gzip import GzipFile
4+ import hashlib
35import json
46from os .path import (
57 dirname , join , isfile , realpath ,
68 relpath , split , exists , basename
79)
8- from os import listdir , makedirs , remove
10+ from os import environ , listdir , makedirs , remove
911import os
1012import shlex
1113import shutil
@@ -87,6 +89,10 @@ def get_bootstrap_name():
8789 join (curdir , 'templates' )))
8890
8991
92+ DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS = 'org.kivy.android.PythonActivity'
93+ DEFAULT_PYTHON_SERVICE_JAVA_CLASS = 'org.kivy.android.PythonService'
94+
95+
9096def ensure_dir (path ):
9197 if not exists (path ):
9298 makedirs (path )
@@ -161,16 +167,25 @@ def select(fn):
161167 return False
162168 return not is_blacklist (fn )
163169
170+ def clean (tinfo ):
171+ """cleaning function (for reproducible builds)"""
172+ tinfo .uid = tinfo .gid = 0
173+ tinfo .uname = tinfo .gname = ''
174+ tinfo .mtime = 0
175+ return tinfo
176+
164177 # get the files and relpath file of all the directory we asked for
165178 files = []
166179 for sd in source_dirs :
167180 sd = realpath (sd )
168181 compile_dir (sd , optimize_python = optimize_python )
169182 files += [(x , relpath (realpath (x ), sd )) for x in listfiles (sd )
170183 if select (x )]
184+ files .sort () # deterministic
171185
172186 # create tar.gz of thoses files
173- tf = tarfile .open (tfn , 'w:gz' , format = tarfile .USTAR_FORMAT )
187+ gf = GzipFile (tfn , 'wb' , mtime = 0 ) # deterministic
188+ tf = tarfile .open (None , 'w' , gf , format = tarfile .USTAR_FORMAT )
174189 dirs = []
175190 for fn , afn in files :
176191 dn = dirname (afn )
@@ -189,8 +204,9 @@ def select(fn):
189204 tf .addfile (tinfo )
190205
191206 # put the file
192- tf .add (fn , afn )
207+ tf .add (fn , afn , filter = clean )
193208 tf .close ()
209+ gf .close ()
194210
195211
196212def compile_dir (dfn , optimize_python = True ):
@@ -421,6 +437,7 @@ def make_package(args):
421437 service = True
422438
423439 service_names = []
440+ base_service_class = args .service_class_name .split ('.' )[- 1 ]
424441 for sid , spec in enumerate (args .services ):
425442 spec = spec .split (':' )
426443 name = spec [0 ]
@@ -445,6 +462,7 @@ def make_package(args):
445462 foreground = foreground ,
446463 sticky = sticky ,
447464 service_id = sid + 1 ,
465+ base_service_class = base_service_class ,
448466 )
449467
450468 # Find the SDK directory and target API
@@ -524,9 +542,18 @@ def make_package(args):
524542 versioned_name = versioned_name )
525543
526544 # String resources:
545+ timestamp = time .time ()
546+ if 'SOURCE_DATE_EPOCH' in environ :
547+ # for reproducible builds
548+ timestamp = int (environ ['SOURCE_DATE_EPOCH' ])
549+ private_version = "{} {} {}" .format (
550+ args .version ,
551+ args .numeric_version ,
552+ timestamp
553+ )
527554 render_args = {
528555 "args" : args ,
529- "private_version" : str ( time . time () )
556+ "private_version" : hashlib . sha1 ( private_version . encode ()). hexdigest ( )
530557 }
531558 if get_bootstrap_name () == "sdl2" :
532559 render_args ["url_scheme" ] = url_scheme
@@ -687,7 +714,7 @@ def parse_args_and_make_package(args=None):
687714 help = ('Enable the AndroidX support library, '
688715 'requires api = 28 or greater' ))
689716 ap .add_argument ('--android-entrypoint' , dest = 'android_entrypoint' ,
690- default = 'org.kivy.android.PythonActivity' ,
717+ default = DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS ,
691718 help = 'Defines which java class will be used for startup, usually a subclass of PythonActivity' )
692719 ap .add_argument ('--android-apptheme' , dest = 'android_apptheme' ,
693720 default = '@android:style/Theme.NoTitleBar' ,
@@ -786,9 +813,16 @@ def parse_args_and_make_package(args=None):
786813 ap .add_argument ('--extra-manifest-xml' , default = '' ,
787814 help = ('Extra xml to write directly inside the <manifest> element of'
788815 'AndroidManifest.xml' ))
816+ ap .add_argument ('--extra-manifest-application-arguments' , default = '' ,
817+ help = 'Extra arguments to be added to the <manifest><application> tag of'
818+ 'AndroidManifest.xml' )
789819 ap .add_argument ('--manifest-placeholders' , dest = 'manifest_placeholders' ,
790820 default = '[:]' , help = ('Inject build variables into the manifest '
791821 'via the manifestPlaceholders property' ))
822+ ap .add_argument ('--service-class-name' , dest = 'service_class_name' , default = DEFAULT_PYTHON_SERVICE_JAVA_CLASS ,
823+ help = 'Use that parameter if you need to implement your own PythonServive Java class' )
824+ ap .add_argument ('--activity-class-name' , dest = 'activity_class_name' , default = DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS ,
825+ help = 'The full java class name of the main activity' )
792826
793827 # Put together arguments, and add those from .p4a config file:
794828 if args is None :
@@ -808,6 +842,7 @@ def _read_configuration():
808842 _read_configuration ()
809843
810844 args = ap .parse_args (args )
845+
811846 args .ignore_path = []
812847
813848 if args .name and args .name [0 ] == '"' and args .name [- 1 ] == '"' :
0 commit comments