Merge branch 'master' into master

This commit is contained in:
Veselin Penev 2021-08-17 18:02:38 +02:00 committed by GitHub
commit f9b1acf4a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 346 additions and 113 deletions

25
.deepsource.toml Normal file
View file

@ -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

View file

@ -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, etc. Buildozer will use that spec to create a package for Android, iOS, Windows,
OSX and/or Linux. 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. 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 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 only Python 3 is supported.
Note that this tool has nothing to do with the eponymous online build service 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): ## 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 ## Contributing
We love pull requests and discussing novel ideas. Check out our 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. feel free to improve buildozer.
The following mailing list and IRC channel are used exclusively for The following mailing list and IRC channel are used exclusively for

View file

@ -19,7 +19,7 @@ from buildozer.jsonstore import JsonStore
from sys import stdout, stderr, exit from sys import stdout, stderr, exit
from re import search from re import search
from os.path import join, exists, dirname, realpath, splitext, expanduser 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 os import environ, unlink, walk, sep, listdir, makedirs
from copy import copy from copy import copy
from shutil import copyfile, rmtree, copytree, move from shutil import copyfile, rmtree, copytree, move
@ -253,7 +253,7 @@ class Buildozer:
def cmd(self, command, **kwargs): def cmd(self, command, **kwargs):
# prepare the environ, based on the system + our own env # prepare the environ, based on the system + our own env
env = copy(environ) env = environ.copy()
env.update(self.environ) env.update(self.environ)
# prepare the process # prepare the process
@ -269,15 +269,18 @@ class Buildozer:
get_stderr = kwargs.pop('get_stderr', False) get_stderr = kwargs.pop('get_stderr', False)
break_on_error = kwargs.pop('break_on_error', True) break_on_error = kwargs.pop('break_on_error', True)
sensible = kwargs.pop('sensible', False) sensible = kwargs.pop('sensible', False)
run_condition = kwargs.pop('run_condition', None)
quiet = kwargs.pop('quiet', False)
if not sensible: if not quiet:
self.debug('Run {0!r}'.format(command)) if not sensible:
else: self.debug('Run {0!r}'.format(command))
if type(command) in (list, tuple):
self.debug('Run {0!r} ...'.format(command[0]))
else: else:
self.debug('Run {0!r} ...'.format(command.split()[0])) if isinstance(command, (list, tuple)):
self.debug('Cwd {}'.format(kwargs.get('cwd'))) 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 # open the process
if sys.platform == 'win32': if sys.platform == 'win32':
@ -297,9 +300,9 @@ class Buildozer:
ret_stdout = [] if get_stdout else None ret_stdout = [] if get_stdout else None
ret_stderr = [] if get_stderr else None ret_stderr = [] if get_stderr else None
while True: while not run_condition or run_condition():
try: try:
readx = select.select([fd_stdout, fd_stderr], [], [])[0] readx = select.select([fd_stdout, fd_stderr], [], [], 1)[0]
except select.error: except select.error:
break break
if fd_stdout in readx: if fd_stdout in readx:
@ -322,7 +325,13 @@ class Buildozer:
stdout.flush() stdout.flush()
stderr.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: if process.returncode != 0 and break_on_error:
self.error('Command failed: {0}'.format(command)) self.error('Command failed: {0}'.format(command))
self.log_env(self.ERROR, kwargs['env']) self.log_env(self.ERROR, kwargs['env'])
@ -337,10 +346,12 @@ class Buildozer:
self.error('raising an issue with buildozer itself.') 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') self.error('In case of a bug report, please add a full log with log_level = 2')
raise BuildozerCommandException() raise BuildozerCommandException()
if ret_stdout: if ret_stdout:
ret_stdout = b''.join(ret_stdout) ret_stdout = b''.join(ret_stdout)
if ret_stderr: if ret_stderr:
ret_stderr = b''.join(ret_stderr) ret_stderr = b''.join(ret_stderr)
return (ret_stdout.decode('utf-8', 'ignore') if ret_stdout else None, return (ret_stdout.decode('utf-8', 'ignore') if ret_stdout else None,
ret_stderr.decode('utf-8') if ret_stderr else None, ret_stderr.decode('utf-8') if ret_stderr else None,
process.returncode) process.returncode)
@ -349,7 +360,7 @@ class Buildozer:
from pexpect import spawnu from pexpect import spawnu
# prepare the environ, based on the system + our own env # prepare the environ, based on the system + our own env
env = copy(environ) env = environ.copy()
env.update(self.environ) env.update(self.environ)
# prepare the process # prepare the process
@ -924,8 +935,7 @@ class Buildozer:
if not meth.__doc__: if not meth.__doc__:
continue continue
doc = [x for x in doc = list(meth.__doc__.strip().splitlines())[0].strip()
meth.__doc__.strip().splitlines()][0].strip()
print(' {0:<18} {1}'.format(name, doc)) print(' {0:<18} {1}'.format(name, doc))
print('') print('')

