diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..29a28f5 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,25 @@ +version = 1 + +test_patterns = ["tests/**"] + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" + + +[[analyzers]] +name = "docker" +enabled = true + + [analyzers.meta] + dockerfile_paths = [ + "dockerfile_dev", + "dockerfile_prod" + ] + +[[analyzers]] +name = "ruby" +enabled = true diff --git a/README.md b/README.md index 4445e65..64bf4f4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ your application requirements and settings such as title, icon, included modules etc. Buildozer will use that spec to create a package for Android, iOS, Windows, OSX and/or Linux. -Buildozer currently supports packaging for Android via the [python-for-android](http://github.com/kivy/python-for-android/) +Buildozer currently supports packaging for Android via the [python-for-android](https://github.com/kivy/python-for-android/) project, and for iOS via the kivy-ios project. iOS and OSX are still under work. For Android, buildozer will automatically download and prepare the @@ -24,7 +24,7 @@ build dependencies. For more information, see Note that only Python 3 is supported. Note that this tool has nothing to do with the eponymous online build service -[buildozer.io](http://buildozer.io). +[buildozer.io](https://buildozer.io). ## Installing Buildozer with target Python 3 (default): @@ -176,7 +176,7 @@ For [debugging on Android](https://python-for-android.readthedocs.io/en/stable/t ## Contributing We love pull requests and discussing novel ideas. Check out our -[contribution guide](http://kivy.org/docs/contribute.html) and +[contribution guide](https://kivy.org/docs/contribute.html) and feel free to improve buildozer. The following mailing list and IRC channel are used exclusively for diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 5184a80..84c1ec7 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -19,7 +19,7 @@ from buildozer.jsonstore import JsonStore from sys import stdout, stderr, exit from re import search from os.path import join, exists, dirname, realpath, splitext, expanduser -from subprocess import Popen, PIPE +from subprocess import Popen, PIPE, TimeoutExpired from os import environ, unlink, walk, sep, listdir, makedirs from copy import copy from shutil import copyfile, rmtree, copytree, move @@ -253,7 +253,7 @@ class Buildozer: def cmd(self, command, **kwargs): # prepare the environ, based on the system + our own env - env = copy(environ) + env = environ.copy() env.update(self.environ) # prepare the process @@ -269,15 +269,18 @@ class Buildozer: get_stderr = kwargs.pop('get_stderr', False) break_on_error = kwargs.pop('break_on_error', True) sensible = kwargs.pop('sensible', False) + run_condition = kwargs.pop('run_condition', None) + quiet = kwargs.pop('quiet', False) - if not sensible: - self.debug('Run {0!r}'.format(command)) - else: - if type(command) in (list, tuple): - self.debug('Run {0!r} ...'.format(command[0])) + if not quiet: + if not sensible: + self.debug('Run {0!r}'.format(command)) else: - self.debug('Run {0!r} ...'.format(command.split()[0])) - self.debug('Cwd {}'.format(kwargs.get('cwd'))) + if isinstance(command, (list, tuple)): + self.debug('Run {0!r} ...'.format(command[0])) + else: + self.debug('Run {0!r} ...'.format(command.split()[0])) + self.debug('Cwd {}'.format(kwargs.get('cwd'))) # open the process if sys.platform == 'win32': @@ -297,9 +300,9 @@ class Buildozer: ret_stdout = [] if get_stdout else None ret_stderr = [] if get_stderr else None - while True: + while not run_condition or run_condition(): try: - readx = select.select([fd_stdout, fd_stderr], [], [])[0] + readx = select.select([fd_stdout, fd_stderr], [], [], 1)[0] except select.error: break if fd_stdout in readx: @@ -322,7 +325,13 @@ class Buildozer: stdout.flush() stderr.flush() - process.communicate() + try: + process.communicate( + timeout=(1 if run_condition and not run_condition() else None) + ) + except TimeoutExpired: + pass + if process.returncode != 0 and break_on_error: self.error('Command failed: {0}'.format(command)) self.log_env(self.ERROR, kwargs['env']) @@ -337,10 +346,12 @@ class Buildozer: self.error('raising an issue with buildozer itself.') self.error('In case of a bug report, please add a full log with log_level = 2') raise BuildozerCommandException() + if ret_stdout: ret_stdout = b''.join(ret_stdout) if ret_stderr: ret_stderr = b''.join(ret_stderr) + return (ret_stdout.decode('utf-8', 'ignore') if ret_stdout else None, ret_stderr.decode('utf-8') if ret_stderr else None, process.returncode) @@ -349,7 +360,7 @@ class Buildozer: from pexpect import spawnu # prepare the environ, based on the system + our own env - env = copy(environ) + env = environ.copy() env.update(self.environ) # prepare the process @@ -924,8 +935,7 @@ class Buildozer: if not meth.__doc__: continue - doc = [x for x in - meth.__doc__.strip().splitlines()][0].strip() + doc = list(meth.__doc__.strip().splitlines())[0].strip() print(' {0:<18} {1}'.format(name, doc)) print('') diff --git a/buildozer/default.spec b/buildozer/default.spec index dac13fb..c03cf64 100644 --- a/buildozer/default.spec +++ b/buildozer/default.spec @@ -87,6 +87,10 @@ fullscreen = 0 # Lottie files can be created using various tools, like Adobe After Effect or Synfig. #android.presplash_lottie = "path/to/lottie/file.json" +# (str) Adaptive icon of the application (used if Android API level is 26+ at runtime) +#icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png +#icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png + # (list) Permissions #android.permissions = INTERNET @@ -178,6 +182,11 @@ fullscreen = 0 # (list) Gradle dependencies to add #android.gradle_dependencies = +# (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies' +# contains an 'androidx' package, or any package from Kotlin source. +# android.enable_androidx requires android.api >= 28 +#android.enable_androidx = False + # (list) add java compile options # this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option # see https://developer.android.com/studio/write/java8-support for further information @@ -235,6 +244,9 @@ fullscreen = 0 # (str) Android logcat filters to use #android.logcat_filters = *:S python:D +# (bool) Android logcat only display log for activity's pid +#android.logcat_pid_only = False + # (str) Android additional adb arguments #android.adb_args = -H host.docker.internal @@ -260,16 +272,25 @@ android.allow_backup = True # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] # android.manifest_placeholders = [:] +# (bool) disables the compilation of py to pyc/pyo files when packaging +# android.no-compile-pyo = True + # # Python for android (p4a) specific # -# (str) python-for-android fork to use, defaults to upstream (kivy) +# (str) python-for-android URL to use for checkout +#p4a.url = + +# (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy) #p4a.fork = kivy # (str) python-for-android branch to use, defaults to master #p4a.branch = master +# (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch +#p4a.commit = HEAD + # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #p4a.source_dir = @@ -317,9 +338,27 @@ ios.codesign.allowed = false # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" +# (str) The development team to use for signing the debug version +#ios.codesign.development_team.debug = + # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s +# (str) The development team to use for signing the release version +#ios.codesign.development_team.release = + +# (str) URL pointing to .ipa file to be installed +# This option should be defined along with `display_image_url` and `full_size_image_url` options. +#ios.manifest.app_url = + +# (str) URL pointing to an icon (57x57px) to be displayed during download +# This option should be defined along with `app_url` and `full_size_image_url` options. +#ios.manifest.display_image_url = + +# (str) URL pointing to a large icon (512x512px) to be used by iTunes +# This option should be defined along with `app_url` and `display_image_url` options. +#ios.manifest.full_size_image_url = + [buildozer] diff --git a/buildozer/target.py b/buildozer/target.py index 631462c..7262a43 100644 --- a/buildozer/target.py +++ b/buildozer/target.py @@ -234,7 +234,7 @@ class Target: This will clone the contents of a git repository to `buildozer.platform_dir`. The location of this repo can be - speficied via URL and branch name, or via a custom (local) + specified via URL and branch name, or via a custom (local) directory name. :Parameters: diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 809b648..6cac71b 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -33,6 +33,7 @@ from os.path import exists, join, realpath, expanduser, basename, relpath from platform import architecture from shutil import copyfile, rmtree from glob import glob +from time import sleep from buildozer.libs.version import parse from distutils.version import LooseVersion @@ -60,6 +61,7 @@ class TargetAndroid(Target): p4a_directory_name = "python-for-android" p4a_fork = 'kivy' p4a_branch = 'master' + p4a_commit = 'HEAD' p4a_apk_cmd = "apk --debug --bootstrap=" p4a_recommended_ndk_version = None extra_p4a_args = '' @@ -98,11 +100,15 @@ class TargetAndroid(Target): else: self.extra_p4a_args += ' --ignore-setup-py' + activity_class_name = self.buildozer.config.getdefault( 'app', 'android.activity_class_name', 'org.kivy.android.PythonActivity') if activity_class_name != 'org.kivy.android.PythonActivity': self.extra_p4a_args += ' --activity-class-name={}'.format(activity_class_name) + if self.buildozer.log_level >= 2: + self.extra_p4a_args += ' --debug' + self.warn_on_deprecated_tokens() def warn_on_deprecated_tokens(self): @@ -124,7 +130,7 @@ class TargetAndroid(Target): # Default p4a dir p4a_dir = join(self.buildozer.platform_dir, self.p4a_directory_name) - # Possibly overriden by user setting + # Possibly overridden by user setting system_p4a_dir = self.buildozer.config.getdefault('app', 'p4a.source_dir') if system_p4a_dir: p4a_dir = expanduser(system_p4a_dir) @@ -248,10 +254,7 @@ class TargetAndroid(Target): 'adb.exe') self.javac_cmd = self._locate_java('javac.exe') self.keytool_cmd = self._locate_java('keytool.exe') - elif platform in ('darwin', ): - self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb') - self.javac_cmd = self._locate_java('javac') - self.keytool_cmd = self._locate_java('keytool') + # darwin, linux else: self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb') self.javac_cmd = self._locate_java('javac') @@ -375,7 +378,7 @@ class TargetAndroid(Target): self.buildozer.info('Android ANT is missing, downloading') archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION) - url = 'http://archive.apache.org/dist/ant/binaries/' + url = 'https://archive.apache.org/dist/ant/binaries/' self.buildozer.download(url, archive, cwd=ant_dir) @@ -423,7 +426,7 @@ class TargetAndroid(Target): return ndk_dir import re - _version = re.search('(.+?)[a-z]', self.android_ndk_version).group(1) + _version = int(re.search(r'(\d+)', self.android_ndk_version).group(1)) self.buildozer.info('Android NDK is missing, downloading') # Welcome to the NDK URL hell! @@ -433,28 +436,23 @@ class TargetAndroid(Target): # from 10e on the URLs can be looked up at # https://developer.android.com/ndk/downloads/older_releases + is_darwin = platform == 'darwin' + is_linux = platform.startswith('linux') + if platform in ('win32', 'cygwin'): - # Checking of 32/64 bits at Windows from: http://stackoverflow.com/a/1405971/798575 + # Checking of 32/64 bits at Windows from: https://stackoverflow.com/a/1405971/798575 import struct archive = 'android-ndk-r{0}-windows-{1}.zip' is_64 = (8 * struct.calcsize("P") == 64) - - elif platform in ('darwin', ): - if _version >= '10e': - archive = 'android-ndk-r{0}-darwin-{1}.zip' - elif _version >= '10c': - archive = 'android-ndk-r{0}-darwin-{1}.bin' + elif is_darwin or is_linux: + _platform = 'linux' if is_linux else 'darwin' + if self.android_ndk_version in ['10c', '10d', '10e']: + ext = 'bin' + elif _version <= 10: + ext = 'tar.bz2' else: - archive = 'android-ndk-r{0}-darwin-{1}.tar.bz2' - is_64 = (os.uname()[4] == 'x86_64') - - elif platform.startswith('linux'): - if _version >= '10e': - archive = 'android-ndk-r{0}-linux-{1}.zip' - elif _version >= '10c': - archive = 'android-ndk-r{0}-linux-{1}.bin' - else: - archive = 'android-ndk-r{0}-linux-{1}.tar.bz2' + ext = 'zip' + archive = 'android-ndk-r{0}-' + _platform + '-{1}.' + ext is_64 = (os.uname()[4] == 'x86_64') else: raise SystemError('Unsupported platform: {}'.format(platform)) @@ -464,10 +462,10 @@ class TargetAndroid(Target): archive = archive.format(self.android_ndk_version, architecture) unpacked = unpacked.format(self.android_ndk_version) - if _version >= '10e': + if _version >= 11: url = 'https://dl.google.com/android/repository/' else: - url = 'http://dl.google.com/android/ndk/' + url = 'https://dl.google.com/android/ndk/' self.buildozer.download(url, archive, @@ -666,7 +664,7 @@ class TargetAndroid(Target): self.buildozer.error( 'You might have missed to install 32bits libs') self.buildozer.error( - 'Check http://buildozer.readthedocs.org/en/latest/installation.html') + 'Check https://buildozer.readthedocs.org/en/latest/installation.html') self.buildozer.error('') else: self.buildozer.error('') @@ -699,9 +697,15 @@ class TargetAndroid(Target): p4a_fork = self.buildozer.config.getdefault( 'app', 'p4a.fork', self.p4a_fork ) + p4a_url = self.buildozer.config.getdefault( + 'app', 'p4a.url', f'https://github.com/{p4a_fork}/python-for-android.git' + ) p4a_branch = self.buildozer.config.getdefault( 'app', 'p4a.branch', self.p4a_branch ) + p4a_commit = self.buildozer.config.getdefault( + 'app', 'p4a.commit', self.p4a_commit + ) p4a_dir = self.p4a_dir system_p4a_dir = self.buildozer.config.getdefault('app', @@ -714,21 +718,19 @@ class TargetAndroid(Target): self.buildozer.error('') raise BuildozerException() else: - # check that fork/branch has not been changed + # check that url/branch has not been changed if self.buildozer.file_exists(p4a_dir): - cur_fork = cmd( + cur_url = cmd( 'git config --get remote.origin.url', get_stdout=True, cwd=p4a_dir, - )[0].split('/')[3] + )[0].strip() cur_branch = cmd( 'git branch -vv', get_stdout=True, cwd=p4a_dir )[0].split()[1] - if any([cur_fork != p4a_fork, cur_branch != p4a_branch]): + if any([cur_url != p4a_url, cur_branch != p4a_branch]): self.buildozer.info( - "Detected old fork/branch ({}/{}), deleting...".format( - cur_fork, cur_branch - ) + f"Detected old url/branch ({cur_url}/{cur_branch}), deleting..." ) rmtree(p4a_dir) @@ -736,11 +738,10 @@ class TargetAndroid(Target): cmd( ( 'git clone -b {p4a_branch} --single-branch ' - 'https://github.com/{p4a_fork}/python-for-android.git ' - '{p4a_dir}' + '{p4a_url} {p4a_dir}' ).format( p4a_branch=p4a_branch, - p4a_fork=p4a_fork, + p4a_url=p4a_url, p4a_dir=self.p4a_directory_name, ), cwd=self.buildozer.platform_dir, @@ -755,6 +756,8 @@ class TargetAndroid(Target): cmd('git fetch --tags origin {0}:{0}'.format(p4a_branch), cwd=p4a_dir) cmd('git checkout {}'.format(p4a_branch), cwd=p4a_dir) + if p4a_commit != 'HEAD': + cmd('git reset --hard {}'.format(p4a_commit), cwd=p4a_dir) # also install dependencies (currently, only setup.py knows about it) # let's extract them. @@ -766,7 +769,7 @@ class TargetAndroid(Target): except IOError: self.buildozer.error('Failed to read python-for-android setup.py at {}'.format( join(self.p4a_dir, 'setup.py'))) - exit(1) + sys.exit(1) pip_deps = [] for dep in deps: pip_deps.append("'{}'".format(dep)) @@ -939,6 +942,11 @@ class TargetAndroid(Target): cmd.append('--manifest-placeholders') cmd.append("{}".format(manifest_placeholders)) + # support disabling of compilation + compile_py = self.buildozer.config.getdefault('app', 'android.no-compile-pyo', None) + if compile_py: + cmd.append('--no-compile-pyo') + cmd.append('--arch') cmd.append(self._arch) @@ -988,6 +996,12 @@ class TargetAndroid(Target): cwd=self.buildozer.global_platform_dir) self.buildozer.environ.pop('ANDROID_SERIAL', None) + while True: + if self._get_pid(): + break + sleep(.1) + self.buildozer.info('Waiting for application to start.') + self.buildozer.info('Application started.') def cmd_p4a(self, *args): @@ -1160,6 +1174,11 @@ class TargetAndroid(Target): icon = config.getdefault('app', 'icon.filename', '') if icon: build_cmd += [("--icon", join(self.buildozer.root_dir, icon))] + icon_fg = config.getdefault('app', 'icon.adaptive_foreground.filename', '') + icon_bg = config.getdefault('app', 'icon.adaptive_background.filename', '') + if icon_fg and icon_bg: + build_cmd += [("--icon-fg", join(self.buildozer.root_dir, icon_fg))] + build_cmd += [("--icon-bg", join(self.buildozer.root_dir, icon_bg))] # OUYA Console support ouya_category = config.getdefault('app', 'android.ouya.category', @@ -1193,6 +1212,13 @@ class TargetAndroid(Target): if wakelock: build_cmd += [("--wakelock", )] + # AndroidX ? + enable_androidx = config.getbooldefault('app', + 'android.enable_androidx', + False) + if enable_androidx: + build_cmd += [("--enable-androidx", )] + # intent filters intent_filters = config.getdefault( 'app', 'android.manifest.intent_filters', '') @@ -1313,7 +1339,7 @@ class TargetAndroid(Target): self.buildozer.error( 'Invalid library reference (path not found): {}'.format( cref)) - exit(1) + sys.exit(1) # get a relative path from the project file ref = relpath(ref, realpath(expanduser(dist_dir))) # ensure the reference exists @@ -1416,6 +1442,18 @@ class TargetAndroid(Target): self.buildozer.info('Application pushed.') + def _get_pid(self): + pid, *_ = self.buildozer.cmd( + f'{self.adb_cmd} shell pidof {self._get_package()}', + get_stdout=True, + show_output=False, + break_on_error=False, + quiet=True, + ) + if pid: + return pid.strip() + return False + def cmd_logcat(self, *args): '''Show the log from the device ''' @@ -1427,10 +1465,23 @@ class TargetAndroid(Target): "app", "android.logcat_filters", "", section_sep=":", split_char=" ") filters = " ".join(filters) self.buildozer.environ['ANDROID_SERIAL'] = serial[0] - self.buildozer.cmd('{adb} logcat {filters}'.format(adb=self.adb_cmd, - filters=filters), - cwd=self.buildozer.global_platform_dir, - show_output=True) + extra_args = [] + pid = None + if self.buildozer.config.getdefault('app', 'android.logcat_pid_only'): + pid = self._get_pid() + if pid: + extra_args.extend(('--pid', pid)) + + self.buildozer.cmd( + f"{self.adb_cmd} logcat {filters} {' '.join(extra_args)}", + cwd=self.buildozer.global_platform_dir, + show_output=True, + run_condition=self._get_pid if pid else None, + break_on_error=False, + ) + + self.buildozer.info(f"{self._get_package()} terminated") + self.buildozer.environ.pop('ANDROID_SERIAL', None) diff --git a/buildozer/targets/ios.py b/buildozer/targets/ios.py index 05eeedd..5323763 100644 --- a/buildozer/targets/ios.py +++ b/buildozer/targets/ios.py @@ -115,8 +115,8 @@ class TargetIos(Target): kwargs.setdefault('cwd', self.ios_dir) return self.buildozer.cmd(self._toolchain_cmd + cmd, **kwargs) - def xcodebuild(self, cmd='', **kwargs): - return self.buildozer.cmd(self._xcodebuild_cmd + cmd, **kwargs) + def xcodebuild(self, *args, **kwargs): + return self.buildozer.cmd(self._xcodebuild_cmd + ' '.join(arg for arg in args if arg is not None), **kwargs) @property def code_signing_allowed(self): @@ -124,6 +124,11 @@ class TargetIos(Target): allowed = "YES" if allowed else "NO" return f"CODE_SIGNING_ALLOWED={allowed}" + @property + def code_signing_development_team(self): + team = self.buildozer.config.getdefault("app", f"ios.codesign.development_team.{self.build_mode}", None) + return f"DEVELOPMENT_TEAM={team}" if team else None + def get_available_packages(self): available_modules = self.toolchain("recipes --compact", get_stdout=True)[0] return available_modules.splitlines()[0].split() @@ -202,7 +207,8 @@ class TargetIos(Target): plist_rfn = join(self.app_project_dir, plist_fn) version = self.buildozer.get_version() self.buildozer.info('Update Plist {}'.format(plist_fn)) - plist = plistlib.readPlist(plist_rfn) + with open(plist_rfn, 'rb') as f: + plist = plistlib.load(f) plist['CFBundleIdentifier'] = self._get_package() plist['CFBundleShortVersionString'] = version plist['CFBundleVersion'] = '{}.{}'.format(version, @@ -211,12 +217,36 @@ class TargetIos(Target): # add icons self._create_icons() + # Generate OTA distribution manifest if `app_url`, `display_image_url` and `full_size_image_url` are defined. + app_url = self.buildozer.config.getdefault("app", "ios.manifest.app_url", None) + display_image_url = self.buildozer.config.getdefault("app", "ios.manifest.display_image_url", None) + full_size_image_url = self.buildozer.config.getdefault("app", "ios.manifest.full_size_image_url", None) + + if any((app_url, display_image_url, full_size_image_url)): + + if not all((app_url, display_image_url, full_size_image_url)): + self.buildozer.error("Options ios.manifest.app_url, ios.manifest.display_image_url" + " and ios.manifest.full_size_image_url should be defined all together") + return + + plist['manifest'] = { + 'appURL': app_url, + 'displayImageURL': display_image_url, + 'fullSizeImageURL': full_size_image_url, + } + # ok, write the modified plist. - plistlib.writePlist(plist, plist_rfn) + with open(plist_rfn, 'wb') as f: + plistlib.dump(plist, f) mode = self.build_mode.capitalize() self.xcodebuild( - f"-configuration {mode} ENABLE_BITCODE=NO {self.code_signing_allowed} clean build", + f'-configuration {mode}', + '-allowProvisioningUpdates', + 'ENABLE_BITCODE=NO', + self.code_signing_allowed, + self.code_signing_development_team, + 'clean build', cwd=self.app_project_dir) ios_app_dir = '{app_lower}-ios/build/{mode}-iphoneos/{app_lower}.app'.format( app_lower=app_name.lower(), mode=mode) @@ -242,25 +272,24 @@ class TargetIos(Target): self.buildozer.rmdir(intermediate_dir) self.buildozer.info('Creating archive...') - self.xcodebuild(( - ' -alltargets' - ' -configuration {mode}' - ' -scheme {scheme}' - ' -archivePath "{xcarchive}"' - ' archive' - ' ENABLE_BITCODE=NO' - ).format(mode=mode, xcarchive=xcarchive, scheme=app_name.lower()), + self.xcodebuild( + '-alltargets', + f'-configuration {mode}', + f'-scheme {app_name.lower()}', + f'-archivePath "{xcarchive}"', + 'archive', + 'ENABLE_BITCODE=NO', + self.code_signing_development_team, cwd=build_dir) self.buildozer.info('Creating IPA...') - self.xcodebuild(( - ' -exportArchive' - ' -exportFormat IPA' - ' -archivePath "{xcarchive}"' - ' -exportPath "{ipa}"' - ' CODE_SIGN_IDENTITY={ioscodesign}' - ' ENABLE_BITCODE=NO' - ).format(xcarchive=xcarchive, ipa=ipa_tmp, ioscodesign=ioscodesign), + self.xcodebuild( + '-exportArchive', + f'-archivePath "{xcarchive}"', + f'-exportOptionsPlist "{plist_rfn}"', + f'-exportPath "{ipa_tmp}"', + f'CODE_SIGN_IDENTITY={ioscodesign}', + 'ENABLE_BITCODE=NO', cwd=build_dir) self.buildozer.info('Moving IPA to bin...') diff --git a/buildozer/targets/osx.py b/buildozer/targets/osx.py index c6313ba..6fd5d22 100644 --- a/buildozer/targets/osx.py +++ b/buildozer/targets/osx.py @@ -46,16 +46,16 @@ class TargetOSX(Target): self.buildozer.info('Downloading kivy...') status_code = check_output( ('curl', '-L', '--write-out', '%{http_code}', '-o', 'Kivy{}.dmg'.format(py_branch), - 'http://kivy.org/downloads/{}/Kivy-{}-osx-python{}.dmg' + 'https://kivy.org/downloads/{}/Kivy-{}-osx-python{}.dmg' .format(current_kivy_vers, current_kivy_vers, py_branch)), cwd=cwd) if status_code == "404": self.buildozer.error( "Unable to download the Kivy App. Check osx.kivy_version in your buildozer.spec, and verify " - "Kivy servers are accessible. http://kivy.org/downloads/") + "Kivy servers are accessible. https://kivy.org/downloads/") check_call(("rm", "Kivy{}.dmg".format(py_branch)), cwd=cwd) - exit(1) + sys.exit(1) self.buildozer.info('Extracting and installing Kivy...') check_call(('hdiutil', 'attach', cwd + '/Kivy{}.dmg'.format(py_branch))) @@ -76,8 +76,6 @@ class TargetOSX(Target): else: self.download_kivy(kivy_app_dir, py_branch) - return - def check_requirements(self): self.ensure_sdk() self.ensure_kivyapp() @@ -90,7 +88,7 @@ class TargetOSX(Target): len(errors))) for error in errors: print(error) - exit(1) + sys.exit(1) # check def build_package(self): @@ -177,7 +175,7 @@ class TargetOSX(Target): if not args: self.buildozer.error('Missing target command') self.buildozer.usage() - exit(1) + sys.exit(1) result = [] last_command = [] @@ -191,7 +189,7 @@ class TargetOSX(Target): if not last_command: self.buildozer.error('Argument passed without a command') self.buildozer.usage() - exit(1) + sys.exit(1) last_command.append(arg) if last_command: result.append(last_command) @@ -202,7 +200,7 @@ class TargetOSX(Target): command, args = item[0], item[1:] if not hasattr(self, 'cmd_{0}'.format(command)): self.buildozer.error('Unknown command {0}'.format(command)) - exit(1) + sys.exit(1) func = getattr(self, 'cmd_{0}'.format(command)) diff --git a/buildozer/tools/packer/Makefile b/buildozer/tools/packer/Makefile index 5021d91..8ae8b96 100644 --- a/buildozer/tools/packer/Makefile +++ b/buildozer/tools/packer/Makefile @@ -25,7 +25,7 @@ torrent: mktorrent \ -a ${TORRENT_ANNOUNCE} \ -o output-kivy-buildozer-vm/kivy-buildozer-vm.torrent \ - -w http://txzone.net/files/torrents/${PACKAGE_FILENAME} \ + -w https://txzone.net/files/torrents/${PACKAGE_FILENAME} \ -v output-kivy-buildozer-vm/${PACKAGE_FILENAME} upload: diff --git a/buildozer/tools/packer/scripts/additional-packages.sh b/buildozer/tools/packer/scripts/additional-packages.sh index b97ebe8..116cefe 100644 --- a/buildozer/tools/packer/scripts/additional-packages.sh +++ b/buildozer/tools/packer/scripts/additional-packages.sh @@ -3,7 +3,7 @@ # an error when using the android sdk: # "Can't read cryptographic policy directory: unlimited" -wget http://bootstrap.pypa.io/get-pip.py +wget https://bootstrap.pypa.io/get-pip.py python get-pip.py rm get-pip.py diff --git a/docs/Makefile b/docs/Makefile index 7048a95..0d57ec6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -9,7 +9,7 @@ BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://www.sphinx-doc.org/) endif # Internal variables. diff --git a/docs/make.bat b/docs/make.bat index c5f1be8..acd4fe5 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -56,7 +56,7 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) diff --git a/docs/source/contribute.rst b/docs/source/contribute.rst index f3c7575..0ce90ba 100644 --- a/docs/source/contribute.rst +++ b/docs/source/contribute.rst @@ -15,10 +15,10 @@ limitation. To test your own recipe via Buildozer, you need to: -#. Fork `Python for Android `_, and +#. Fork `Python for Android `_, and clone your own version (this will allow easy contribution later):: - git clone http://github.com/YOURNAME/python-for-android + git clone https://github.com/YOURNAME/python-for-android #. Change your `buildozer.spec` to reference your version:: @@ -44,6 +44,6 @@ include it in the python-for-android project, by issuing a Pull Request: git push origin master -#. Go to `http://github.com/YOURNAME/python-for-android`, and you should see +#. Go to `https://github.com/YOURNAME/python-for-android`, and you should see your new branch and a button "Pull Request" on it. Use it, write a description about what you did, and Send! diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 5bfd3f3..3f1b2af 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -1,3 +1,4 @@ + Installation ============ @@ -27,6 +28,48 @@ Android on Ubuntu 20.04 (64bit) # add the following line at the end of your ~/.bashrc file export PATH=$PATH:~/.local/bin/ +Android on Windows 10 +~~~~~~~~~~~~~~~~~~~~~ + +To use buildozer in Windows 10 you need first to enable Windows Subsystem for Linux (WSL) and install a Linux distribution: https://docs.microsoft.com/en-us/windows/wsl/install-win10. + +These instructions were tested with WSL 1 and Ubuntu 18.04 LTS. + +After installing WSL and Ubuntu in your Windows 10 machine, open Ubuntu and do this: + +1) Run the commands listed on the previous section (Android in Ubuntu 18.04 (64-bit). +2) Run the following commands: + +:: + + # Use here the python version you need + sudo apt install -y python3.7-venv + # Create a folder for buildozer. For example: C:\buildozer + mkdir /mnt/c/buildozer + cd /mnt/c/buildozer + python3.7 -m venv venv-buildozer + source venv/bin/activate + python -m pip install --upgrade pip + python -m pip install --upgrade wheel + python -m pip install --upgrade cython + python -m pip install --upgrade virtualenv + python -m pip install --upgrade buildozer + # Restart your WSL terminal to enable the path change + +Windows Subsystem for Linux does not have direct access to USB. Due to this, you need to install the Windows version of ADB (Android Debug Bridge): + +- Go to https://developer.android.com/studio/releases/platform-tools and click on "Download SDK Platform-Tools for Windows". + +- Unzip the downloaded file to a new folder. For example, "C:\\platform-tools". + +Before Using Buildozer +~~~~~~~~~~~~~~~~~~~~~~ + +If you wish, clone your code to a new folder, where the build process will run. + +You don't need to create a virtualenv for your code requirements. But just add these requirements to a configuration file called buildozer.spec as you will see in the following sections. + +Before running buildozer in your code folder, remember to go into the buildozer folder and activate the buildozer virtualenv. Android on macOS ~~~~~~~~~~~~~~~~ diff --git a/setup.py b/setup.py index 1df1fd9..01c2956 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ setup( long_description_content_type='text/markdown', author='Mathieu Virbel', author_email='mat@kivy.org', - url='http://github.com/kivy/buildozer', + url='https://github.com/kivy/buildozer', license='MIT', packages=[ 'buildozer', 'buildozer.targets', 'buildozer.libs', 'buildozer.scripts' diff --git a/tests/targets/test_android.py b/tests/targets/test_android.py index 170840e..94ee1e0 100644 --- a/tests/targets/test_android.py +++ b/tests/targets/test_android.py @@ -1,5 +1,6 @@ import os import tempfile +from six import StringIO from unittest import mock import pytest @@ -350,3 +351,44 @@ class TestTargetAndroid: ] ) ] + + def test_install_platform_p4a_clone_url(self): + """The `p4a.url` config should be used for cloning p4a before the `p4a.fork` option.""" + target_android = init_target(self.temp_dir, { + 'p4a.url': 'https://custom-p4a-url/p4a.git', + 'p4a.fork': 'myfork', + }) + + with patch_buildozer_cmd() as m_cmd, mock.patch('buildozer.targets.android.open') as m_open: + m_open.return_value = StringIO('install_reqs = []') # to stub setup.py parsing + target_android._install_p4a() + + assert mock.call( + 'git clone -b master --single-branch https://custom-p4a-url/p4a.git python-for-android', + cwd=mock.ANY) in m_cmd.call_args_list + + def test_install_platform_p4a_clone_fork(self): + """The `p4a.fork` config should be used for cloning p4a.""" + target_android = init_target(self.temp_dir, { + 'p4a.fork': 'fork' + }) + + with patch_buildozer_cmd() as m_cmd, mock.patch('buildozer.targets.android.open') as m_open: + m_open.return_value = StringIO('install_reqs = []') # to stub setup.py parsing + target_android._install_p4a() + + assert mock.call( + 'git clone -b master --single-branch https://github.com/fork/python-for-android.git python-for-android', + cwd=mock.ANY) in m_cmd.call_args_list + + def test_install_platform_p4a_clone_default(self): + """The default URL should be used for cloning p4a if no config options `p4a.url` and `p4a.fork` are set.""" + target_android = init_target(self.temp_dir) + + with patch_buildozer_cmd() as m_cmd, mock.patch('buildozer.targets.android.open') as m_open: + m_open.return_value = StringIO('install_reqs = []') # to stub setup.py parsing + target_android._install_p4a() + + assert mock.call( + 'git clone -b master --single-branch https://github.com/kivy/python-for-android.git python-for-android', + cwd=mock.ANY) in m_cmd.call_args_list diff --git a/tests/targets/test_ios.py b/tests/targets/test_ios.py index f5c5709..862d11a 100644 --- a/tests/targets/test_ios.py +++ b/tests/targets/test_ios.py @@ -182,7 +182,6 @@ class TestTargetIos: # fmt: off with patch_target_ios("_unlock_keychain") as m_unlock_keychain, \ patch_buildozer_error() as m_error, \ - patch_target_ios("xcodebuild") as m_xcodebuild, \ mock.patch("buildozer.targets.ios.plistlib.readPlist") as m_readplist, \ mock.patch("buildozer.targets.ios.plistlib.writePlist") as m_writeplist, \ patch_buildozer_cmd() as m_cmd: @@ -196,13 +195,6 @@ class TestTargetIos: 'You must fill the "ios.codesign.debug" token.' ) ] - assert m_xcodebuild.call_args_list == [ - mock.call( - "-configuration Debug ENABLE_BITCODE=NO " - "CODE_SIGNING_ALLOWED=NO clean build", - cwd="/ios/dir/myapp-ios", - ) - ] assert m_readplist.call_args_list == [ mock.call("/ios/dir/myapp-ios/myapp-Info.plist") ] @@ -216,4 +208,8 @@ class TestTargetIos: "/ios/dir/myapp-ios/myapp-Info.plist", ) ] - assert m_cmd.call_args_list == [mock.call(mock.ANY, cwd=target.ios_dir)] + assert m_cmd.call_args_list == [mock.call(mock.ANY, cwd=target.ios_dir), mock.call( + "xcodebuild -configuration Debug -allowProvisioningUpdates ENABLE_BITCODE=NO " + "CODE_SIGNING_ALLOWED=NO clean build", + cwd="/ios/dir/myapp-ios", + )] diff --git a/tests/targets/utils.py b/tests/targets/utils.py index 062820c..0c78712 100644 --- a/tests/targets/utils.py +++ b/tests/targets/utils.py @@ -53,7 +53,7 @@ def init_buildozer(temp_dir, target, options=None): spec = [] for line in default_spec: if line.strip(): - match = re.search(r"[#\s]?([a-z_\.]+)", line) + match = re.search(r"[#\s]?([0-9a-z_.]+)", line) key = match and match.group(1) if key in options: line = "{} = {}\n".format(key, options[key]) diff --git a/tests/test_buildozer.py b/tests/test_buildozer.py index 7f27c42..6ef385d 100644 --- a/tests/test_buildozer.py +++ b/tests/test_buildozer.py @@ -61,7 +61,7 @@ class TestBuildozer(unittest.TestCase): def test_buildozer_base(self): """ - Basic test making sure the Buildozer object can be instanciated. + Basic test making sure the Buildozer object can be instantiated. """ buildozer = Buildozer() assert buildozer.specfilename == 'buildozer.spec' @@ -158,7 +158,7 @@ class TestBuildozer(unittest.TestCase): assert m_file_extract.call_args_list == [mock.call(mock.ANY, cwd='/my/ant/path')] assert ant_path == my_ant_path assert download.call_args_list == [ - mock.call("http://archive.apache.org/dist/ant/binaries/", mock.ANY, cwd=my_ant_path)] + mock.call("https://archive.apache.org/dist/ant/binaries/", mock.ANY, cwd=my_ant_path)] # Mock ant already installed with mock.patch.object(Buildozer, 'file_exists', return_value=True): ant_path = target._install_apache_ant()