From 3af40d18052797ce562654817d2e8dca80e700ff Mon Sep 17 00:00:00 2001 From: Russell Martin Date: Fri, 29 Mar 2024 19:20:20 -0400 Subject: [PATCH] [POC] Implement persistent build tracking for more intuitive behavior --- src/briefcase/__main__.py | 77 +++++----- src/briefcase/commands/base.py | 140 +++++++++++++++--- src/briefcase/commands/build.py | 37 ++--- src/briefcase/commands/create.py | 22 ++- src/briefcase/commands/dev.py | 47 +++--- src/briefcase/commands/package.py | 33 ++--- src/briefcase/commands/run.py | 15 +- src/briefcase/commands/update.py | 44 ++++-- src/briefcase/config.py | 44 ++++-- src/briefcase/integrations/docker.py | 2 +- tests/commands/base/test_app_module_path.py | 16 +- .../commands/create/test_install_app_code.py | 36 ++--- .../create/test_install_app_requirements.py | 64 ++++---- .../dev/test_install_dev_requirements.py | 10 +- tests/config/test_AppConfig.py | 4 +- tests/platforms/iOS/xcode/test_create.py | 2 +- tests/platforms/iOS/xcode/test_update.py | 2 +- .../linux/test_LocalRequirementsMixin.py | 8 +- tests/platforms/macOS/app/test_create.py | 8 +- 19 files changed, 368 insertions(+), 243 deletions(-) diff --git a/src/briefcase/__main__.py b/src/briefcase/__main__.py index 31033945c..a819be5f2 100644 --- a/src/briefcase/__main__.py +++ b/src/briefcase/__main__.py @@ -15,48 +15,49 @@ def main(): result = 0 command = None + printer = Printer() console = Console(printer=printer) logger = Log(printer=printer) - try: - Command, extra_cmdline = parse_cmdline(sys.argv[1:]) - command = Command(logger=logger, console=console) - options, overrides = command.parse_options(extra=extra_cmdline) - command.parse_config( - Path.cwd() / "pyproject.toml", - overrides=overrides, - ) - command(**options) - except HelpText as e: - logger.info() - logger.info(str(e)) - result = e.error_code - except BriefcaseWarning as w: - # The case of something that hasn't gone right, but in an - # acceptable way. - logger.warning(str(w)) - result = w.error_code - except BriefcaseTestSuiteFailure as e: - # Test suite status is logged when the test is executed. - # Set the return code, but don't log anything else. - result = e.error_code - except BriefcaseError as e: - logger.error() - logger.error(str(e)) - result = e.error_code - logger.capture_stacktrace() - except Exception: - logger.capture_stacktrace() - raise - except KeyboardInterrupt: - logger.warning() - logger.warning("Aborted by user.") - logger.warning() - result = -42 - if logger.save_log: + + with suppress(KeyboardInterrupt): + try: + Command, extra_cmdline = parse_cmdline(sys.argv[1:]) + command = Command(logger=logger, console=console) + options, overrides = command.parse_options(extra=extra_cmdline) + command.parse_config(Path.cwd() / "pyproject.toml", overrides=overrides) + command(**options) + except HelpText as e: + logger.info() + logger.info(str(e)) + result = e.error_code + except BriefcaseWarning as w: + # The case of something that hasn't gone right, but in an + # acceptable way. + logger.warning(str(w)) + result = w.error_code + except BriefcaseTestSuiteFailure as e: + # Test suite status is logged when the test is executed. + # Set the return code, but don't log anything else. + result = e.error_code + except BriefcaseError as e: + logger.error() + logger.error(str(e)) + result = e.error_code + logger.capture_stacktrace() + except Exception: logger.capture_stacktrace() - finally: - with suppress(KeyboardInterrupt): + raise + except KeyboardInterrupt: + logger.warning() + logger.warning("Aborted by user.") + logger.warning() + result = -42 + if logger.save_log: + logger.capture_stacktrace() + finally: + if command is not None: + command.tracking_save() logger.save_log_to_file(command) return result diff --git a/src/briefcase/commands/base.py b/src/briefcase/commands/base.py index aa088ec67..e49d1f1c3 100644 --- a/src/briefcase/commands/base.py +++ b/src/briefcase/commands/base.py @@ -12,9 +12,11 @@ import textwrap from abc import ABC, abstractmethod from argparse import RawDescriptionHelpFormatter +from collections.abc import Iterable from pathlib import Path from typing import Any +import tomli_w from cookiecutter import exceptions as cookiecutter_exceptions from cookiecutter.repository import is_repo_url from platformdirs import PlatformDirs @@ -153,11 +155,12 @@ def __init__( self, logger: Log, console: Console, - tools: ToolCache = None, - apps: dict = None, - base_path: Path = None, - data_path: Path = None, + tools: ToolCache | None = None, + apps: dict[str, AppConfig] | None = None, + base_path: Path | None = None, + data_path: Path | None = None, is_clone: bool = False, + tracking: dict[AppConfig, dict[str, ...]] = None, ): """Base for all Commands. @@ -171,10 +174,7 @@ def __init__( Command; for instance, RunCommand can invoke UpdateCommand and/or BuildCommand. """ - if base_path is None: - self.base_path = Path.cwd() - else: - self.base_path = base_path + self.base_path = Path.cwd() if base_path is None else base_path self.data_path = self.validate_data_path(data_path) self.apps = {} if apps is None else apps self.is_clone = is_clone @@ -194,6 +194,9 @@ def __init__( self.global_config = None self._briefcase_toml: dict[AppConfig, dict[str, ...]] = {} + self._tracking: dict[AppConfig, dict[str, ...]] = ( + {} if tracking is None else tracking + ) @property def logger(self): @@ -319,6 +322,7 @@ def _command_factory(self, command_name: str): console=self.input, tools=self.tools, is_clone=True, + tracking=self._tracking, ) command.clone_options(self) return command @@ -389,6 +393,9 @@ def binary_path(self, app) -> Path: :param app: The app config """ + def briefcase_toml_path(self, app: AppConfig) -> Path: + return self.bundle_path(app) / "briefcase.toml" + def briefcase_toml(self, app: AppConfig) -> dict[str, ...]: """Load the ``briefcase.toml`` file provided by the app template. @@ -399,11 +406,11 @@ def briefcase_toml(self, app: AppConfig) -> dict[str, ...]: return self._briefcase_toml[app] except KeyError: try: - with (self.bundle_path(app) / "briefcase.toml").open("rb") as f: - self._briefcase_toml[app] = tomllib.load(f) + toml = self.briefcase_toml_path(app).read_text(encoding="utf-8") except OSError as e: raise MissingAppMetadata(self.bundle_path(app)) from e else: + self._briefcase_toml[app] = tomllib.loads(toml) return self._briefcase_toml[app] def path_index(self, app: AppConfig, path_name: str) -> str | dict | list: @@ -487,11 +494,11 @@ def app_module_path(self, app: AppConfig) -> Path: """Find the path for the application module for an app. :param app: The config object for the app - :returns: The Path to the dist-info folder. + :returns: The Path to the app module """ app_home = [ path.split("/") - for path in app.sources + for path in app.sources() if path.rsplit("/", 1)[-1] == app.module_name ] @@ -500,7 +507,7 @@ def app_module_path(self, app: AppConfig) -> Path: f"Unable to find code for application {app.app_name!r}" ) elif len(app_home) == 1: - path = Path(str(self.base_path), *app_home[0]) + path = Path(self.base_path, *app_home[0]) else: raise BriefcaseCommandError( f"Multiple paths in sources found for application {app.app_name!r}" @@ -508,6 +515,106 @@ def app_module_path(self, app: AppConfig) -> Path: return path + def dist_info_path(self, app: AppConfig) -> Path: + """Path to dist-info for the app in the output format build.""" + return self.app_path(app) / f"{app.module_name}-{app.version}.dist-info" + + def tracking_path(self, app: AppConfig) -> Path: + return self.bundle_path(app) / f".tracking.{app.module_name}.toml" + + def tracking(self, app: AppConfig) -> dict[str, ...]: + """Load the build tracking information for the app. + + :param app: The config object for the app + :return: build tracking cache + """ + try: + return self._tracking[app] + except KeyError: + try: + toml = self.tracking_path(app).read_text(encoding="utf-8") + except (OSError, AttributeError): + toml = "" + + self._tracking[app] = tomllib.loads(toml) + return self._tracking[app] + + def tracking_save(self) -> None: + """Update the persistent build tracking information.""" + for app in self.apps.values(): + try: + content = tomli_w.dumps(self._tracking[app]) + except KeyError: + pass + else: + try: + with self.tracking_path(app).open("w", encoding="utf-8") as f: + f.write(content) + except OSError as e: + self.logger.warning( + f"Failed to update build tracking for {app.app_name!r}: " + f"{type(e).__name__}: {e}" + ) + + def tracking_set(self, app: AppConfig, key: str, value: object) -> None: + """Update a build tracking key/value pair.""" + self.tracking(app)[key] = value + + def tracking_add_requirements( + self, + app: AppConfig, + requires: Iterable[str], + ) -> None: + """Update the building tracking for the app's requirements.""" + self.tracking_set(app, key="requires", value=requires) + + def tracking_is_requirements_updated( + self, + app: AppConfig, + requires: Iterable[str], + ) -> bool: + """Has the app's requirements changed since last run?""" + try: + return self.tracking(app)["requires"] != requires + except KeyError: + return True + + def tracking_source_modified_time( + self, + sources: Iterable[str | os.PathLike], + ) -> float: + """The epoch datetime of the most recently modified file in the app's + sources.""" + return max( + max((Path(dir_path) / f).stat().st_mtime for f in files) + for src in sources + for dir_path, _, files in self.tools.os.walk(Path.cwd() / src) + ) + + def tracking_add_source_modified_time( + self, + app: AppConfig, + sources: Iterable[str | os.PathLike], + ) -> None: + """Update build tracking for the app's source code's last modified datetime.""" + self.tracking_set( + app, + key="src_last_modified", + value=self.tracking_source_modified_time(sources), + ) + + def tracking_is_source_modified( + self, + app: AppConfig, + sources: Iterable[str | os.PathLike], + ) -> bool: + """Has the app's source been modified since last run?""" + try: + tracked_time = self.tracking(app)["src_last_modified"] + return tracked_time < self.tracking_source_modified_time(sources) + except KeyError: + return True + @property def briefcase_required_python_version(self): """The major.minor of the minimum Python version required by Briefcase itself. @@ -755,12 +862,7 @@ def add_default_options(self, parser): help="Save a detailed log to file. By default, this log file is only created for critical errors", ) - def _add_update_options( - self, - parser, - context_label="", - update=True, - ): + def _add_update_options(self, parser, context_label="", update=True): """Internal utility method for adding common update options. :param parser: The parser to which options should be added. diff --git a/src/briefcase/commands/build.py b/src/briefcase/commands/build.py index 9c3c7ddc4..02f7e722b 100644 --- a/src/briefcase/commands/build.py +++ b/src/briefcase/commands/build.py @@ -24,6 +24,7 @@ def build_app(self, app: AppConfig, **options): def _build_app( self, app: AppConfig, + build: bool, update: bool, update_requirements: bool, update_resources: bool, @@ -31,7 +32,7 @@ def _build_app( no_update: bool, test_mode: bool, **options, - ) -> dict | None: + ) -> dict: """Internal method to invoke a build on a single app. Ensures the app exists, and has been updated (if requested) before attempting to issue the actual build command. @@ -48,17 +49,10 @@ def _build_app( """ if not self.bundle_path(app).exists(): state = self.create_command(app, test_mode=test_mode, **options) - elif ( - update # An explicit update has been requested - or update_requirements # An explicit update of requirements has been requested - or update_resources # An explicit update of resources has been requested - or update_support # An explicit update of app support has been requested - or ( - test_mode and not no_update - ) # Test mode, but updates have not been disabled - ): + elif not no_update: state = self.update_command( app, + update_app=update, update_requirements=update_requirements, update_resources=update_resources, update_support=update_support, @@ -66,22 +60,27 @@ def _build_app( **options, ) else: - state = None + state = {} - self.verify_app(app) + if build or state.pop("is_app_updated", False): + self.verify_app(app) - state = self.build_app(app, test_mode=test_mode, **full_options(state, options)) + state = self.build_app( + app, test_mode=test_mode, **full_options(state, options) + ) + + qualifier = " (test mode)" if test_mode else "" + self.logger.info( + f"Built {self.binary_path(app).relative_to(self.base_path)}{qualifier}", + prefix=app.app_name, + ) - qualifier = " (test mode)" if test_mode else "" - self.logger.info( - f"Built {self.binary_path(app).relative_to(self.base_path)}{qualifier}", - prefix=app.app_name, - ) return state def __call__( self, app: AppConfig | None = None, + build: bool = True, update: bool = False, update_requirements: bool = False, update_resources: bool = False, @@ -117,6 +116,7 @@ def __call__( if app: state = self._build_app( app, + build=build, update=update, update_requirements=update_requirements, update_resources=update_resources, @@ -130,6 +130,7 @@ def __call__( for app_name, app in sorted(self.apps.items()): state = self._build_app( app, + build=build, update=update, update_requirements=update_requirements, update_resources=update_resources, diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py index da05e9866..8001399fa 100644 --- a/src/briefcase/commands/create.py +++ b/src/briefcase/commands/create.py @@ -548,7 +548,7 @@ def _install_app_requirements( :param requires: The list of requirements to install :param app_packages_path: The full path of the app_packages folder into which requirements should be installed. - :param progress_message: The waitbar progress message to display to the user. + :param progress_message: The Wait Bar progress message to display to the user. :param pip_kwargs: Any additional keyword arguments to pass to the subprocess when invoking pip. """ @@ -586,9 +586,7 @@ def install_app_requirements(self, app: AppConfig, test_mode: bool): :param app: The config object for the app :param test_mode: Should the test requirements be installed? """ - requires = app.requires.copy() if app.requires else [] - if test_mode and app.test_requires: - requires.extend(app.test_requires) + requires = app.requires(test_mode=test_mode) try: requirements_path = self.app_requirements_path(app) @@ -602,6 +600,8 @@ def install_app_requirements(self, app: AppConfig, test_mode: bool): "Application path index file does not define " "`app_requirements_path` or `app_packages_path`" ) from e + else: + self.tracking_add_requirements(app, requires=requires) def install_app_code(self, app: AppConfig, test_mode: bool): """Install the application code into the bundle. @@ -615,9 +615,7 @@ def install_app_code(self, app: AppConfig, test_mode: bool): self.tools.shutil.rmtree(app_path) self.tools.os.mkdir(app_path) - sources = app.sources.copy() if app.sources else [] - if test_mode and app.test_sources: - sources.extend(app.test_sources) + sources = app.sources(test_mode=test_mode) # Install app code. if sources: @@ -636,12 +634,10 @@ def install_app_code(self, app: AppConfig, test_mode: bool): else: self.logger.info(f"No sources defined for {app.app_name}.") - # Write the dist-info folder for the application. - write_dist_info( - app=app, - dist_info_path=self.app_path(app) - / f"{app.module_name}-{app.version}.dist-info", - ) + self.tracking_add_source_modified_time(app, sources=sources) + + # Write the dist-info folder for the application + write_dist_info(app=app, dist_info_path=self.dist_info_path(app)) def install_image(self, role, variant, size, source, target): """Install an icon/image of the requested size at a target location, using the diff --git a/src/briefcase/commands/dev.py b/src/briefcase/commands/dev.py index f37e36ec5..d6d8fa16d 100644 --- a/src/briefcase/commands/dev.py +++ b/src/briefcase/commands/dev.py @@ -74,16 +74,23 @@ def add_options(self, parser): help="Run the app in test mode", ) - def install_dev_requirements(self, app: AppConfig, **options): + def dist_info_path(self, app: AppConfig) -> Path: + """Path to dist-info for the app where the app source lives.""" + return self.app_module_path(app).parent / f"{app.module_name}.dist-info" + + def tracking_path(self, app: AppConfig) -> Path: + """Build tracking.""" + return self.dist_info_path(app) / f".tracking.{app.module_name}.toml" + + def install_dev_requirements(self, app: AppConfig, test_mode: bool, **options): """Install the requirements for the app dev. This will always include test requirements, if specified. :param app: The config object for the app + :param test_mode: Whether the test suite is being run, rather than the app? """ - requires = app.requires if app.requires else [] - if app.test_requires: - requires.extend(app.test_requires) + requires = app.requires(test_mode=test_mode) if requires: with self.input.wait_bar("Installing dev requirements..."): @@ -106,6 +113,8 @@ def install_dev_requirements(self, app: AppConfig, **options): ) except subprocess.CalledProcessError as e: raise RequirementsInstallError() from e + else: + self.tracking_add_requirements(app, requires=requires) else: self.logger.info("No application requirements.") @@ -195,31 +204,29 @@ def __call__( raise BriefcaseCommandError( f"Project doesn't define an application named '{appname}'" ) from e - else: raise BriefcaseCommandError( "Project specifies more than one application; use --app to specify which one to start." ) + # Confirm host compatibility, that all required tools are available, # and that the app configuration is finalized. self.finalize(app) self.verify_app(app) - # Look for the existence of a dist-info file. - # If one exists, assume that the requirements have already been - # installed. If a dependency update has been manually requested, - # do it regardless. - dist_info_path = ( - self.app_module_path(app).parent / f"{app.module_name}.dist-info" - ) - if not run_app: - # If we are not running the app, it means we should update requirements. - update_requirements = True - if update_requirements or not dist_info_path.exists(): + # If we are not running the app, it means we should update requirements. + update_requirements |= not run_app + + if not update_requirements: + update_requirements = self.tracking_is_requirements_updated( + app, requires=app.requires(test_mode=test_mode) + ) + + if update_requirements: self.logger.info("Installing requirements...", prefix=app.app_name) - self.install_dev_requirements(app, **options) - write_dist_info(app, dist_info_path) + self.install_dev_requirements(app, test_mode, **options) + write_dist_info(app, self.dist_info_path(app)) if run_app: if test_mode: @@ -228,10 +235,10 @@ def __call__( ) else: self.logger.info("Starting in dev mode...", prefix=app.app_name) - env = self.get_environment(app, test_mode=test_mode) + return self.run_dev_app( app, - env, + env=self.get_environment(app, test_mode=test_mode), test_mode=test_mode, passthrough=[] if passthrough is None else passthrough, **options, diff --git a/src/briefcase/commands/package.py b/src/briefcase/commands/package.py index 403481907..b5e4d8e01 100644 --- a/src/briefcase/commands/package.py +++ b/src/briefcase/commands/package.py @@ -58,28 +58,16 @@ def _package_app( :param update: Should the application be updated (and rebuilt) first? :param packaging_format: The format of the packaging artefact to create. """ - - template_file = self.bundle_path(app) - binary_file = self.binary_path(app) - if not template_file.exists(): - state = self.create_command(app, **options) - state = self.build_command(app, **full_options(state, options)) - elif update: - # If we're updating for packaging, update everything. - # This ensures everything in the packaged artefact is up to date, - # and is in a production state - state = self.update_command( - app, - update_resources=True, - update_requirements=True, - update_support=True, - **options, - ) - state = self.build_command(app, **full_options(state, options)) - elif not binary_file.exists(): - state = self.build_command(app, **options) - else: - state = None + # Update and build the app if necessary + state = self.build_command( + app, + build=not self.binary_path(app).exists(), + update=update, + update_resources=update, + update_requirements=update, + update_support=update, + **options, + ) # Annotate the packaging format onto the app app.packaging_format = packaging_format @@ -100,6 +88,7 @@ def _package_app( filename = self.distribution_path(app).relative_to(self.base_path) self.logger.info(f"Packaged {filename}", prefix=app.app_name) + return state def add_options(self, parser): diff --git a/src/briefcase/commands/run.py b/src/briefcase/commands/run.py index 4977b3419..5b10a976d 100644 --- a/src/briefcase/commands/run.py +++ b/src/briefcase/commands/run.py @@ -283,21 +283,10 @@ def __call__( # and that the app configuration is finalized. self.finalize(app) - template_file = self.bundle_path(app) - binary_file = self.binary_path(app) - if ( - (not template_file.exists()) # App hasn't been created - or update # An explicit update has been requested - or update_requirements # An explicit update of requirements has been requested - or update_resources # An explicit update of resources has been requested - or update_support # An explicit update of support files has been requested - or (not binary_file.exists()) # Binary doesn't exist yet - or ( - test_mode and not no_update - ) # Test mode, but updates have not been disabled - ): + if not no_update: state = self.build_command( app, + build=not self.binary_path(app).exists(), update=update, update_requirements=update_requirements, update_resources=update_resources, diff --git a/src/briefcase/commands/update.py b/src/briefcase/commands/update.py index 4b3809fcc..e454d0b99 100644 --- a/src/briefcase/commands/update.py +++ b/src/briefcase/commands/update.py @@ -17,31 +17,49 @@ def add_options(self, parser): def update_app( self, app: AppConfig, + update_app: bool, update_requirements: bool, update_resources: bool, update_support: bool, test_mode: bool, **options, - ) -> dict | None: + ) -> dict: """Update an existing application bundle. :param app: The config object for the app + :param update_app: Should the app sources be updated? :param update_requirements: Should requirements be updated? :param update_resources: Should extra resources be updated? :param update_support: Should app support be updated? - :param test_mode: Should the app be updated in test mode? + :param test_mode: Should the app be updated for test mode? """ if not self.bundle_path(app).exists(): self.logger.error( "Application does not exist; call create first!", prefix=app.app_name ) - return + return {} - self.verify_app(app) + if not update_requirements: + update_requirements = self.tracking_is_requirements_updated( + app, requires=app.requires(test_mode=test_mode) + ) + + if not update_app: + update_app = self.tracking_is_source_modified( + app, sources=app.sources(test_mode=test_mode) + ) - self.logger.info("Updating application code...", prefix=app.app_name) - self.install_app_code(app=app, test_mode=test_mode) + is_app_being_updated = ( + update_app or update_requirements or update_resources or update_support + ) + + if is_app_being_updated: + self.verify_app(app) + + if update_app: + self.logger.info("Updating application code...", prefix=app.app_name) + self.install_app_code(app=app, test_mode=test_mode) if update_requirements: self.logger.info("Updating requirements...", prefix=app.app_name) @@ -56,20 +74,24 @@ def update_app( self.cleanup_app_support_package(app=app) self.install_app_support_package(app=app) - self.logger.info("Removing unneeded app content...", prefix=app.app_name) - self.cleanup_app_content(app=app) + if is_app_being_updated: + self.logger.info("Removing unneeded app content...", prefix=app.app_name) + self.cleanup_app_content(app=app) + + self.logger.info("Application updated.", prefix=app.app_name) - self.logger.info("Application updated.", prefix=app.app_name) + return {"is_app_updated": is_app_being_updated} def __call__( self, app: AppConfig | None = None, + update_app: bool = True, update_requirements: bool = False, update_resources: bool = False, update_support: bool = False, test_mode: bool = False, **options, - ) -> dict | None: + ) -> dict: # Confirm host compatibility, that all required tools are available, # and that the app configuration is finalized. self.finalize(app) @@ -77,6 +99,7 @@ def __call__( if app: state = self.update_app( app, + update_app=update_app, update_requirements=update_requirements, update_resources=update_resources, update_support=update_support, @@ -88,6 +111,7 @@ def __call__( for app_name, app in sorted(self.apps.items()): state = self.update_app( app, + update_app=update_app, update_requirements=update_requirements, update_resources=update_resources, update_support=update_support, diff --git a/src/briefcase/config.py b/src/briefcase/config.py index a86f8f2ac..59f49e66c 100644 --- a/src/briefcase/config.py +++ b/src/briefcase/config.py @@ -210,12 +210,12 @@ def __init__( self.bundle = bundle # Description can only be a single line. Ignore everything else. self.description = description.split("\n")[0] - self.sources = sources + self._sources = sources self.formal_name = app_name if formal_name is None else formal_name self.url = url self.author = author self.author_email = author_email - self.requires = requires + self._requires = requires self.icon = icon self.splash = splash self.document_types = {} if document_type is None else document_type @@ -253,8 +253,8 @@ def __init__( ) # Sources list doesn't include any duplicates - source_modules = {source.rsplit("/", 1)[-1] for source in self.sources} - if len(self.sources) != len(source_modules): + source_modules = {source.rsplit("/", 1)[-1] for source in self._sources} + if len(self._sources) != len(source_modules): raise BriefcaseConfigError( f"The `sources` list for {self.app_name!r} contains duplicated " "package names." @@ -267,11 +267,11 @@ def __init__( f"package named {self.module_name!r}." ) - def __repr__(self): + def __repr__(self) -> str: return f"<{self.bundle_identifier} v{self.version} AppConfig>" @property - def module_name(self): + def module_name(self) -> str: """The module name for the app. This is derived from the name, but: @@ -280,7 +280,7 @@ def module_name(self): return self.app_name.replace("-", "_") @property - def bundle_name(self): + def bundle_name(self) -> str: """The bundle name for the app. This is derived from the app name, but: @@ -289,7 +289,7 @@ def bundle_name(self): return self.app_name.replace("_", "-") @property - def bundle_identifier(self): + def bundle_identifier(self) -> str: """The bundle identifier for the app. This is derived from the bundle and the bundle name, joined by a `.`. @@ -297,7 +297,7 @@ def bundle_identifier(self): return f"{self.bundle}.{self.bundle_name}" @property - def class_name(self): + def class_name(self) -> str: """The class name for the app. This is derived from the formal name for the app. @@ -305,19 +305,19 @@ def class_name(self): return make_class_name(self.formal_name) @property - def package_name(self): + def package_name(self) -> str: """The bundle name of the app, with `-` replaced with `_` to create something that can be used a namespace identifier on Python or Java, similar to `module_name`.""" return self.bundle.replace("-", "_") - def PYTHONPATH(self, test_mode): + def PYTHONPATH(self, test_mode: bool) -> list[str]: """The PYTHONPATH modifications needed to run this app. :param test_mode: Should test_mode sources be included? """ paths = [] - sources = self.sources + sources = self._sources if test_mode and self.test_sources: sources.extend(self.test_sources) @@ -327,7 +327,7 @@ def PYTHONPATH(self, test_mode): paths.append(path) return paths - def main_module(self, test_mode: bool): + def main_module(self, test_mode: bool) -> str: """The path to the main module for the app. In normal operation, this is ``app.module_name``; however, @@ -340,6 +340,20 @@ def main_module(self, test_mode: bool): else: return self.module_name + def requires(self, test_mode: bool = False) -> list[str]: + """App requirements incorporating whether test mode is active.""" + requires = self._requires.copy() if self._requires else [] + if test_mode and self.test_requires: + requires.extend(self.test_requires) + return requires + + def sources(self, test_mode: bool = False) -> list[str]: + """App sources incorporating whether test mode is active.""" + sources = self._sources.copy() if self._sources else [] + if test_mode and self.test_sources: + sources.extend(self.test_sources) + return sources + def merge_config(config, data): """Merge a new set of configuration requirements into a base configuration. @@ -464,9 +478,11 @@ def parse_config(config_file, platform, output_format): # Merge the PEP621 configuration (if it exists) try: - merge_pep621_config(global_config, pyproject["project"]) + pep612_config = pyproject["project"] except KeyError: pass + else: + merge_pep621_config(global_config, pep612_config) # For consistent results, sort the platforms and formats all_platforms = sorted(get_platforms().keys()) diff --git a/src/briefcase/integrations/docker.py b/src/briefcase/integrations/docker.py index e7660aaf1..cd853f7a5 100644 --- a/src/briefcase/integrations/docker.py +++ b/src/briefcase/integrations/docker.py @@ -924,7 +924,7 @@ def prepare( f"HOST_GID={self.tools.os.getgid()}", Path( self.app_base_path, - *self.app.sources[0].split("/")[:-1], + *self.app.sources()[0].split("/")[:-1], ), ] + (extra_build_args if extra_build_args is not None else []), diff --git a/tests/commands/base/test_app_module_path.py b/tests/commands/base/test_app_module_path.py index bce0edf05..784faa4f3 100644 --- a/tests/commands/base/test_app_module_path.py +++ b/tests/commands/base/test_app_module_path.py @@ -6,7 +6,7 @@ def test_single_source(base_command, my_app): """If an app provides a single source location and it matches, it is selected as the dist-info location.""" - my_app.sources = ["src/my_app"] + my_app._sources = ["src/my_app"] assert base_command.app_module_path(my_app) == base_command.base_path / "src/my_app" @@ -14,7 +14,7 @@ def test_single_source(base_command, my_app): def test_no_prefix(base_command, my_app): """If an app provides a source location without a prefix and it matches, it is selected as the dist-info location.""" - my_app.sources = ["my_app"] + my_app._sources = ["my_app"] assert base_command.app_module_path(my_app) == base_command.base_path / "my_app" @@ -22,7 +22,7 @@ def test_no_prefix(base_command, my_app): def test_long_prefix(base_command, my_app): """If an app provides a source location with a long prefix and it matches, it is selected as the dist-info location.""" - my_app.sources = ["path/to/src/my_app"] + my_app._sources = ["path/to/src/my_app"] assert ( base_command.app_module_path(my_app) @@ -33,14 +33,14 @@ def test_long_prefix(base_command, my_app): def test_matching_source(base_command, my_app): """If an app provides a single matching source location, it is selected as the dist- info location.""" - my_app.sources = ["src/other", "src/my_app", "src/extra"] + my_app._sources = ["src/other", "src/my_app", "src/extra"] assert base_command.app_module_path(my_app) == base_command.base_path / "src/my_app" def test_multiple_match(base_command, my_app): """If an app provides multiple matching source location, an error is raised.""" - my_app.sources = ["src/my_app", "extra/my_app"] + my_app._sources = ["src/my_app", "extra/my_app"] with pytest.raises( BriefcaseCommandError, @@ -52,7 +52,7 @@ def test_multiple_match(base_command, my_app): def test_hyphen_source(base_command, my_app): """If an app provides a single source location with a hyphen, an error is raised.""" # The source directory must be a valid module, so hyphens aren't legal. - my_app.sources = ["src/my-app"] + my_app._sources = ["src/my-app"] with pytest.raises( BriefcaseCommandError, @@ -64,7 +64,7 @@ def test_hyphen_source(base_command, my_app): def test_no_match(base_command, my_app): """If an app provides a multiple locations, none of which match, an error is raised.""" - my_app.sources = ["src/pork", "src/spam"] + my_app._sources = ["src/pork", "src/spam"] with pytest.raises( BriefcaseCommandError, @@ -75,7 +75,7 @@ def test_no_match(base_command, my_app): def test_no_source(base_command, my_app): """If an app provides no source locations, an error is raised.""" - my_app.sources = [] + my_app._sources = [] with pytest.raises( BriefcaseCommandError, diff --git a/tests/commands/create/test_install_app_code.py b/tests/commands/create/test_install_app_code.py index ab0a754c5..a398001e2 100644 --- a/tests/commands/create/test_install_app_code.py +++ b/tests/commands/create/test_install_app_code.py @@ -50,7 +50,7 @@ def test_no_code( create_command.tools.shutil = mock.MagicMock(spec_set=shutil) create_command.tools.os = mock.MagicMock(spec_set=os) - myapp.sources = None + myapp._sources = None create_command.install_app_code(myapp, test_mode=False) @@ -76,7 +76,7 @@ def test_empty_code( create_command.tools.shutil = mock.MagicMock(spec_set=shutil) create_command.tools.os = mock.MagicMock(spec_set=os) - myapp.sources = [] + myapp._sources = [] create_command.install_app_code(myapp, test_mode=False) @@ -98,7 +98,7 @@ def test_source_missing( ): """If an app defines sources that are missing, an error is raised.""" # Set the app definition to point at sources that don't exist - myapp.sources = ["missing"] + myapp._sources = ["missing"] with pytest.raises(MissingAppSources): create_command.install_app_code(myapp, test_mode=False) @@ -138,7 +138,7 @@ def test_source_dir( ) # Set the app definition, and install sources - myapp.sources = ["src/first", "src/second"] + myapp._sources = ["src/first", "src/second"] create_command.install_app_code(myapp, test_mode=False) @@ -156,7 +156,7 @@ def test_source_dir( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources == ["src/first", "src/second"] + assert myapp._sources == ["src/first", "src/second"] assert myapp.test_sources is None @@ -182,7 +182,7 @@ def test_source_file( ) # Set the app definition, and install sources - myapp.sources = ["src/demo.py", "other.py"] + myapp._sources = ["src/demo.py", "other.py"] create_command.install_app_code(myapp, test_mode=False) @@ -194,7 +194,7 @@ def test_source_file( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources == ["src/demo.py", "other.py"] + assert myapp._sources == ["src/demo.py", "other.py"] assert myapp.test_sources is None @@ -231,7 +231,7 @@ def test_no_existing_app_folder( shutil.rmtree(app_path) # Set the app definition, and install sources - myapp.sources = ["src/first/demo.py", "src/second"] + myapp._sources = ["src/first/demo.py", "src/second"] create_command.install_app_code(myapp, test_mode=False) @@ -260,7 +260,7 @@ def test_no_existing_app_folder( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources == ["src/first/demo.py", "src/second"] + assert myapp._sources == ["src/first/demo.py", "src/second"] assert myapp.test_sources is None @@ -334,7 +334,7 @@ def test_replace_sources( old_dist_info_dir.mkdir() # Set the app definition, and install sources - myapp.sources = ["src/first/demo.py", "src/second"] + myapp._sources = ["src/first/demo.py", "src/second"] create_command.install_app_code(myapp, test_mode=False) @@ -363,7 +363,7 @@ def test_replace_sources( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources == ["src/first/demo.py", "src/second"] + assert myapp._sources == ["src/first/demo.py", "src/second"] assert myapp.test_sources is None @@ -385,7 +385,7 @@ def test_non_latin_metadata( create_command.tools.shutil = mock.MagicMock(spec_set=shutil) create_command.tools.os = mock.MagicMock(spec_set=os) - myapp.sources = [] + myapp._sources = [] create_command.install_app_code(myapp, test_mode=False) @@ -471,7 +471,7 @@ def test_test_sources( ) # Set the app definition, and install sources - myapp.sources = ["src/first", "src/second"] + myapp._sources = ["src/first", "src/second"] myapp.test_sources = ["tests", "othertests"] create_command.install_app_code(myapp, test_mode=False) @@ -491,7 +491,7 @@ def test_test_sources( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources == ["src/first", "src/second"] + assert myapp._sources == ["src/first", "src/second"] assert myapp.test_sources == ["tests", "othertests"] @@ -543,7 +543,7 @@ def test_test_sources_test_mode( ) # Set the app definition, and install sources - myapp.sources = ["src/first", "src/second"] + myapp._sources = ["src/first", "src/second"] myapp.test_sources = ["tests", "othertests"] create_command.install_app_code(myapp, test_mode=True) @@ -566,7 +566,7 @@ def test_test_sources_test_mode( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources == ["src/first", "src/second"] + assert myapp._sources == ["src/first", "src/second"] assert myapp.test_sources == ["tests", "othertests"] @@ -614,7 +614,7 @@ def test_only_test_sources_test_mode( ) # Set the app definition, and install sources - myapp.sources = None + myapp._sources = None myapp.test_sources = ["tests", "othertests"] create_command.install_app_code(myapp, test_mode=True) @@ -634,5 +634,5 @@ def test_only_test_sources_test_mode( assert_dist_info(app_path) # Original app definitions haven't changed - assert myapp.sources is None + assert myapp._sources is None assert myapp.test_sources == ["tests", "othertests"] diff --git a/tests/commands/create/test_install_app_requirements.py b/tests/commands/create/test_install_app_requirements.py index 59dda9dc4..f4cc936b6 100644 --- a/tests/commands/create/test_install_app_requirements.py +++ b/tests/commands/create/test_install_app_requirements.py @@ -56,7 +56,7 @@ def test_bad_path_index(create_command, myapp, bundle_path, app_requirements_pat tomli_w.dump(index, f) # Set up requirements for the app - myapp.requires = ["first", "second", "third"] + myapp._requires = ["first", "second", "third"] # Install requirements with pytest.raises( @@ -72,7 +72,7 @@ def test_bad_path_index(create_command, myapp, bundle_path, app_requirements_pat assert not app_requirements_path.exists() # Original app definitions haven't changed - assert myapp.requires == ["first", "second", "third"] + assert myapp._requires == ["first", "second", "third"] assert myapp.test_requires is None @@ -83,7 +83,7 @@ def test_app_packages_no_requires( app_packages_path_index, ): """If an app has no requirements, install_app_requirements is a no-op.""" - myapp.requires = None + myapp._requires = None create_command.install_app_requirements(myapp, test_mode=False) @@ -98,7 +98,7 @@ def test_app_packages_empty_requires( app_packages_path_index, ): """If an app has an empty requirements list, install_app_requirements is a no-op.""" - myapp.requires = [] + myapp._requires = [] create_command.install_app_requirements(myapp, test_mode=False) @@ -113,7 +113,7 @@ def test_app_packages_valid_requires( app_packages_path_index, ): """If an app has a valid list of requirements, pip is invoked.""" - myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp._requires = ["first", "second==1.2.3", "third>=3.2.1"] create_command.install_app_requirements(myapp, test_mode=False) @@ -141,7 +141,7 @@ def test_app_packages_valid_requires( ) # Original app definitions haven't changed - assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp._requires == ["first", "second==1.2.3", "third>=3.2.1"] assert myapp.test_requires is None @@ -153,7 +153,7 @@ def test_app_packages_valid_requires_no_support_package( ): """If the template doesn't specify a support package, the cross-platform site isn't specified.""" - myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp._requires = ["first", "second==1.2.3", "third>=3.2.1"] # Override the cache of paths to specify an app packages path, but no support package path create_command._briefcase_toml[myapp] = { @@ -186,7 +186,7 @@ def test_app_packages_valid_requires_no_support_package( ) # Original app definitions haven't changed - assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp._requires == ["first", "second==1.2.3", "third>=3.2.1"] assert myapp.test_requires is None @@ -197,7 +197,7 @@ def test_app_packages_invalid_requires( app_packages_path_index, ): """If an app has a valid list of requirements, pip is invoked.""" - myapp.requires = ["does-not-exist"] + myapp._requires = ["does-not-exist"] # Unfortunately, no way to tell the difference between "offline" and # "your requirements are invalid"; pip returns status code 1 for all @@ -233,7 +233,7 @@ def test_app_packages_invalid_requires( ) # Original app definitions haven't changed - assert myapp.requires == ["does-not-exist"] + assert myapp._requires == ["does-not-exist"] assert myapp.test_requires is None @@ -244,7 +244,7 @@ def test_app_packages_offline( app_packages_path_index, ): """If user is offline, pip fails.""" - myapp.requires = ["first", "second", "third"] + myapp._requires = ["first", "second", "third"] # Unfortunately, no way to tell the difference between "offline" and # "your requirements are invalid"; pip returns status code 1 for all @@ -282,7 +282,7 @@ def test_app_packages_offline( ) # Original app definitions haven't changed - assert myapp.requires == ["first", "second", "third"] + assert myapp._requires == ["first", "second", "third"] assert myapp.test_requires is None @@ -299,11 +299,11 @@ def test_app_packages_install_requirements( create_command.logger.verbosity = logging_level # Set up the app requirements - myapp.requires = ["first", "second", "third"] + myapp._requires = ["first", "second", "third"] # The side effect of calling pip is creating installation artefacts create_command.tools[myapp].app_context.run.side_effect = ( - create_installation_artefacts(app_packages_path, myapp.requires) + create_installation_artefacts(app_packages_path, myapp._requires) ) # Install the requirements @@ -340,7 +340,7 @@ def test_app_packages_install_requirements( assert (app_packages_path / "third/__main__.py").exists() # Original app definitions haven't changed - assert myapp.requires == ["first", "second", "third"] + assert myapp._requires == ["first", "second", "third"] assert myapp.test_requires is None @@ -355,11 +355,11 @@ def test_app_packages_replace_existing_requirements( create_installation_artefacts(app_packages_path, ["old", "ancient"])() # Set up the app requirements - myapp.requires = ["first", "second", "third"] + myapp._requires = ["first", "second", "third"] # The side effect of calling pip is creating installation artefacts create_command.tools[myapp].app_context.run.side_effect = ( - create_installation_artefacts(app_packages_path, myapp.requires) + create_installation_artefacts(app_packages_path, myapp._requires) ) # Install the requirements @@ -401,7 +401,7 @@ def test_app_packages_replace_existing_requirements( assert not (app_packages_path / "ancient").exists() # Original app definitions haven't changed - assert myapp.requires == ["first", "second", "third"] + assert myapp._requires == ["first", "second", "third"] assert myapp.test_requires is None @@ -412,7 +412,7 @@ def test_app_requirements_no_requires( app_requirements_path_index, ): """If an app has no requirements, a requirements file is still written.""" - myapp.requires = None + myapp._requires = None # Install requirements into the bundle create_command.install_app_requirements(myapp, test_mode=False) @@ -423,7 +423,7 @@ def test_app_requirements_no_requires( assert f.read() == "" # Original app definitions haven't changed - assert myapp.requires is None + assert myapp._requires is None assert myapp.test_requires is None @@ -435,7 +435,7 @@ def test_app_requirements_empty_requires( ): """If an app has an empty requirements list, a requirements file is still written.""" - myapp.requires = [] + myapp._requires = [] # Install requirements into the bundle create_command.install_app_requirements(myapp, test_mode=False) @@ -446,7 +446,7 @@ def test_app_requirements_empty_requires( assert f.read() == "" # Original app definitions haven't changed - assert myapp.requires == [] + assert myapp._requires == [] assert myapp.test_requires is None @@ -458,7 +458,7 @@ def test_app_requirements_requires( ): """If an app has an empty requirements list, a requirements file is still written.""" - myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp._requires = ["first", "second==1.2.3", "third>=3.2.1"] # Install requirements into the bundle create_command.install_app_requirements(myapp, test_mode=False) @@ -469,7 +469,7 @@ def test_app_requirements_requires( assert f.read() == "first\nsecond==1.2.3\nthird>=3.2.1\n" # Original app definitions haven't changed - assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp._requires == ["first", "second==1.2.3", "third>=3.2.1"] assert myapp.test_requires is None @@ -508,7 +508,7 @@ def _test_app_requirements_paths( requirement, converted = requirement else: converted = requirement - myapp.requires = ["first", requirement, "third"] + myapp._requires = ["first", requirement, "third"] create_command.install_app_requirements(myapp, test_mode=False) with app_requirements_path.open(encoding="utf-8") as f: @@ -524,7 +524,7 @@ def _test_app_requirements_paths( ) # Original app definitions haven't changed - assert myapp.requires == ["first", requirement, "third"] + assert myapp._requires == ["first", requirement, "third"] assert myapp.test_requires is None @@ -645,7 +645,7 @@ def test_app_packages_test_requires( ): """If an app has test requirements, they're not included unless we are in test mode.""" - myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp._requires = ["first", "second==1.2.3", "third>=3.2.1"] myapp.test_requires = ["pytest", "pytest-tldr"] create_command.install_app_requirements(myapp, test_mode=False) @@ -674,7 +674,7 @@ def test_app_packages_test_requires( ) # Original app definitions haven't changed - assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp._requires == ["first", "second==1.2.3", "third>=3.2.1"] assert myapp.test_requires == ["pytest", "pytest-tldr"] @@ -685,7 +685,7 @@ def test_app_packages_test_requires_test_mode( app_packages_path_index, ): """If an app has test requirements and we're in test mode, they are installed.""" - myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp._requires = ["first", "second==1.2.3", "third>=3.2.1"] myapp.test_requires = ["pytest", "pytest-tldr"] create_command.install_app_requirements(myapp, test_mode=True) @@ -716,7 +716,7 @@ def test_app_packages_test_requires_test_mode( ) # Original app definitions haven't changed - assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp._requires == ["first", "second==1.2.3", "third>=3.2.1"] assert myapp.test_requires == ["pytest", "pytest-tldr"] @@ -728,7 +728,7 @@ def test_app_packages_only_test_requires_test_mode( ): """If an app only has test requirements and we're in test mode, they are installed.""" - myapp.requires = None + myapp._requires = None myapp.test_requires = ["pytest", "pytest-tldr"] create_command.install_app_requirements(myapp, test_mode=True) @@ -756,5 +756,5 @@ def test_app_packages_only_test_requires_test_mode( ) # Original app definitions haven't changed - assert myapp.requires is None + assert myapp._requires is None assert myapp.test_requires == ["pytest", "pytest-tldr"] diff --git a/tests/commands/dev/test_install_dev_requirements.py b/tests/commands/dev/test_install_dev_requirements.py index a813a4cdc..c3f421bae 100644 --- a/tests/commands/dev/test_install_dev_requirements.py +++ b/tests/commands/dev/test_install_dev_requirements.py @@ -13,7 +13,7 @@ def test_install_requirements_no_error(dev_command, first_app, logging_level): # Configure logging level dev_command.logger.verbosity = logging_level - first_app.requires = ["package-one", "package_two", "packagethree"] + first_app._requires = ["package-one", "package_two", "packagethree"] dev_command.install_dev_requirements(app=first_app) @@ -37,7 +37,7 @@ def test_install_requirements_no_error(dev_command, first_app, logging_level): def test_install_requirements_error(dev_command, first_app): """Ensure RequirementsInstallError exception is raised for install errors.""" - first_app.requires = ["package-one", "package_two", "packagethree"] + first_app._requires = ["package-one", "package_two", "packagethree"] dev_command.tools.subprocess.run.side_effect = CalledProcessError( returncode=-1, cmd="pip" @@ -70,7 +70,7 @@ def test_install_requirements_error(dev_command, first_app): def test_no_requirements(dev_command, first_app): """Ensure dependency installation is not attempted when nothing to install.""" - first_app.requires = [] + first_app._requires = [] dev_command.install_dev_requirements(app=first_app) @@ -79,7 +79,7 @@ def test_no_requirements(dev_command, first_app): def test_install_requirements_test_mode(dev_command, first_app): """If an app has test requirements, they are also installed.""" - first_app.requires = ["package-one", "package_two", "packagethree"] + first_app._requires = ["package-one", "package_two", "packagethree"] first_app.test_requires = ["test-one", "test_two"] dev_command.install_dev_requirements(app=first_app) @@ -107,7 +107,7 @@ def test_install_requirements_test_mode(dev_command, first_app): def test_only_test_requirements(dev_command, first_app): """If an app only has test requirements, they're installed correctly.""" - first_app.requires = None + first_app._requires = None first_app.test_requires = ["test-one", "test_two"] dev_command.install_dev_requirements(app=first_app) diff --git a/tests/config/test_AppConfig.py b/tests/config/test_AppConfig.py index 8d3cb6b5c..a016b5083 100644 --- a/tests/config/test_AppConfig.py +++ b/tests/config/test_AppConfig.py @@ -19,7 +19,7 @@ def test_minimal_AppConfig(): assert config.version == "1.2.3" assert config.bundle == "org.beeware" assert config.description == "A simple app" - assert config.requires is None + assert config._requires is None # Derived properties have been set. assert config.bundle_name == "myapp" @@ -70,7 +70,7 @@ def test_extra_attrs(): assert config.description == "A simple app" assert config.long_description == "A longer description\nof the app" assert config.template == "/path/to/template" - assert config.requires == ["first", "second", "third"] + assert config._requires == ["first", "second", "third"] # Properties that are derived by default have been set explicitly assert config.formal_name == "My App!" diff --git a/tests/platforms/iOS/xcode/test_create.py b/tests/platforms/iOS/xcode/test_create.py index 6e9ba9d9a..8ec6abf91 100644 --- a/tests/platforms/iOS/xcode/test_create.py +++ b/tests/platforms/iOS/xcode/test_create.py @@ -37,7 +37,7 @@ def test_extra_pip_args(create_command, first_app_generated, tmp_path): # requirements for the current platform. create_command.tools.host_arch = "wonky" - first_app_generated.requires = ["something==1.2.3", "other>=2.3.4"] + first_app_generated._requires = ["something==1.2.3", "other>=2.3.4"] create_command.tools[first_app_generated].app_context = MagicMock( spec_set=Subprocess diff --git a/tests/platforms/iOS/xcode/test_update.py b/tests/platforms/iOS/xcode/test_update.py index 9f2300151..cb5627a27 100644 --- a/tests/platforms/iOS/xcode/test_update.py +++ b/tests/platforms/iOS/xcode/test_update.py @@ -24,7 +24,7 @@ def test_extra_pip_args(update_command, first_app_generated, tmp_path): # requirements for the current platform. update_command.tools.host_arch = "wonky" - first_app_generated.requires = ["something==1.2.3", "other>=2.3.4"] + first_app_generated._requires = ["something==1.2.3", "other>=2.3.4"] update_command.tools[first_app_generated].app_context = MagicMock( spec_set=Subprocess diff --git a/tests/platforms/linux/test_LocalRequirementsMixin.py b/tests/platforms/linux/test_LocalRequirementsMixin.py index db3d8002c..6c9317f60 100644 --- a/tests/platforms/linux/test_LocalRequirementsMixin.py +++ b/tests/platforms/linux/test_LocalRequirementsMixin.py @@ -267,7 +267,7 @@ def test_install_app_requirements_with_locals( """If the app has local requirements, they are compiled into sdists for installation.""" # Add local requirements - first_app_config.requires.extend([first_package, second_package, third_package]) + first_app_config._requires.extend([first_package, second_package, third_package]) # Mock the side effect of building an sdist def build_sdist(*args, **kwargs): @@ -373,7 +373,7 @@ def test_install_app_requirements_with_bad_local( ): """If the app has local requirement that can't be built, an error is raised.""" # Add a local requirement - first_app_config.requires.append(first_package) + first_app_config._requires.append(first_package) # Mock the building an sdist raising an error create_command.tools.subprocess.check_output.side_effect = ( @@ -425,7 +425,7 @@ def test_install_app_requirements_with_missing_local_build( """If the app references a requirement that needs to be built, but is missing, an error is raised.""" # Define a local requirement, but don't create the files it points at - first_app_config.requires.append(str(tmp_path / "local/first")) + first_app_config._requires.append(str(tmp_path / "local/first")) # Install requirements with pytest.raises( @@ -457,7 +457,7 @@ def test_install_app_requirements_with_bad_local_file( """If the app references a local requirement file that doesn't exist, an error is raised.""" # Add a local requirement that doesn't exist - first_app_config.requires.append(str(tmp_path / "local/missing-2.3.4.tar.gz")) + first_app_config._requires.append(str(tmp_path / "local/missing-2.3.4.tar.gz")) # Install requirements with pytest.raises( diff --git a/tests/platforms/macOS/app/test_create.py b/tests/platforms/macOS/app/test_create.py index b7e286741..4b66ccfd2 100644 --- a/tests/platforms/macOS/app/test_create.py +++ b/tests/platforms/macOS/app/test_create.py @@ -303,7 +303,7 @@ def test_install_app_packages( bundle_path = tmp_path / "base_path/build/first-app/macos/app" create_command.tools.host_arch = host_arch - first_app_templated.requires = ["first", "second==1.2.3", "third>=3.2.1"] + first_app_templated._requires = ["first", "second==1.2.3", "third>=3.2.1"] # Mock the result of finding the binary packages - 2 of the packages are binary; # the version on the loosely specified package doesn't match the lower bound. @@ -428,7 +428,7 @@ def test_install_app_packages_no_binary( create_installed_package(bundle_path / f"app_packages.{other_arch}", "legacy") create_command.tools.host_arch = host_arch - first_app_templated.requires = ["first", "second==1.2.3", "third>=3.2.1"] + first_app_templated._requires = ["first", "second==1.2.3", "third>=3.2.1"] # Mock the result of finding no binary packages. create_command.find_binary_packages = mock.Mock(return_value=[]) @@ -504,7 +504,7 @@ def test_install_app_packages_failure(create_command, first_app_templated, tmp_p create_installed_package(bundle_path / "app_packages.x86_64", "legacy") create_command.tools.host_arch = "arm64" - first_app_templated.requires = ["first", "second==1.2.3", "third>=3.2.1"] + first_app_templated._requires = ["first", "second==1.2.3", "third>=3.2.1"] # Mock the result of finding the binary packages - 2 of the packages are binary; # the version on the loosely specified package doesn't match the lower bound. @@ -627,7 +627,7 @@ def test_install_app_packages_non_universal( bundle_path = tmp_path / "base_path/build/first-app/macos/app" create_command.tools.host_arch = host_arch - first_app_templated.requires = ["first", "second==1.2.3", "third>=3.2.1"] + first_app_templated._requires = ["first", "second==1.2.3", "third>=3.2.1"] first_app_templated.universal_build = False # Mock the find_binary_packages command so we can confirm it wasn't invoked.