View file

@ -87,6 +87,10 @@ fullscreen = 0
# Lottie files can be created using various tools, like Adobe After Effect or Synfig. # Lottie files can be created using various tools, like Adobe After Effect or Synfig.
#android.presplash_lottie = "path/to/lottie/file.json" #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 # (list) Permissions
#android.permissions = INTERNET #android.permissions = INTERNET
@ -178,6 +182,11 @@ fullscreen = 0
# (list) Gradle dependencies to add # (list) Gradle dependencies to add
#android.gradle_dependencies = #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 # (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option # 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 # see https://developer.android.com/studio/write/java8-support for further information
@ -235,6 +244,9 @@ fullscreen = 0
# (str) Android logcat filters to use # (str) Android logcat filters to use
#android.logcat_filters = *:S python:D #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 # (str) Android additional adb arguments
#android.adb_args = -H host.docker.internal #android.adb_args = -H host.docker.internal
@ -260,16 +272,25 @@ android.allow_backup = True
# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
# android.manifest_placeholders = [:] # 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 # 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 #p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master # (str) python-for-android branch to use, defaults to master
#p4a.branch = 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) # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir = #p4a.source_dir =
@ -317,9 +338,27 @@ ios.codesign.allowed = false
# Get a list of available identities: buildozer ios list_identities # Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)" #ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) The development team to use for signing the debug version
#ios.codesign.development_team.debug = <hexstring>
# (str) Name of the certificate to use for signing the release version # (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s #ios.codesign.release = %(ios.codesign.debug)s
# (str) The development team to use for signing the release version
#ios.codesign.development_team.release = <hexstring>
# (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] [buildozer]

View file

