From 310214b8e34e1b5ac26b7a749ac9ef57d42a9741 Mon Sep 17 00:00:00 2001 From: Julian-o Date: Sat, 8 Jul 2023 23:24:05 +1000 Subject: [PATCH] Factor out logging code from Buildozer class --- buildozer/__init__.py | 183 +++++++++++------------------------ buildozer/logger.py | 87 +++++++++++++++++ buildozer/target.py | 17 ++-- buildozer/targets/android.py | 121 +++++++++++------------ buildozer/targets/ios.py | 40 ++++---- buildozer/targets/osx.py | 71 +++++++------- tests/targets/test_ios.py | 4 +- tests/targets/utils.py | 4 +- tests/test_buildozer.py | 40 ++------ tests/test_logger.py | 53 ++++++++++ 10 files changed, 339 insertions(+), 281 deletions(-) create mode 100644 buildozer/logger.py create mode 100644 tests/test_logger.py diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 4f7e8fb44..d062bc20a 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -16,6 +16,7 @@ import textwrap import warnings from buildozer.jsonstore import JsonStore +from buildozer.logger import Logger from sys import stdout, stderr, exit from re import search from os.path import join, exists, dirname, realpath, splitext, expanduser @@ -25,7 +26,6 @@ from shutil import copyfile, rmtree, copytree, move, which from fnmatch import fnmatch -from pprint import pformat import shlex import pexpect @@ -36,41 +36,6 @@ except ImportError: # on windows, no fcntl fcntl = None -try: - # if installed, it can give color to windows as well - import colorama - colorama.init() - - RESET_SEQ = colorama.Fore.RESET + colorama.Style.RESET_ALL - COLOR_SEQ = lambda x: x # noqa: E731 - BOLD_SEQ = '' - if sys.platform == 'win32': - BLACK = colorama.Fore.BLACK + colorama.Style.DIM - else: - BLACK = colorama.Fore.BLACK + colorama.Style.BRIGHT - RED = colorama.Fore.RED - BLUE = colorama.Fore.CYAN - USE_COLOR = 'NO_COLOR' not in environ - -except ImportError: - if sys.platform != 'win32': - RESET_SEQ = "\033[0m" - COLOR_SEQ = lambda x: "\033[1;{}m".format(30 + x) # noqa: E731 - BOLD_SEQ = "\033[1m" - BLACK = 0 - RED = 1 - BLUE = 4 - USE_COLOR = 'NO_COLOR' not in environ - else: - RESET_SEQ = '' - COLOR_SEQ = '' - BOLD_SEQ = '' - RED = BLUE = BLACK = 0 - USE_COLOR = False - -# error, info, debug -LOG_LEVELS_C = (RED, BLUE, BLACK) -LOG_LEVELS_T = 'EID' SIMPLE_HTTP_SERVER_PORT = 8000 @@ -101,15 +66,10 @@ class BuildozerCommandException(BuildozerException): class Buildozer: - ERROR = 0 - INFO = 1 - DEBUG = 2 - standard_cmds = ('distclean', 'update', 'debug', 'release', 'deploy', 'run', 'serve') def __init__(self, filename='buildozer.spec', target=None): - self.log_level = 2 self.environ = {} self.specfilename = filename self.state = None @@ -123,6 +83,8 @@ def __init__(self, filename='buildozer.spec', target=None): self.config.getbooldefault = self._get_config_bool self.config.getrawdefault = self._get_config_raw_default + self.logger = Logger() + if exists(filename): self.config.read(filename, "utf-8") self.check_configuration_tokens() @@ -132,8 +94,9 @@ def __init__(self, filename='buildozer.spec', target=None): set_config_from_envs(self.config) try: - self.log_level = int(self.config.getdefault( - 'buildozer', 'log_level', '2')) + self.logger.set_level( + int(self.config.getdefault( + 'buildozer', 'log_level', '2'))) except Exception: pass @@ -163,20 +126,20 @@ def prepare_for_build(self): if hasattr(self.target, '_build_prepared'): return - self.info('Preparing build') + self.logger.info('Preparing build') - self.info('Check requirements for {0}'.format(self.targetname)) + self.logger.info('Check requirements for {0}'.format(self.targetname)) self.target.check_requirements() - self.info('Install platform') + self.logger.info('Install platform') self.target.install_platform() - self.info('Check application requirements') + self.logger.info('Check application requirements') self.check_application_requirements() self.check_garden_requirements() - self.info('Compile platform') + self.logger.info('Compile platform') self.target.compile_platform() # flag to prevent multiple build @@ -200,54 +163,26 @@ def build(self): self.build_id = int(self.state.get('cache.build_id', '0')) + 1 self.state['cache.build_id'] = str(self.build_id) - self.info('Build the application #{}'.format(self.build_id)) + self.logger.info('Build the application #{}'.format(self.build_id)) self.build_application() - self.info('Package the application') + self.logger.info('Package the application') self.target.build_package() # flag to prevent multiple build self.target._build_done = True - # - # Log functions - # - - def log(self, level, msg): - if level > self.log_level: - return - if USE_COLOR: - color = COLOR_SEQ(LOG_LEVELS_C[level]) - print(''.join((RESET_SEQ, color, '# ', msg, RESET_SEQ))) - else: - print('{} {}'.format(LOG_LEVELS_T[level], msg)) - - def debug(self, msg): - self.log(self.DEBUG, msg) - - def log_env(self, level, env): - """dump env into debug logger in readable format""" - self.log(level, "ENVIRONMENT:") - for k, v in env.items(): - self.log(level, " {} = {}".format(k, pformat(v))) - - def info(self, msg): - self.log(self.INFO, msg) - - def error(self, msg): - self.log(self.ERROR, msg) - # # Internal check methods # def checkbin(self, msg, fn): - self.debug('Search for {0}'.format(msg)) + self.logger.debug('Search for {0}'.format(msg)) executable_location = which(fn) if executable_location: - self.debug(' -> found at {0}'.format(executable_location)) + self.logger.debug(' -> found at {0}'.format(executable_location)) return realpath(executable_location) - self.error('{} not found, please install it.'.format(msg)) + self.logger.error('{} not found, please install it.'.format(msg)) exit(1) def cmd(self, command, **kwargs): @@ -260,7 +195,7 @@ def cmd(self, command, **kwargs): kwargs.setdefault('stdout', PIPE) kwargs.setdefault('stderr', PIPE) kwargs.setdefault('close_fds', True) - kwargs.setdefault('show_output', self.log_level > 1) + kwargs.setdefault('show_output', self.logger.log_level > 1) show_output = kwargs.pop('show_output') get_stdout = kwargs.pop('get_stdout', False) @@ -272,13 +207,13 @@ def cmd(self, command, **kwargs): if not quiet: if not sensible: - self.debug('Run {0!r}'.format(command)) + self.logger.debug('Run {0!r}'.format(command)) else: if isinstance(command, (list, tuple)): - self.debug('Run {0!r} ...'.format(command[0])) + self.logger.debug('Run {0!r} ...'.format(command[0])) else: - self.debug('Run {0!r} ...'.format(command.split()[0])) - self.debug('Cwd {}'.format(kwargs.get('cwd'))) + self.logger.debug('Run {0!r} ...'.format(command.split()[0])) + self.logger.debug('Cwd {}'.format(kwargs.get('cwd'))) # open the process if sys.platform == 'win32': @@ -331,18 +266,18 @@ def cmd(self, command, **kwargs): pass if process.returncode != 0 and break_on_error: - self.error('Command failed: {0}'.format(command)) - self.log_env(self.ERROR, kwargs['env']) - self.error('') - self.error('Buildozer failed to execute the last command') - if self.log_level <= self.INFO: - self.error('If the error is not obvious, please raise the log_level to 2') - self.error('and retry the latest command.') + self.logger.error('Command failed: {0}'.format(command)) + self.logger.log_env(self.logger.ERROR, kwargs['env']) + self.logger.error('') + self.logger.error('Buildozer failed to execute the last command') + if self.logger.log_level <= self.logger.INFO: + self.logger.error('If the error is not obvious, please raise the log_level to 2') + self.logger.error('and retry the latest command.') else: - self.error('The error might be hidden in the log above this error') - self.error('Please read the full log, and search for it before') - 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.logger.error('The error might be hidden in the log above this error') + self.logger.error('Please read the full log, and search for it before') + self.logger.error('raising an issue with buildozer itself.') + self.logger.error('In case of a bug report, please add a full log with log_level = 2') raise BuildozerCommandException() if ret_stdout: @@ -362,7 +297,7 @@ def cmd_expect(self, command, **kwargs): # prepare the process kwargs.setdefault('env', env) - kwargs.setdefault('show_output', self.log_level > 1) + kwargs.setdefault('show_output', self.logger.log_level > 1) sensible = kwargs.pop('sensible', False) show_output = kwargs.pop('show_output') @@ -370,17 +305,17 @@ def cmd_expect(self, command, **kwargs): kwargs['logfile'] = codecs.getwriter('utf8')(stdout.buffer) if not sensible: - self.debug('Run (expect) {0!r}'.format(command)) + self.logger.debug('Run (expect) {0!r}'.format(command)) else: - self.debug('Run (expect) {0!r} ...'.format(command.split()[0])) + self.logger.debug('Run (expect) {0!r} ...'.format(command.split()[0])) - self.debug('Cwd {}'.format(kwargs.get('cwd'))) + self.logger.debug('Cwd {}'.format(kwargs.get('cwd'))) return pexpect.spawnu(shlex.join(command), **kwargs) def check_configuration_tokens(self): '''Ensure the spec file is 'correct'. ''' - self.info('Check configuration tokens') + self.logger.info('Check configuration tokens') self.migrate_configuration_tokens() get = self.config.getdefault errors = [] @@ -412,7 +347,7 @@ def check_configuration_tokens(self): if o not in ("landscape", "portrait", "landscape-reverse", "portrait-reverse"): adderror(f'[app] "{o}" is not a valid value for "orientation"') if errors: - self.error('{0} error(s) found in the buildozer.spec'.format( + self.logger.error('{0} error(s) found in the buildozer.spec'.format( len(errors))) for error in errors: print(error) @@ -435,14 +370,14 @@ def migrate_configuration_tokens(self): value = config.get("app", entry_old) config.set("app", entry_new, value) config.remove_option("app", entry_old) - self.error("In section [app]: {} is deprecated, rename to {}!".format( + self.logger.error("In section [app]: {} is deprecated, rename to {}!".format( entry_old, entry_new)) def check_build_layout(self): '''Ensure the build (local and global) directory layout and files are ready. ''' - self.info('Ensure build layout') + self.logger.info('Ensure build layout') if not exists(self.specfilename): print('No {0} found in the current directory. Abandon.'.format( @@ -483,7 +418,7 @@ def check_application_requirements(self): target_available_packages] if requirements and hasattr(sys, 'real_prefix'): - e = self.error + e = self.logger.error e('virtualenv is needed to install pure-Python modules, but') e('virtualenv does not support nesting, and you are running') e('buildozer in one. Please run buildozer outside of a') @@ -495,7 +430,7 @@ def check_application_requirements(self): exists(self.applibs_dir) and self.state.get('cache.applibs', '') == requirements ): - self.debug('Application requirements already installed, pass') + self.logger.debug('Application requirements already installed, pass') return # recreate applibs @@ -511,7 +446,7 @@ def check_application_requirements(self): def _install_application_requirement(self, module): self._ensure_virtualenv() - self.debug('Install requirement {} in virtualenv'.format(module)) + self.logger.debug('Install requirement {} in virtualenv'.format(module)) self.cmd( ["pip", "install", f"--target={self.applibs_dir}", module], env=self.env_venv, @@ -556,13 +491,13 @@ def _ensure_virtualenv(self): def mkdir(self, dn): if exists(dn): return - self.debug('Create directory {0}'.format(dn)) + self.logger.debug('Create directory {0}'.format(dn)) makedirs(dn) def rmdir(self, dn): if not exists(dn): return - self.debug('Remove directory and subdirectory {}'.format(dn)) + self.logger.debug('Remove directory and subdirectory {}'.format(dn)) rmtree(dn) def file_matches(self, patterns): @@ -580,9 +515,9 @@ def file_rename(self, source, target, cwd=None): if cwd: source = join(cwd, source) target = join(cwd, target) - self.debug('Rename {0} to {1}'.format(source, target)) + self.logger.debug('Rename {0} to {1}'.format(source, target)) if not os.path.isdir(os.path.dirname(target)): - self.error(('Rename {0} to {1} fails because {2} is not a ' + self.logger.error(('Rename {0} to {1} fails because {2} is not a ' 'directory').format(source, target, target)) move(source, target) @@ -590,7 +525,7 @@ def file_copy(self, source, target, cwd=None): if cwd: source = join(cwd, source) target = join(cwd, target) - self.debug('Copy {0} to {1}'.format(source, target)) + self.logger.debug('Copy {0} to {1}'.format(source, target)) copyfile(source, target) def file_extract(self, archive, cwd=None): @@ -629,7 +564,7 @@ def file_copytree(self, src, dest): copyfile(src, dest) def clean_platform(self): - self.info('Clean the platform build directory') + self.logger.info('Clean the platform build directory') if not exists(self.platform_dir): return rmtree(self.platform_dir) @@ -651,7 +586,7 @@ def report_hook(index, blksize, size): if self.file_exists(filename): unlink(filename) - self.debug('Downloading {0}'.format(url)) + self.logger.debug('Downloading {0}'.format(url)) urlretrieve(url, filename, report_hook) return filename @@ -685,7 +620,7 @@ def get_version(self): 'Unable to find capture version in {0}\n' ' (looking for `{1}`)'.format(fn, regex)) version = match.groups()[0] - self.debug('Captured version: {0}'.format(version)) + self.logger.debug('Captured version: {0}'.format(version)) return version raise Exception('Missing version or version.regex + version.filename') @@ -713,7 +648,7 @@ def _copy_application_sources(self): exclude_patterns = [pat.lower() for pat in exclude_patterns] include_patterns = [pat.lower() for pat in include_patterns] - self.debug('Copy application source from {}'.format(source_dir)) + self.logger.debug('Copy application source from {}'.format(source_dir)) rmtree(self.app_dir) @@ -792,7 +727,7 @@ def _copy_application_sources(self): self.mkdir(dfn) # copy! - self.debug('Copy {0}'.format(sfn)) + self.logger.debug('Copy {0}'.format(sfn)) copyfile(sfn, rfn) def _copy_application_libs(self): @@ -815,7 +750,7 @@ def _add_sitecustomize(self): data = header + data with open(main_py, 'wb') as fd: fd.write(data) - self.info('Patched service/main.py to include applibs') + self.logger.info('Patched service/main.py to include applibs') def namify(self, name): '''Return a "valid" name from a name with lot of invalid chars @@ -979,7 +914,7 @@ def run_command(self, args): arg = args.pop(0) if arg in ('-v', '--verbose'): - self.log_level = 2 + self.logger.set_level(2) elif arg in ('-h', '--help'): self.usage() @@ -1053,7 +988,7 @@ def cmd_distclean(self, *args): print("Warning: Your ndk, sdk and all other cached packages will be" " removed. Continue? (y/n)") if sys.stdin.readline().lower()[0] == 'y': - self.info('Clean the global build directory') + self.logger.info('Clean the global build directory') if not exists(self.global_buildozer_dir): return rmtree(self.global_buildozer_dir) @@ -1066,14 +1001,14 @@ def cmd_appclean(self, *args): more than the user intends. ''' if self.user_build_dir is not None: - self.error( + self.logger.error( ('Failed: build_dir is specified as {} in the buildozer config. `appclean` will ' 'not attempt to delete files in a user-specified build directory.').format(self.user_build_dir)) elif exists(self.buildozer_dir): - self.info('Deleting {}'.format(self.buildozer_dir)) + self.logger.info('Deleting {}'.format(self.buildozer_dir)) rmtree(self.buildozer_dir) else: - self.error('{} already deleted, skipping.'.format(self.buildozer_dir)) + self.logger.error('{} already deleted, skipping.'.format(self.buildozer_dir)) def cmd_help(self, *args): '''Show the Buildozer help. diff --git a/buildozer/logger.py b/buildozer/logger.py new file mode 100644 index 000000000..6598f20a7 --- /dev/null +++ b/buildozer/logger.py @@ -0,0 +1,87 @@ +""" +Logger +====== + +Logger implementation used by Buildozer. + +Supports colored output, where available. +""" + +from os import environ +from pprint import pformat +import sys + +try: + # if installed, it can give color to Windows as well + import colorama + + colorama.init() +except ImportError: + colorama = None + +# set color codes +if colorama: + RESET_SEQ = colorama.Fore.RESET + colorama.Style.RESET_ALL + COLOR_SEQ = lambda x: x # noqa: E731 + BOLD_SEQ = "" + if sys.platform == "win32": + BLACK = colorama.Fore.BLACK + colorama.Style.DIM + else: + BLACK = colorama.Fore.BLACK + colorama.Style.BRIGHT + RED = colorama.Fore.RED + BLUE = colorama.Fore.CYAN + USE_COLOR = "NO_COLOR" not in environ +elif sys.platform != "win32": + RESET_SEQ = "\033[0m" + COLOR_SEQ = lambda x: "\033[1;{}m".format(30 + x) # noqa: E731 + BOLD_SEQ = "\033[1m" + BLACK = 0 + RED = 1 + BLUE = 4 + USE_COLOR = "NO_COLOR" not in environ +else: + RESET_SEQ = "" + COLOR_SEQ = "" + BOLD_SEQ = "" + RED = BLUE = BLACK = 0 + USE_COLOR = False + + +class Logger: + ERROR = 0 + INFO = 1 + DEBUG = 2 + + LOG_LEVELS_C = (RED, BLUE, BLACK) # Map levels to colors + LOG_LEVELS_T = "EID" # error, info, debug + + log_level = ERROR + + def log(self, level, msg): + if level > self.log_level: + return + if USE_COLOR: + color = COLOR_SEQ(Logger.LOG_LEVELS_C[level]) + print("".join((RESET_SEQ, color, "# ", msg, RESET_SEQ))) + else: + print("{} {}".format(Logger.LOG_LEVELS_T[level], msg)) + + def debug(self, msg): + self.log(self.DEBUG, msg) + + def info(self, msg): + self.log(self.INFO, msg) + + def error(self, msg): + self.log(self.ERROR, msg) + + def log_env(self, level, env): + """dump env into logger in readable format""" + self.log(level, "ENVIRONMENT:") + for k, v in env.items(): + self.log(level, " {} = {}".format(k, pformat(v))) + + @classmethod + def set_level(cls, level): + """set minimum threshold for log messages""" + cls.log_level = level diff --git a/buildozer/target.py b/buildozer/target.py index df09485b3..c7d6b6213 100644 --- a/buildozer/target.py +++ b/buildozer/target.py @@ -2,6 +2,8 @@ import os from os.path import join +from buildozer.logger import Logger + def no_config(f): f.__no_config = True @@ -14,16 +16,17 @@ def __init__(self, buildozer): self.build_mode = 'debug' self.artifact_format = 'apk' self.platform_update = False + self.logger = Logger() def check_requirements(self): pass def check_configuration_tokens(self, errors=None): if errors: - self.buildozer.info('Check target configuration tokens') - self.buildozer.error( + self.logger.info('Check target configuration tokens') + self.logger.error( '{0} error(s) found in the buildozer.spec'.format( - len(errors))) + len(errors))) for error in errors: print(error) exit(1) @@ -49,7 +52,7 @@ def get_available_packages(self): def run_commands(self, args): if not args: - self.buildozer.error('Missing target command') + self.logger.error('Missing target command') self.buildozer.usage() exit(1) @@ -68,7 +71,7 @@ def run_commands(self, args): last_command.append(arg) else: if not last_command: - self.buildozer.error('Argument passed without a command') + self.logger.error('Argument passed without a command') self.buildozer.usage() exit(1) last_command.append(arg) @@ -80,7 +83,7 @@ def run_commands(self, args): for item in result: command, args = item[0], item[1:] if not hasattr(self, 'cmd_{0}'.format(command)): - self.buildozer.error('Unknown command {0}'.format(command)) + self.logger.error('Unknown command {0}'.format(command)) exit(1) func = getattr(self, 'cmd_{0}'.format(command)) @@ -106,7 +109,7 @@ def cmd_debug(self, *args): self.buildozer.build() def cmd_release(self, *args): - error = self.buildozer.error + error = self.logger.error self.buildozer.prepare_for_build() if self.buildozer.config.get("app", "package.domain") == "org.test": error("") diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 7900055b9..752c0ba4d 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -25,7 +25,8 @@ import re import ast from sys import platform, executable -from buildozer import BuildozerException, USE_COLOR +from buildozer import BuildozerException +from buildozer.logger import USE_COLOR from buildozer.target import Target from os import environ from os.path import exists, join, realpath, expanduser, basename, relpath @@ -71,9 +72,9 @@ def __init__(self, *args, **kwargs): if self.buildozer.config.has_option( "app", "android.arch" ) and not self.buildozer.config.has_option("app", "android.archs"): - self.buildozer.error("`android.archs` not detected, instead `android.arch` is present.") - self.buildozer.error("`android.arch` will be removed and ignored in future.") - self.buildozer.error("If you're seeing this error, please migrate to `android.archs`.") + self.logger.error("`android.archs` not detected, instead `android.arch` is present.") + self.logger.error("`android.arch` will be removed and ignored in future.") + self.logger.error("If you're seeing this error, please migrate to `android.archs`.") self._archs = self.buildozer.config.getlist( 'app', 'android.arch', DEFAULT_ARCHS) else: @@ -112,7 +113,7 @@ def __init__(self, *args, **kwargs): if activity_class_name != 'org.kivy.android.PythonActivity': self.extra_p4a_args.append(f"--activity-class-name={activity_class_name}") - if self.buildozer.log_level >= 2: + if self.logger.log_level >= 2: self.extra_p4a_args.append("--debug") user_extra_p4a_args = self.buildozer.config.getdefault('app', 'p4a.extra_args', "") @@ -126,7 +127,7 @@ def warn_on_deprecated_tokens(self): if value is not None: error = ('WARNING: Config token {} {} is deprecated and ignored, ' 'but you set value {}').format(section, token, value) - self.buildozer.error(error) + self.logger.error(error) def _p4a(self, cmd, **kwargs): kwargs.setdefault('cwd', self.p4a_dir) @@ -163,7 +164,7 @@ def p4a_recommended_android_ndk(self): ndk_version = DEFAULT_ANDROID_NDK_VERSION rec_file = join(self.p4a_dir, "pythonforandroid", "recommendations.py") if not os.path.isfile(rec_file): - self.buildozer.error(MSG_P4A_RECOMMENDED_NDK_ERROR) + self.logger.error(MSG_P4A_RECOMMENDED_NDK_ERROR) return ndk_version for line in open(rec_file, "r"): @@ -173,7 +174,7 @@ def p4a_recommended_android_ndk(self): # clean version of unwanted characters for i in {"'", '"', "\n", " "}: ndk_version = ndk_version.replace(i, "") - self.buildozer.info( + self.logger.info( "Recommended android's NDK version by p4a is: {}".format( ndk_version ) @@ -336,13 +337,13 @@ def _locate_java(self, s): def _install_apache_ant(self): ant_dir = self.apache_ant_dir if self.buildozer.file_exists(ant_dir): - self.buildozer.info('Apache ANT found at {0}'.format(ant_dir)) + self.logger.info('Apache ANT found at {0}'.format(ant_dir)) return ant_dir if not os.path.exists(ant_dir): os.makedirs(ant_dir) - self.buildozer.info('Android ANT is missing, downloading') + self.logger.info('Android ANT is missing, downloading') archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION) url = 'https://archive.apache.org/dist/ant/binaries/' self.buildozer.download(url, @@ -350,16 +351,16 @@ def _install_apache_ant(self): cwd=ant_dir) self.buildozer.file_extract(archive, cwd=ant_dir) - self.buildozer.info('Apache ANT installation done.') + self.logger.info('Apache ANT installation done.') return ant_dir def _install_android_sdk(self): sdk_dir = self.android_sdk_dir if self.buildozer.file_exists(sdk_dir): - self.buildozer.info('Android SDK found at {0}'.format(sdk_dir)) + self.logger.info('Android SDK found at {0}'.format(sdk_dir)) return sdk_dir - self.buildozer.info('Android SDK is missing, downloading') + self.logger.info('Android SDK is missing, downloading') if platform in ('win32', 'cygwin'): archive = 'commandlinetools-win-{}_latest.zip'.format(DEFAULT_SDK_TAG) elif platform in ('darwin', ): @@ -377,24 +378,24 @@ def _install_android_sdk(self): archive, cwd=sdk_dir) - self.buildozer.info('Unpacking Android SDK') + self.logger.info('Unpacking Android SDK') self.buildozer.file_extract(archive, cwd=sdk_dir) - self.buildozer.info('Android SDK tools base installation done.') + self.logger.info('Android SDK tools base installation done.') return sdk_dir def _install_android_ndk(self): ndk_dir = self.android_ndk_dir if self.buildozer.file_exists(ndk_dir): - self.buildozer.info('Android NDK found at {0}'.format(ndk_dir)) + self.logger.info('Android NDK found at {0}'.format(ndk_dir)) return ndk_dir import re _version = int(re.search(r'(\d+)', self.android_ndk_version).group(1)) - self.buildozer.info('Android NDK is missing, downloading') + self.logger.info('Android NDK is missing, downloading') # Welcome to the NDK URL hell! # a list of all NDK URLs up to level 14 can be found here: # https://gist.github.com/roscopecoltran/43861414fbf341adac3b6fa05e7fad08 @@ -439,13 +440,13 @@ def _install_android_ndk(self): archive, cwd=self.buildozer.global_platform_dir) - self.buildozer.info('Unpacking Android NDK') + self.logger.info('Unpacking Android NDK') self.buildozer.file_extract(archive, cwd=self.buildozer.global_platform_dir) self.buildozer.file_rename(unpacked, ndk_dir, cwd=self.buildozer.global_platform_dir) - self.buildozer.info('Android NDK installation done.') + self.logger.info('Android NDK installation done.') return ndk_dir def _android_list_build_tools_versions(self): @@ -491,7 +492,7 @@ def _android_update_sdk(self, *sdkmanager_commands): def _read_version_subdir(self, *args): versions = [] if not os.path.exists(join(*args)): - self.buildozer.debug('build-tools folder not found {}'.format(join( + self.logger.debug('build-tools folder not found {}'.format(join( *args))) return parse("0") for v in os.listdir(join(*args)): @@ -500,7 +501,7 @@ def _read_version_subdir(self, *args): except: pass if not versions: - self.buildozer.error( + self.logger.error( 'Unable to find the latest version for {}'.format(join(*args))) return parse("0") return max(versions) @@ -535,23 +536,23 @@ def _install_android_packages(self): 'app', 'android.skip_update', False) if not skip_upd: - self.buildozer.info('Installing/updating SDK platform tools if necessary') + self.logger.info('Installing/updating SDK platform tools if necessary') # just calling sdkmanager with the items will install them if necessary self._android_update_sdk('platform-tools') self._android_update_sdk('--update') else: - self.buildozer.info('Skipping Android SDK update due to spec file setting') - self.buildozer.info('Note: this also prevents installing missing ' + self.logger.info('Skipping Android SDK update due to spec file setting') + self.logger.info('Note: this also prevents installing missing ' 'SDK components') # 2. install the latest build tool - self.buildozer.info('Updating SDK build tools if necessary') + self.logger.info('Updating SDK build tools if necessary') installed_v_build_tools = self._read_version_subdir(self.android_sdk_dir, 'build-tools') available_v_build_tools = self._android_list_build_tools_versions() if not available_v_build_tools: - self.buildozer.error('Did not find any build tools available to download') + self.logger.error('Did not find any build tools available to download') latest_v_build_tools = sorted(available_v_build_tools)[-1] if latest_v_build_tools > installed_v_build_tools: @@ -559,7 +560,7 @@ def _install_android_packages(self): self._android_update_sdk(f"build-tools;{latest_v_build_tools}") installed_v_build_tools = latest_v_build_tools else: - self.buildozer.info( + self.logger.info( 'Skipping update to build tools {} due to spec setting'.format( latest_v_build_tools)) @@ -567,23 +568,23 @@ def _install_android_packages(self): self._check_aidl(installed_v_build_tools) # 3. finally, install the android for the current api - self.buildozer.info('Downloading platform api target if necessary') + self.logger.info('Downloading platform api target if necessary') android_platform = join(self.android_sdk_dir, 'platforms', 'android-{}'.format(self.android_api)) if not self.buildozer.file_exists(android_platform): if not skip_upd: self._sdkmanager(f"platforms;android-{self.android_api}") else: - self.buildozer.info( + self.logger.info( 'Skipping install API {} platform tools due to spec setting'.format( self.android_api)) - self.buildozer.info('Android packages installation done.') + self.logger.info('Android packages installation done.') self.buildozer.state[cache_key] = cache_value self.buildozer.state.sync() def _check_aidl(self, v_build_tools): - self.buildozer.debug('Check that aidl can be executed') + self.logger.debug('Check that aidl can be executed') v_build_tools = self._read_version_subdir(self.android_sdk_dir, 'build-tools') aidl_cmd = join(self.android_sdk_dir, 'build-tools', @@ -593,19 +594,19 @@ def _check_aidl(self, v_build_tools): break_on_error=False, show_output=False) if returncode != 1: - self.buildozer.error('Aidl cannot be executed') + self.logger.error('Aidl cannot be executed') if architecture()[0] == '64bit': - self.buildozer.error('') - self.buildozer.error( + self.logger.error('') + self.logger.error( 'You might have missed to install 32bits libs') - self.buildozer.error( + self.logger.error( 'Check https://buildozer.readthedocs.org/en/latest/installation.html') - self.buildozer.error('') + self.logger.error('') else: - self.buildozer.error('') - self.buildozer.error( + self.logger.error('') + self.logger.error( 'In case of a bug report, please add a full log with log_level = 2') - self.buildozer.error('') + self.logger.error('') raise BuildozerException() def install_platform(self): @@ -619,7 +620,7 @@ def install_platform(self): # some of our configuration cannot be check without platform. self.check_configuration_tokens() if not self._p4a_have_aab_support(): - self.buildozer.error( + self.logger.error( "This buildozer version requires a python-for-android version with AAB (Android App Bundle) support. " "Please update your pinned version accordingly." ) @@ -654,9 +655,9 @@ def _install_p4a(self): if system_p4a_dir: # Don't install anything, just check that the dir does exist if not self.buildozer.file_exists(p4a_dir): - self.buildozer.error( + self.logger.error( 'Path for p4a.source_dir does not exist') - self.buildozer.error('') + self.logger.error('') raise BuildozerException() else: # check that url/branch has not been changed @@ -670,7 +671,7 @@ def _install_p4a(self): ["git", "branch", "-vv"], get_stdout=True, cwd=p4a_dir )[0].split()[1] if any([cur_url != p4a_url, cur_branch != p4a_branch]): - self.buildozer.info( + self.logger.info( f"Detected old url/branch ({cur_url}/{cur_branch}), deleting..." ) rmtree(p4a_dir) @@ -709,7 +710,7 @@ def _install_p4a(self): deps = re.findall(r"^\s*install_reqs = (\[[^\]]*\])", setup, re.DOTALL | re.MULTILINE)[0] deps = ast.literal_eval(deps) except IOError: - self.buildozer.error('Failed to read python-for-android setup.py at {}'.format( + self.logger.error('Failed to read python-for-android setup.py at {}'.format( join(self.p4a_dir, 'setup.py'))) sys.exit(1) @@ -734,7 +735,7 @@ def compile_platform(self): } if source_dirs: self.buildozer.environ.update(source_dirs) - self.buildozer.info('Using custom source dirs:\n {}'.format( + self.logger.info('Using custom source dirs:\n {}'.format( '\n '.join(['{} = {}'.format(k, v) for k, v in source_dirs.items()]))) @@ -934,7 +935,7 @@ def check_p4a_sign_env(self, error=False): key = "P4A_RELEASE_{}".format(key) if key not in os.environ: if error: - self.buildozer.error( + self.logger.error( ("Asking for release but {} is missing" "--sign will not be passed").format(key)) check = False @@ -956,7 +957,7 @@ def cmd_run(self, *args): # push on the device for serial in self.serials: self.buildozer.environ['ANDROID_SERIAL'] = serial - self.buildozer.info('Run on {}'.format(serial)) + self.logger.info('Run on {}'.format(serial)) self.buildozer.cmd( [ self.adb_executable, @@ -977,9 +978,9 @@ def cmd_run(self, *args): if self._get_pid(): break sleep(.1) - self.buildozer.info('Waiting for application to start.') + self.logger.info('Waiting for application to start.') - self.buildozer.info('Application started.') + self.logger.info('Application started.') def cmd_p4a(self, *args): ''' @@ -1042,7 +1043,7 @@ def build_package(self): if lib_dir not in self._archs: continue - self.buildozer.debug('Search and copy libs for {}'.format(lib_dir)) + self.logger.debug('Search and copy libs for {}'.format(lib_dir)) for fn in self.buildozer.file_matches(patterns): self.buildozer.file_copy( join(self.buildozer.root_dir, fn), @@ -1283,8 +1284,8 @@ def build_package(self): # copy to our place copyfile(join(artifact_dir, artifact), join(self.buildozer.bin_dir, artifact_dest)) - self.buildozer.info('Android packaging done!') - self.buildozer.info( + self.logger.info('Android packaging done!') + self.logger.info( u'APK {0} available in the bin directory'.format(artifact_dest)) self.buildozer.state['android:latestapk'] = artifact_dest self.buildozer.state['android:latestmode'] = self.build_mode @@ -1317,7 +1318,7 @@ def _update_libraries_references(self, dist_dir): # get the full path of the current reference ref = realpath(join(source_dir, cref)) if not self.buildozer.file_exists(ref): - self.buildozer.error( + self.logger.error( 'Invalid library reference (path not found): {}'.format( cref)) sys.exit(1) @@ -1338,7 +1339,7 @@ def _update_libraries_references(self, dist_dir): for index, ref in enumerate(references): fd.write(u'android.library.reference.{}={}\n'.format(index + 1, ref)) - self.buildozer.debug('project.properties updated') + self.logger.debug('project.properties updated') @property def serials(self): @@ -1381,30 +1382,30 @@ def cmd_deploy(self, *args): super().cmd_deploy(*args) state = self.buildozer.state if 'android:latestapk' not in state: - self.buildozer.error('No APK built yet. Run "debug" first.') + self.logger.error('No APK built yet. Run "debug" first.') if state.get('android:latestmode', '') != 'debug': - self.buildozer.error('Only debug APK are supported for deploy') + self.logger.error('Only debug APK are supported for deploy') return # search the APK in the bin dir apk = state['android:latestapk'] full_apk = join(self.buildozer.bin_dir, apk) if not self.buildozer.file_exists(full_apk): - self.buildozer.error( + self.logger.error( 'Unable to found the latest APK. Please run "debug" again.') # push on the device for serial in self.serials: self.buildozer.environ['ANDROID_SERIAL'] = serial - self.buildozer.info('Deploy on {}'.format(serial)) + self.logger.info('Deploy on {}'.format(serial)) self.buildozer.cmd( [self.adb_executable, *self.adb_args, "install", "-r", full_apk], cwd=self.buildozer.global_platform_dir, ) self.buildozer.environ.pop('ANDROID_SERIAL', None) - self.buildozer.info('Application pushed.') + self.logger.info('Application pushed.') def _get_pid(self): pid, *_ = self.buildozer.cmd( @@ -1450,7 +1451,7 @@ def cmd_logcat(self, *args): break_on_error=False, ) - self.buildozer.info(f"{self._get_package()} terminated") + self.logger.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 245f0ccc0..3962e6a37 100644 --- a/buildozer/targets/ios.py +++ b/buildozer/targets/ios.py @@ -84,7 +84,7 @@ def check_requirements(self): checkbin('automake', 'automake') checkbin('libtool', 'libtool') - self.buildozer.debug('Check availability of a iPhone SDK') + self.logger.debug('Check availability of a iPhone SDK') sdk = cmd('xcodebuild -showsdks | fgrep "iphoneos" |' 'tail -n 1 | awk \'{print $2}\'', get_stdout=True, shell=True)[0] @@ -92,13 +92,13 @@ def check_requirements(self): raise Exception( 'No iPhone SDK found. Please install at least one iOS SDK.') else: - self.buildozer.debug(' -> found %r' % sdk) + self.logger.debug(' -> found %r' % sdk) - self.buildozer.debug('Check Xcode path') + self.logger.debug('Check Xcode path') xcode = cmd(["xcode-select", "-print-path"], get_stdout=True)[0] if not xcode: raise Exception('Unable to get xcode path') - self.buildozer.debug(' -> found {0}'.format(xcode)) + self.logger.debug(' -> found {0}'.format(xcode)) def install_platform(self): """ @@ -167,12 +167,12 @@ def compile_platform(self): if source_dirs: need_compile = 1 self.buildozer.environ.update(source_dirs) - self.buildozer.info('Using custom source dirs:\n {}'.format( + self.logger.info('Using custom source dirs:\n {}'.format( '\n '.join(['{} = {}'.format(k, v) for k, v in source_dirs.items()]))) if not need_compile: - self.buildozer.info('Distribution already compiled, pass.') + self.logger.info('Distribution already compiled, pass.') return self.toolchain(["build", *ios_requirements]) @@ -214,7 +214,7 @@ def build_package(self): plist_fn = '{}-Info.plist'.format(app_name.lower()) plist_rfn = join(self.app_project_dir, plist_fn) version = self.buildozer.get_version() - self.buildozer.info('Update Plist {}'.format(plist_fn)) + self.logger.info('Update Plist {}'.format(plist_fn)) plist = self.load_plist_from_file(plist_rfn) plist['CFBundleIdentifier'] = self._get_package() plist['CFBundleShortVersionString'] = version @@ -232,7 +232,7 @@ def build_package(self): 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" + self.logger.error("Options ios.manifest.app_url, ios.manifest.display_image_url" " and ios.manifest.full_size_image_url should be defined all together") return @@ -270,7 +270,7 @@ def build_package(self): self.buildozer.rmdir(intermediate_dir) - self.buildozer.info('Creating archive...') + self.logger.info('Creating archive...') self.xcodebuild( '-alltargets', '-configuration', @@ -290,13 +290,13 @@ def build_package(self): key = 'ios.codesign.{}'.format(self.build_mode) ioscodesign = self.buildozer.config.getdefault('app', key, '') if not ioscodesign: - self.buildozer.error('Cannot create the IPA package without' + self.logger.error('Cannot create the IPA package without' ' signature. You must fill the "{}" token.'.format(key)) return elif ioscodesign[0] not in ('"', "'"): ioscodesign = '"{}"'.format(ioscodesign) - self.buildozer.info('Creating IPA...') + self.logger.info('Creating IPA...') self.xcodebuild( '-exportArchive', f'-archivePath "{xcarchive}"', @@ -306,11 +306,11 @@ def build_package(self): 'ENABLE_BITCODE=NO', cwd=build_dir) - self.buildozer.info('Moving IPA to bin...') + self.logger.info('Moving IPA to bin...') self.buildozer.file_rename(ipa_tmp, ipa) - self.buildozer.info('iOS packaging done!') - self.buildozer.info('IPA {0} available in the bin directory'.format( + self.logger.info('iOS packaging done!') + self.logger.info('IPA {0} available in the bin directory'.format( basename(ipa))) self.buildozer.state['ios:latestipa'] = ipa self.buildozer.state['ios:latestmode'] = self.build_mode @@ -338,17 +338,17 @@ def cmd_xcode(self, *args): def _run_ios_deploy(self, lldb=False): state = self.buildozer.state if 'ios:latestappdir' not in state: - self.buildozer.error( + self.logger.error( 'App not built yet. Run "debug" or "release" first.') return ios_app_dir = state.get('ios:latestappdir') if lldb: debug_mode = '-d' - self.buildozer.info('Deploy and start the application') + self.logger.info('Deploy and start the application') else: debug_mode = '' - self.buildozer.info('Deploy the application') + self.logger.info('Deploy the application') self.buildozer.cmd( [join(self.ios_deploy_dir, "ios-deploy"), debug_mode, "-b", ios_app_dir], @@ -362,7 +362,7 @@ def _create_icons(self): return icon_fn = join(self.buildozer.app_dir, icon) if not self.buildozer.file_exists(icon_fn): - self.buildozer.error('Icon {} does not exists'.format(icon_fn)) + self.logger.error('Icon {} does not exists'.format(icon_fn)) return self.toolchain(["icon", self.app_project_dir, icon_fn]) @@ -447,10 +447,10 @@ def _unlock_keychain(self): if not error: correct = True break - self.buildozer.error('Invalid keychain password') + self.logger.error('Invalid keychain password') if not correct: - self.buildozer.error('Unable to unlock the keychain, exiting.') + self.logger.error('Unable to unlock the keychain, exiting.') raise BuildozerCommandException() # maybe user want to save it for further reuse? diff --git a/buildozer/targets/osx.py b/buildozer/targets/osx.py index 175f329ae..db743b4b0 100644 --- a/buildozer/targets/osx.py +++ b/buildozer/targets/osx.py @@ -1,17 +1,19 @@ -''' +""" OSX target, based on kivy-sdk-packager -''' +""" import sys if sys.platform != 'darwin': raise NotImplementedError('This will only work on osx') -from buildozer.target import Target from os.path import exists, join, abspath, dirname from subprocess import check_call, check_output +from buildozer.target import Target + class TargetOSX(Target): + targetname = "osx" def ensure_sdk(self): @@ -20,14 +22,14 @@ def ensure_sdk(self): join(self.buildozer.platform_dir, 'kivy-sdk-packager-master')): self.buildozer.info( 'kivy-sdk-packager found at ' - '{}'.format(self.buildozer.platform_dir)) + '{}'.format(self.buildozer.platform_dir)) return self.buildozer.info('kivy-sdk-packager does not exist, clone it') platdir = self.buildozer.platform_dir check_call( ('curl', '-O', '-L', - 'https://github.com/kivy/kivy-sdk-packager/archive/master.zip'), + 'https://github.com/kivy/kivy-sdk-packager/archive/master.zip'), cwd=platdir) check_call(('unzip', 'master.zip'), cwd=platdir) check_call(('rm', 'master.zip'), cwd=platdir) @@ -39,33 +41,34 @@ def download_kivy(self, cwd): self.buildozer.info('Kivy found in Applications dir...') check_call( ('cp', '-a', '/Applications/Kivy.app', - 'Kivy.app'), cwd=cwd) + 'Kivy.app'), cwd=cwd) else: if not exists(join(cwd, 'Kivy.dmg')): self.buildozer.info('Downloading kivy...') - status_code = check_output( - ('curl', '-L', '--write-out', '%{http_code}', '-o', 'Kivy.dmg', + status_code = check_output(( + 'curl', '-L', '--write-out', '%{http_code}', + '-o', 'Kivy.dmg', f'https://kivy.org/downloads/{current_kivy_vers}/Kivy.dmg'), cwd=cwd) if status_code == "404": - self.buildozer.error( + self.logger.error( "Unable to download the Kivy App. Check osx.kivy_version in your buildozer.spec, and verify " "Kivy servers are accessible. https://kivy.org/downloads/") check_call(("rm", "Kivy.dmg"), cwd=cwd) sys.exit(1) - self.buildozer.info('Extracting and installing Kivy...') + self.logger.info('Extracting and installing Kivy...') check_call(('hdiutil', 'attach', cwd + '/Kivy.dmg')) check_call(('cp', '-a', '/Volumes/Kivy/Kivy.app', './Kivy.app'), cwd=cwd) def ensure_kivyapp(self): - self.buildozer.info('check if Kivy.app exists in local dir') + self.logger.info('check if Kivy.app exists in local dir') kivy_app_dir = join(self.buildozer.platform_dir, 'kivy-sdk-packager-master', 'osx') if exists(join(kivy_app_dir, 'Kivy.app')): - self.buildozer.info('Kivy.app found at ' + kivy_app_dir) + self.logger.info('Kivy.app found at ' + kivy_app_dir) else: self.download_kivy(kivy_app_dir) @@ -75,17 +78,17 @@ def check_requirements(self): def check_configuration_tokens(self, errors=None): if errors: - self.buildozer.info('Check target configuration tokens') - self.buildozer.error( + self.logger.info('Check target configuration tokens') + self.logger.error( '{0} error(s) found in the buildozer.spec'.format( - len(errors))) + len(errors))) for error in errors: print(error) sys.exit(1) # check def build_package(self): - self.buildozer.info('Building package') + self.logger.info('Building package') bc = self.buildozer.config bcg = bc.get @@ -97,26 +100,28 @@ def build_package(self): version = self.buildozer.get_version() author = bc.getdefault('app', 'author', '') - self.buildozer.info('Create {}.app'.format(package_name)) + self.logger.info('Create {}.app'.format(package_name)) cwd = join(self.buildozer.platform_dir, 'kivy-sdk-packager-master', 'osx') # remove kivy from app_deps app_deps = [a for a in app_deps.split('\n') if not a.startswith('#') and a not in ['kivy', '']] cmd = [ 'Kivy.app/Contents/Resources/script', - '-m', 'pip', 'install', + '-m', 'pip', 'install', ] cmd.extend(app_deps) check_output(cmd, cwd=cwd) cmd = [ - sys.executable, 'package_app.py', self.buildozer.app_dir, + sys.executable, + 'package_app.py', + self.buildozer.app_dir, '--appname={}'.format(package_name), - '--bundlename={}'.format(title), - '--bundleid={}'.format(domain), - '--bundleversion={}'.format(version), - '--displayname={}'.format(title) - ] + '--bundlename={}'.format(title), + '--bundleid={}'.format(domain), + '--bundleversion={}'.format(version), + '--displayname={}'.format(title) + ] if icon: cmd.append('--icon={}'.format(icon)) if author: @@ -124,27 +129,27 @@ def build_package(self): check_output(cmd, cwd=cwd) - self.buildozer.info('{}.app created.'.format(package_name)) - self.buildozer.info('Creating {}.dmg'.format(package_name)) + self.logger.info('{}.app created.'.format(package_name)) + self.logger.info('Creating {}.dmg'.format(package_name)) check_output( ('sh', '-x', 'create-osx-dmg.sh', package_name + '.app', package_name, '-s', '1'), cwd=cwd) - self.buildozer.info('{}.dmg created'.format(package_name)) - self.buildozer.info('moving {}.dmg to bin.'.format(package_name)) + self.logger.info('{}.dmg created'.format(package_name)) + self.logger.info('moving {}.dmg to bin.'.format(package_name)) binpath = join( self.buildozer.user_build_dir or dirname(abspath(self.buildozer.specfilename)), 'bin') check_output( ('cp', '-a', package_name + '.dmg', binpath), cwd=cwd) - self.buildozer.info('All Done!') + self.logger.info('All Done!') def compile_platform(self): pass def install_platform(self): # ultimate configuration check. - # some of our configuration cannot be check without platform. + # some of our configuration cannot be checked without platform. self.check_configuration_tokens() # self.buildozer.environ.update({ @@ -166,7 +171,7 @@ def get_available_packages(self): def run_commands(self, args): if not args: - self.buildozer.error('Missing target command') + self.logger.error('Missing target command') self.buildozer.usage() sys.exit(1) @@ -180,7 +185,7 @@ def run_commands(self, args): last_command.append(arg) else: if not last_command: - self.buildozer.error('Argument passed without a command') + self.logger.error('Argument passed without a command') self.buildozer.usage() sys.exit(1) last_command.append(arg) @@ -192,7 +197,7 @@ def run_commands(self, args): for item in result: command, args = item[0], item[1:] if not hasattr(self, 'cmd_{0}'.format(command)): - self.buildozer.error('Unknown command {0}'.format(command)) + self.logger.error('Unknown command {0}'.format(command)) sys.exit(1) func = getattr(self, 'cmd_{0}'.format(command)) diff --git a/tests/targets/test_ios.py b/tests/targets/test_ios.py index 3118c9a09..ca8023783 100644 --- a/tests/targets/test_ios.py +++ b/tests/targets/test_ios.py @@ -10,8 +10,8 @@ init_buildozer, patch_buildozer_checkbin, patch_buildozer_cmd, - patch_buildozer_error, patch_buildozer_file_exists, + patch_logger_error, ) @@ -195,7 +195,7 @@ def test_build_package_no_signature(self): target.ios_dir = "/ios/dir" # fmt: off with patch_target_ios("_unlock_keychain") as m_unlock_keychain, \ - patch_buildozer_error() as m_error, \ + patch_logger_error() as m_error, \ mock.patch("buildozer.targets.ios.TargetIos.load_plist_from_file") as m_load_plist_from_file, \ mock.patch("buildozer.targets.ios.TargetIos.dump_plist_to_file") as m_dump_plist_to_file, \ patch_buildozer_cmd() as m_cmd: diff --git a/tests/targets/utils.py b/tests/targets/utils.py index 0c78712c1..6b4ceb4f8 100644 --- a/tests/targets/utils.py +++ b/tests/targets/utils.py @@ -22,8 +22,8 @@ def patch_buildozer_file_exists(): return patch_buildozer("file_exists") -def patch_buildozer_error(): - return patch_buildozer("error") +def patch_logger_error(): + return mock.patch("buildozer.logger.Logger.error") def default_specfile_path(): diff --git a/tests/test_buildozer.py b/tests/test_buildozer.py index 46f7552a0..7560820db 100644 --- a/tests/test_buildozer.py +++ b/tests/test_buildozer.py @@ -5,6 +5,7 @@ import buildozer as buildozer_module from buildozer import Buildozer from io import StringIO +from sys import platform import tempfile from unittest import mock @@ -57,7 +58,7 @@ def set_specfile_log_level(cls, specfilename, log_level): replace = 'log_level = {}'.format(log_level) cls.file_re_sub(specfilename, pattern, replace) buildozer = Buildozer(specfilename) - assert buildozer.log_level == log_level + assert buildozer.logger.log_level == log_level def test_buildozer_base(self): """ @@ -91,41 +92,11 @@ def test_log_get_set(self): """ # the default log level value is known buildozer = Buildozer('does_not_exist.spec') - assert buildozer.log_level == 2 + assert buildozer.logger.log_level == 2 # sets log level to 1 on the spec file self.set_specfile_log_level(self.specfile.name, 1) buildozer = Buildozer(self.specfile.name) - assert buildozer.log_level == 1 - - def test_log_print(self): - """ - Checks logger prints different info depending on log level. - """ - # sets log level to 1 in the spec file - self.set_specfile_log_level(self.specfile.name, 1) - buildozer = Buildozer(self.specfile.name) - assert buildozer.log_level == 1 - # at this level, debug messages shouldn't not be printed - with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout: - buildozer.debug('debug message') - buildozer.info('info message') - buildozer.error('error message') - # using `in` keyword rather than `==` because of bash color prefix/suffix - assert 'debug message' not in mock_stdout.getvalue() - assert 'info message' in mock_stdout.getvalue() - assert 'error message' in mock_stdout.getvalue() - # sets log level to 2 in the spec file - self.set_specfile_log_level(self.specfile.name, 2) - buildozer = Buildozer(self.specfile.name) - assert buildozer.log_level == 2 - # at this level all message types should be printed - with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout: - buildozer.debug('debug message') - buildozer.info('info message') - buildozer.error('error message') - assert 'debug message' in mock_stdout.getvalue() - assert 'info message' in mock_stdout.getvalue() - assert 'error message' in mock_stdout.getvalue() + assert buildozer.logger.log_level == 1 def test_run_command_unknown(self): """ @@ -140,6 +111,9 @@ def test_run_command_unknown(self): buildozer.run_command(args) assert mock_stdout.getvalue() == 'Unknown command/target {}\n'.format(command) + @unittest.skipIf( + platform == "win32", + "Test can't handle when resulting path is normalised on Windows") def test_android_ant_path(self): """ Verify that the selected ANT path is being used from the spec file diff --git a/tests/test_logger.py b/tests/test_logger.py new file mode 100644 index 000000000..b913f2e2f --- /dev/null +++ b/tests/test_logger.py @@ -0,0 +1,53 @@ +import unittest +from buildozer.logger import Logger + +from io import StringIO +from unittest import mock + + +class TestLogger(unittest.TestCase): + def test_log_print(self): + """ + Checks logger prints different info depending on log level. + """ + logger = Logger() + + # Test ERROR Level + Logger.set_level(0) + assert logger.log_level == logger.ERROR + + # at this level, only error messages should be printed + with mock.patch("sys.stdout", new_callable=StringIO) as mock_stdout: + logger.debug("debug message") + logger.info("info message") + logger.error("error message") + # using `in` keyword rather than `==` because of color prefix/suffix + assert "debug message" not in mock_stdout.getvalue() + assert "info message" not in mock_stdout.getvalue() + assert "error message" in mock_stdout.getvalue() + + # Test INFO Level + Logger.set_level(1) + assert logger.log_level == logger.INFO + + # at this level, debug messages should not be printed + with mock.patch("sys.stdout", new_callable=StringIO) as mock_stdout: + logger.debug("debug message") + logger.info("info message") + logger.error("error message") + # using `in` keyword rather than `==` because of color prefix/suffix + assert "debug message" not in mock_stdout.getvalue() + assert "info message" in mock_stdout.getvalue() + assert "error message" in mock_stdout.getvalue() + + # sets log level to 2 in the spec file + Logger.set_level(2) + assert logger.log_level == logger.DEBUG + # at this level all message types should be printed + with mock.patch("sys.stdout", new_callable=StringIO) as mock_stdout: + logger.debug("debug message") + logger.info("info message") + logger.error("error message") + assert "debug message" in mock_stdout.getvalue() + assert "info message" in mock_stdout.getvalue() + assert "error message" in mock_stdout.getvalue()