@ -234,7 +234,7 @@ class Target:
This will clone the contents of a git repository to This will clone the contents of a git repository to
`buildozer.platform_dir`. The location of this repo can be `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. directory name.
:Parameters: :Parameters:

View file

@ -33,6 +33,7 @@ from os.path import exists, join, realpath, expanduser, basename, relpath
from platform import architecture from platform import architecture
from shutil import copyfile, rmtree from shutil import copyfile, rmtree
from glob import glob from glob import glob
from time import sleep
from buildozer.libs.version import parse from buildozer.libs.version import parse
from distutils.version import LooseVersion from distutils.version import LooseVersion
@ -60,6 +61,7 @@ class TargetAndroid(Target):
p4a_directory_name = "python-for-android" p4a_directory_name = "python-for-android"
p4a_fork = 'kivy' p4a_fork = 'kivy'
p4a_branch = 'master' p4a_branch = 'master'
p4a_commit = 'HEAD'
p4a_apk_cmd = "apk --debug --bootstrap=" p4a_apk_cmd = "apk --debug --bootstrap="
p4a_recommended_ndk_version = None p4a_recommended_ndk_version = None
extra_p4a_args = '' extra_p4a_args = ''
@ -98,11 +100,15 @@ class TargetAndroid(Target):
else: else:
self.extra_p4a_args += ' --ignore-setup-py' self.extra_p4a_args += ' --ignore-setup-py'
activity_class_name = self.buildozer.config.getdefault( activity_class_name = self.buildozer.config.getdefault(
'app', 'android.activity_class_name', 'org.kivy.android.PythonActivity') 'app', 'android.activity_class_name', 'org.kivy.android.PythonActivity')
if 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) 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() self.warn_on_deprecated_tokens()
def warn_on_deprecated_tokens(self): def warn_on_deprecated_tokens(self):
@ -124,7 +130,7 @@ class TargetAndroid(Target):
# Default p4a dir # Default p4a dir
p4a_dir = join(self.buildozer.platform_dir, self.p4a_directory_name) 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') system_p4a_dir = self.buildozer.config.getdefault('app', 'p4a.source_dir')
if system_p4a_dir: if system_p4a_dir:
p4a_dir = expanduser(system_p4a_dir) p4a_dir = expanduser(system_p4a_dir)
@ -248,10 +254,7 @@ class TargetAndroid(Target):
'adb.exe') 'adb.exe')
self.javac_cmd = self._locate_java('javac.exe') self.javac_cmd = self._locate_java('javac.exe')
self.keytool_cmd = self._locate_java('keytool.exe') self.keytool_cmd = self._locate_java('keytool.exe')
elif platform in ('darwin', ): # darwin, linux
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')
else: else:
self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb') self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb')
self.javac_cmd = self._locate_java('javac') self.javac_cmd = self._locate_java('javac')
@ -375,7 +378,7 @@ class TargetAndroid(Target):
self.buildozer.info('Android ANT is missing, downloading') self.buildozer.info('Android ANT is missing, downloading')
archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION) 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, self.buildozer.download(url,
archive, archive,
cwd=ant_dir) cwd=ant_dir)
@ -423,7 +426,7 @@ class TargetAndroid(Target):
return ndk_dir return ndk_dir
import re 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') self.buildozer.info('Android NDK is missing, downloading')
# Welcome to the NDK URL hell! # Welcome to the NDK URL hell!
@ -433,28 +436,23 @@ class TargetAndroid(Target):
# from 10e on the URLs can be looked up at # from 10e on the URLs can be looked up at
# https://developer.android.com/ndk/downloads/older_releases # https://developer.android.com/ndk/downloads/older_releases
is_darwin = platform == 'darwin'
is_linux = platform.startswith('linux')
if platform in ('win32', 'cygwin'): 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 import struct
archive = 'android-ndk-r{0}-windows-{1}.zip' archive = 'android-ndk-r{0}-windows-{1}.zip'
is_64 = (8 * struct.calcsize("P") == 64) is_64 = (8 * struct.calcsize("P") == 64)
elif is_darwin or is_linux:
elif platform in ('darwin', ): _platform = 'linux' if is_linux else 'darwin'
if _version >= '10e': if self.android_ndk_version in ['10c', '10d', '10e']:
archive = 'android-ndk-r{0}-darwin-{1}.zip' ext = 'bin'
elif _version >= '10c': elif _version <= 10:
archive = 'android-ndk-r{0}-darwin-{1}.bin' ext = 'tar.bz2'
else: else:
archive = 'android-ndk-r{0}-darwin-{1}.tar.bz2' ext = 'zip'
is_64 = (os.uname()[4] == 'x86_64') archive = 'android-ndk-r{0}-' + _platform + '-{1}.' + ext
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'
is_64 = (os.uname()[4] == 'x86_64') is_64 = (os.uname()[4] == 'x86_64')
else: else:
raise SystemError('Unsupported platform: {}'.format(platform)) raise SystemError('Unsupported platform: {}'.format(platform))
@ -464,10 +462,10 @@ class TargetAndroid(Target):
archive = archive.format(self.android_ndk_version, architecture) archive = archive.format(self.android_ndk_version, architecture)
unpacked = unpacked.format(self.android_ndk_version) unpacked = unpacked.format(self.android_ndk_version)
if _version >= '10e': if _version >= 11:
url = 'https://dl.google.com/android/repository/' url = 'https://dl.google.com/android/repository/'
else: else:
url = 'http://dl.google.com/android/ndk/' url = 'https://dl.google.com/android/ndk/'
self.buildozer.download(url, self.buildozer.download(url,
archive, archive,
@ -666,7 +664,7 @@ class TargetAndroid(Target):
self.buildozer.error( self.buildozer.error(
'You might have missed to install 32bits libs') 'You might have missed to install 32bits libs')
self.buildozer.error( self.buildozer.error(
'Check http://buildozer.readthedocs.org/en/latest/installation.html') 'Check https://buildozer.readthedocs.org/en/latest/installation.html')
self.buildozer.error('') self.buildozer.error('')
else: else:
self.buildozer.error('') self.buildozer.error('')
@ -699,9 +697,15 @@ class TargetAndroid(Target):
p4a_fork = self.buildozer.config.getdefault( p4a_fork = self.buildozer.config.getdefault(
'app', 'p4a.fork', self.p4a_fork '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( p4a_branch = self.buildozer.config.getdefault(
'app', 'p4a.branch', self.p4a_branch 'app', 'p4a.branch', self.p4a_branch
) )
p4a_commit = self.buildozer.config.getdefault(
'app', 'p4a.commit', self.p4a_commit
)
p4a_dir = self.p4a_dir p4a_dir = self.p4a_dir
system_p4a_dir = self.buildozer.config.getdefault('app', system_p4a_dir = self.buildozer.config.getdefault('app',
@ -714,21 +718,19 @@ class TargetAndroid(Target):
self.buildozer.error('') self.buildozer.error('')
raise BuildozerException() raise BuildozerException()
else: else:
# check that fork/branch has not been changed # check that url/branch has not been changed
if self.buildozer.file_exists(p4a_dir): if self.buildozer.file_exists(p4a_dir):
cur_fork = cmd( cur_url = cmd(
'git config --get remote.origin.url', 'git config --get remote.origin.url',
get_stdout=True, get_stdout=True,
cwd=p4a_dir, cwd=p4a_dir,
)[0].split('/')[3] )[0].strip()
cur_branch = cmd( cur_branch = cmd(
'git branch -vv', get_stdout=True, cwd=p4a_dir 'git branch -vv', get_stdout=True, cwd=p4a_dir
)[0].split()[1] )[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( self.buildozer.info(
"Detected old fork/branch ({}/{}), deleting...".format( f"Detected old url/branch ({cur_url}/{cur_branch}), deleting..."
cur_fork, cur_branch
)
) )
rmtree(p4a_dir) rmtree(p4a_dir)
@ -736,11 +738,10 @@ class TargetAndroid(Target):
cmd( cmd(
( (
'git clone -b {p4a_branch} --single-branch ' 'git clone -b {p4a_branch} --single-branch '
'https://github.com/{p4a_fork}/python-for-android.git ' '{p4a_url} {p4a_dir}'
'{p4a_dir}'
).format( ).format(
p4a_branch=p4a_branch, p4a_branch=p4a_branch,
p4a_fork=p4a_fork, p4a_url=p4a_url,
p4a_dir=self.p4a_directory_name, p4a_dir=self.p4a_directory_name,
), ),
cwd=self.buildozer.platform_dir, cwd=self.buildozer.platform_dir,
@ -755,6 +756,8 @@ class TargetAndroid(Target):
cmd('git fetch --tags origin {0}:{0}'.format(p4a_branch), cmd('git fetch --tags origin {0}:{0}'.format(p4a_branch),
cwd=p4a_dir) cwd=p4a_dir)
cmd('git checkout {}'.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) # also install dependencies (currently, only setup.py knows about it)
# let's extract them. # let's extract them.
@ -766,7 +769,7 @@ class TargetAndroid(Target):
except IOError: except IOError:
self.buildozer.error('Failed to read python-for-android setup.py at {}'.format( self.buildozer.error('Failed to read python-for-android setup.py at {}'.format(
join(self.p4a_dir, 'setup.py'))) join(self.p4a_dir, 'setup.py')))
exit(1) sys.exit(1)
pip_deps = [] pip_deps = []
for dep in deps: for dep in deps:
pip_deps.append("'{}'".format(dep)) pip_deps.append("'{}'".format(dep))
@ -939,6 +942,11 @@ class TargetAndroid(Target):
cmd.append('--manifest-placeholders') cmd.append('--manifest-placeholders')
cmd.append("{}".format(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('--arch')
cmd.append(self._arch) cmd.append(self._arch)
@ -988,6 +996,12 @@ class TargetAndroid(Target):
cwd=self.buildozer.global_platform_dir) cwd=self.buildozer.global_platform_dir)
self.buildozer.environ.pop('ANDROID_SERIAL', None) 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.') self.buildozer.info('Application started.')
def cmd_p4a(self, *args): def cmd_p4a(self, *args):
@ -1160,6 +1174,11 @@ class TargetAndroid(Target):
icon = config.getdefault('app', 'icon.filename', '') icon = config.getdefault('app', 'icon.filename', '')
if icon: if icon:
build_cmd += [("--icon", join(self.buildozer.root_dir, 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 Console support
ouya_category = config.getdefault('app', 'android.ouya.category', ouya_category = config.getdefault('app', 'android.ouya.category',
@ -1193,6 +1212,13 @@ class TargetAndroid(Target):
if wakelock: if wakelock:
build_cmd += [("--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
intent_filters = config.getdefault( intent_filters = config.getdefault(
'app', 'android.manifest.intent_filters', '') 'app', 'android.manifest.intent_filters', '')
@ -1313,7 +1339,7 @@ class TargetAndroid(Target):
self.buildozer.error( self.buildozer.error(
'Invalid library reference (path not found): {}'.format( 'Invalid library reference (path not found): {}'.format(
cref)) cref))
exit(1) sys.exit(1)
# get a relative path from the project file # get a relative path from the project file
ref = relpath(ref, realpath(expanduser(dist_dir))) ref = relpath(ref, realpath(expanduser(dist_dir)))
# ensure the reference exists # ensure the reference exists
@ -1416,6 +1442,18 @@ class TargetAndroid(Target):
self.buildozer.info('Application pushed.') 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): def cmd_logcat(self, *args):
'''Show the log from the device '''Show the log from the device
''' '''
@ -1427,10 +1465,23 @@ class TargetAndroid(Target):
"app", "android.logcat_filters", "", section_sep=":", split_char=" ") "app", "android.logcat_filters", "", section_sep=":", split_char=" ")
filters = " ".join(filters) filters = " ".join(filters)
self.buildozer.environ['ANDROID_SERIAL'] = serial[0] self.buildozer.environ['ANDROID_SERIAL'] = serial[0]
self.buildozer.cmd('{adb} logcat {filters}'.format(adb=self.adb_cmd, extra_args = []
filters=filters), pid = None
cwd=self.buildozer.global_platform_dir, if self.buildozer.config.getdefault('app', 'android.logcat_pid_only'):
show_output=True) 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) self.buildozer.environ.pop('ANDROID_SERIAL', None)

View file

@ -115,8 +115,8 @@ class TargetIos(Target):
kwargs.setdefault('cwd', self.ios_dir) kwargs.setdefault('cwd', self.ios_dir)
return self.buildozer.cmd(self._toolchain_cmd + cmd, **kwargs) return self.buildozer.cmd(self._toolchain_cmd + cmd, **kwargs)
def xcodebuild(self, cmd='', **kwargs): def xcodebuild(self, *args, **kwargs):
return self.buildozer.cmd(self._xcodebuild_cmd + cmd, **kwargs) return self.buildozer.cmd(self._xcodebuild_cmd + ' '.join(arg for arg in args if arg is not None), **kwargs)
@property @property
def code_signing_allowed(self): def code_signing_allowed(self):
@ -124,6 +124,11 @@ class TargetIos(Target):
allowed = "YES" if allowed else "NO" allowed = "YES" if allowed else "NO"
return f"CODE_SIGNING_ALLOWED={allowed}" 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): def get_available_packages(self):
available_modules = self.toolchain("recipes --compact", get_stdout=True)[0] available_modules = self.toolchain("recipes --compact", get_stdout=True)[0]
return available_modules.splitlines()[0].split() return available_modules.splitlines()[0].split()
@ -202,7 +207,8 @@ class TargetIos(Target):
plist_rfn = join(self.app_project_dir, plist_fn) plist_rfn = join(self.app_project_dir, plist_fn)
version = self.buildozer.get_version() version = self.buildozer.get_version()
self.buildozer.info('Update Plist {}'.format(plist_fn)) 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['CFBundleIdentifier'] = self._get_package()
plist['CFBundleShortVersionString'] = version plist['CFBundleShortVersionString'] = version
plist['CFBundleVersion'] = '{}.{}'.format(version, plist['CFBundleVersion'] = '{}.{}'.format(version,
@ -211,12 +217,36 @@ class TargetIos(Target):
# add icons # add icons
self._create_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. # 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() mode = self.build_mode.capitalize()
self.xcodebuild( 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) cwd=self.app_project_dir)
ios_app_dir = '{app_lower}-ios/build/{mode}-iphoneos/{app_lower}.app'.format( ios_app_dir = '{app_lower}-ios/build/{mode}-iphoneos/{app_lower}.app'.format(
app_lower=app_name.lower(), mode=mode) app_lower=app_name.lower(), mode=mode)
@ -242,25 +272,24 @@ class TargetIos(Target):
self.buildozer.rmdir(intermediate_dir) self.buildozer.rmdir(intermediate_dir)
self.buildozer.info('Creating archive...') self.buildozer.info('Creating archive...')
self.xcodebuild(( self.xcodebuild(
' -alltargets' '-alltargets',
' -configuration {mode}' f'-configuration {mode}',
' -scheme {scheme}' f'-scheme {app_name.lower()}',
' -archivePath "{xcarchive}"' f'-archivePath "{xcarchive}"',
' archive' 'archive',
' ENABLE_BITCODE=NO' 'ENABLE_BITCODE=NO',
).format(mode=mode, xcarchive=xcarchive, scheme=app_name.lower()), self.code_signing_development_team,
cwd=build_dir) cwd=build_dir)
self.buildozer.info('Creating IPA...') self.buildozer.info('Creating IPA...')
self.xcodebuild(( self.xcodebuild(
' -exportArchive' '-exportArchive',
' -exportFormat IPA' f'-archivePath "{xcarchive}"',
' -archivePath "{xcarchive}"' f'-exportOptionsPlist "{plist_rfn}"',
' -exportPath "{ipa}"' f'-exportPath "{ipa_tmp}"',
' CODE_SIGN_IDENTITY={ioscodesign}' f'CODE_SIGN_IDENTITY={ioscodesign}',
' ENABLE_BITCODE=NO' 'ENABLE_BITCODE=NO',
).format(xcarchive=xcarchive, ipa=ipa_tmp, ioscodesign=ioscodesign),
cwd=build_dir) cwd=build_dir)
self.buildozer.info('Moving IPA to bin...') self.buildozer.info('Moving IPA to bin...')

View file

@ -46,16 +46,16 @@ class TargetOSX(Target):
self.buildozer.info('Downloading kivy...') self.buildozer.info('Downloading kivy...')
status_code = check_output( status_code = check_output(
('curl', '-L', '--write-out', '%{http_code}', '-o', 'Kivy{}.dmg'.format(py_branch), ('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)), .format(current_kivy_vers, current_kivy_vers, py_branch)),
cwd=cwd) cwd=cwd)
if status_code == "404": if status_code == "404":
self.buildozer.error( self.buildozer.error(
"Unable to download the Kivy App. Check osx.kivy_version in your buildozer.spec, and verify " "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) check_call(("rm", "Kivy{}.dmg".format(py_branch)), cwd=cwd)
exit(1) sys.exit(1)
self.buildozer.info('Extracting and installing Kivy...') self.buildozer.info('Extracting and installing Kivy...')
check_call(('hdiutil', 'attach', cwd + '/Kivy{}.dmg'.format(py_branch))) check_call(('hdiutil', 'attach', cwd + '/Kivy{}.dmg'.format(py_branch)))
@ -76,8 +76,6 @@ class TargetOSX(Target):
else: else:
self.download_kivy(kivy_app_dir, py_branch) self.download_kivy(kivy_app_dir, py_branch)
return
def check_requirements(self): def check_requirements(self):
self.ensure_sdk() self.ensure_sdk()
self.ensure_kivyapp() self.ensure_kivyapp()
@ -90,7 +88,7 @@ class TargetOSX(Target):
len(errors))) len(errors)))
for error in errors: for error in errors:
print(error) print(error)
exit(1) sys.exit(1)
# check # check
def build_package(self): def build_package(self):
@ -177,7 +175,7 @@ class TargetOSX(Target):
if not args: if not args:
self.buildozer.error('Missing target command') self.buildozer.error('Missing target command')
self.buildozer.usage() self.buildozer.usage()
exit(1) sys.exit(1)
result = [] result = []
last_command = [] last_command = []
@ -191,7 +189,7 @@ class TargetOSX(Target):
if not last_command: if not last_command:
self.buildozer.error('Argument passed without a command') self.buildozer.error('Argument passed without a command')
self.buildozer.usage() self.buildozer.usage()
exit(1) sys.exit(1)
last_command.append(arg) last_command.append(arg)
if last_command: if last_command:
result.append(last_command) result.append(last_command)
@ -202,7 +200,7 @@ class TargetOSX(Target):
command, args = item[0], item[1:] command, args = item[0], item[1:]
if not hasattr(self, 'cmd_{0}'.format(command)): if not hasattr(self, 'cmd_{0}'.format(command)):
self.buildozer.error('Unknown command {0}'.format(command)) self.buildozer.error('Unknown command {0}'.format(command))
exit(1) sys.exit(1)
func = getattr(self, 'cmd_{0}'.format(command)) func = getattr(self, 'cmd_{0}'.format(command))

View file

@ -25,7 +25,7 @@ torrent:
mktorrent \ mktorrent \
-a ${TORRENT_ANNOUNCE} \ -a ${TORRENT_ANNOUNCE} \
-o output-kivy-buildozer-vm/kivy-buildozer-vm.torrent \ -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} -v output-kivy-buildozer-vm/${PACKAGE_FILENAME}
upload: upload:

View file

@ -3,7 +3,7 @@
# an error when using the android sdk: # an error when using the android sdk:
# "Can't read cryptographic policy directory: unlimited" # "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 python get-pip.py
rm get-pip.py rm get-pip.py

View file

@ -9,7 +9,7 @@ BUILDDIR = build
# User-friendly check for sphinx-build # User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 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 endif
# Internal variables. # Internal variables.

View file

@ -56,7 +56,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH. echo.may add the Sphinx directory to PATH.
echo. echo.
echo.If you don't have Sphinx installed, grab it from 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 exit /b 1
) )

View file

@ -15,10 +15,10 @@ limitation.
To test your own recipe via Buildozer, you need to: To test your own recipe via Buildozer, you need to:
#. Fork `Python for Android <http://github.com/kivy/python-for-android>`_, and #. Fork `Python for Android <https://github.com/kivy/python-for-android>`_, and
clone your own version (this will allow easy contribution later):: 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:: #. 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 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 your new branch and a button "Pull Request" on it. Use it, write a
description about what you did, and Send! description about what you did, and Send!

View file

@ -1,3 +1,4 @@
Installation Installation
============ ============
@ -27,6 +28,48 @@ Android on Ubuntu 20.04 (64bit)
# add the following line at the end of your ~/.bashrc file # add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/ 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 Android on macOS
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

View file

@ -57,7 +57,7 @@ setup(
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
author='Mathieu Virbel', author='Mathieu Virbel',
author_email='mat@kivy.org', author_email='mat@kivy.org',
url='http://github.com/kivy/buildozer', url='https://github.com/kivy/buildozer',
license='MIT', license='MIT',
packages=[ packages=[
'buildozer', 'buildozer.targets', 'buildozer.libs', 'buildozer.scripts' 'buildozer', 'buildozer.targets', 'buildozer.libs', 'buildozer.scripts'

View file

@ -1,5 +1,6 @@
import os import os
import tempfile import tempfile
from six import StringIO
from unittest import mock from unittest import mock
import pytest 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

View file

@ -182,7 +182,6 @@ class TestTargetIos:
# fmt: off # fmt: off
with patch_target_ios("_unlock_keychain") as m_unlock_keychain, \ with patch_target_ios("_unlock_keychain") as m_unlock_keychain, \
patch_buildozer_error() as m_error, \ 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.readPlist") as m_readplist, \
mock.patch("buildozer.targets.ios.plistlib.writePlist") as m_writeplist, \ mock.patch("buildozer.targets.ios.plistlib.writePlist") as m_writeplist, \
patch_buildozer_cmd() as m_cmd: patch_buildozer_cmd() as m_cmd:
@ -196,13 +195,6 @@ class TestTargetIos:
'You must fill the "ios.codesign.debug" token.' '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 == [ assert m_readplist.call_args_list == [
mock.call("/ios/dir/myapp-ios/myapp-Info.plist") mock.call("/ios/dir/myapp-ios/myapp-Info.plist")
] ]
@ -216,4 +208,8 @@ class TestTargetIos:
"/ios/dir/myapp-ios/myapp-Info.plist", "/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",
)]

View file

@ -53,7 +53,7 @@ def init_buildozer(temp_dir, target, options=None):
spec = [] spec = []
for line in default_spec: for line in default_spec:
if line.strip(): 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) key = match and match.group(1)
if key in options: if key in options:
line = "{} = {}\n".format(key, options[key]) line = "{} = {}\n".format(key, options[key])

View file

@ -61,7 +61,7 @@ class TestBuildozer(unittest.TestCase):
def test_buildozer_base(self): 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() buildozer = Buildozer()
assert buildozer.specfilename == 'buildozer.spec' 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 m_file_extract.call_args_list == [mock.call(mock.ANY, cwd='/my/ant/path')]
assert ant_path == my_ant_path assert ant_path == my_ant_path
assert download.call_args_list == [ 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 # Mock ant already installed
with mock.patch.object(Buildozer, 'file_exists', return_value=True): with mock.patch.object(Buildozer, 'file_exists', return_value=True):
ant_path = target._install_apache_ant() ant_path = target._install_apache_ant()