Skip to content

Commit

Permalink
Refactor builder bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek committed Dec 3, 2023
1 parent 52a81d8 commit 7d25d82
Show file tree
Hide file tree
Showing 19 changed files with 159 additions and 212 deletions.
87 changes: 11 additions & 76 deletions backend/src/hatchling/bridge/app.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,10 @@
from __future__ import annotations

import os
import pickle
import sys
from typing import Any


class InvokedApplication:
def __init__(self) -> None:
self.__verbosity = int(os.environ.get('HATCH_VERBOSE', '0')) - int(os.environ.get('HATCH_QUIET', '0'))

@property
def verbosity(self) -> int:
return self.__verbosity

@staticmethod
def display(*args: Any, **kwargs: Any) -> None:
send_app_command('display', *args, **kwargs)

@staticmethod
def display_info(*args: Any, **kwargs: Any) -> None:
send_app_command('display_info', *args, **kwargs)

@staticmethod
def display_waiting(*args: Any, **kwargs: Any) -> None:
send_app_command('display_waiting', *args, **kwargs)

@staticmethod
def display_success(*args: Any, **kwargs: Any) -> None:
send_app_command('display_success', *args, **kwargs)

@staticmethod
def display_warning(*args: Any, **kwargs: Any) -> None:
send_app_command('display_warning', *args, **kwargs)

@staticmethod
def display_error(*args: Any, **kwargs: Any) -> None:
send_app_command('display_error', *args, **kwargs)

@staticmethod
def display_debug(*args: Any, **kwargs: Any) -> None:
send_app_command('display_debug', *args, **kwargs)

@staticmethod
def display_mini_header(*args: Any, **kwargs: Any) -> None:
send_app_command('display_mini_header', *args, **kwargs)

@staticmethod
def abort(*args: Any, **kwargs: Any) -> None:
send_app_command('abort', *args, **kwargs)
sys.exit(kwargs.get('code', 1))

def get_safe_application(self) -> SafeApplication:
return SafeApplication(self)


class Application:
"""
The way output is displayed can be [configured](../config/hatch.md#terminal) by users.
Expand All @@ -77,42 +27,42 @@ def verbosity(self) -> int:
@staticmethod
def display(message: str = '', **kwargs: Any) -> None: # noqa: ARG004
# Do not document
print(message)
_display(message)

def display_info(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002
"""
Meant to be used for messages conveying basic information.
"""
if self.__verbosity >= 0:
print(message)
_display(message)

def display_waiting(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002
"""
Meant to be used for messages shown before potentially time consuming operations.
"""
if self.__verbosity >= 0:
print(message)
_display(message)

def display_success(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002
"""
Meant to be used for messages indicating some positive outcome.
"""
if self.__verbosity >= 0:
print(message)
_display(message)

def display_warning(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002
"""
Meant to be used for messages conveying important information.
"""
if self.__verbosity >= -1:
print(message)
_display(message)

def display_error(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002
"""
Meant to be used for messages indicating some unrecoverable error.
"""
if self.__verbosity >= -2: # noqa: PLR2004
print(message)
_display(message)

def display_debug(self, message: str = '', level: int = 1, **kwargs: Any) -> None: # noqa: ARG002
"""
Expand All @@ -124,18 +74,18 @@ def display_debug(self, message: str = '', level: int = 1, **kwargs: Any) -> Non
raise ValueError(error_message)

if self.__verbosity >= level:
print(message)
_display(message)

def display_mini_header(self, message: str = '', **kwargs: Any) -> None: # noqa: ARG002
if self.__verbosity >= 0:
print(f'[{message}]')
_display(f'[{message}]')

def abort(self, message: str = '', code: int = 1, **kwargs: Any) -> None: # noqa: ARG002
"""
Terminate the program with the given return code.
"""
if message and self.__verbosity >= -2: # noqa: PLR2004
print(message)
_display(message)

sys.exit(code)

Expand All @@ -144,7 +94,7 @@ def get_safe_application(self) -> SafeApplication:


class SafeApplication:
def __init__(self, app: InvokedApplication | Application) -> None:
def __init__(self, app: Application) -> None:
self.abort = app.abort
self.verbosity = app.verbosity
self.display = app.display
Expand All @@ -157,19 +107,4 @@ def __init__(self, app: InvokedApplication | Application) -> None:
self.display_mini_header = app.display_mini_header


def format_app_command(method: str, *args: Any, **kwargs: Any) -> str:
procedure = pickle.dumps((method, args, kwargs), 4)

return f"__HATCH__:{''.join('%02x' % i for i in procedure)}"


def get_application(*, called_by_app: bool) -> InvokedApplication | Application:
return InvokedApplication() if called_by_app else Application()


def send_app_command(method: str, *args: Any, **kwargs: Any) -> None:
_send_app_command(format_app_command(method, *args, **kwargs))


def _send_app_command(command: str) -> None:
print(command)
_display = print
8 changes: 4 additions & 4 deletions backend/src/hatchling/cli/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

def build_impl(
*,
called_by_app: bool,
called_by_app: bool, # noqa: ARG001
directory: str,
targets: list[str],
hooks_only: bool,
Expand All @@ -17,12 +17,12 @@ def build_impl(
) -> None:
import os

from hatchling.bridge.app import get_application
from hatchling.bridge.app import Application
from hatchling.builders.constants import BuildEnvVars
from hatchling.metadata.core import ProjectMetadata
from hatchling.plugin.manager import PluginManager

app = get_application(called_by_app=called_by_app)
app = Application()

if hooks_only and no_hooks:
app.abort('Cannot use both --hooks-only and --no-hooks together')
Expand Down Expand Up @@ -67,7 +67,7 @@ def build_impl(
builder_class = builders[target_name]

# Display name before instantiation in case of errors
if not clean_only:
if not clean_only and len(target_data) > 1:
app.display_mini_header(target_name)

builder = builder_class(root, plugin_manager=plugin_manager, metadata=metadata, app=app.get_safe_application())
Expand Down
11 changes: 8 additions & 3 deletions backend/src/hatchling/cli/metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@
from typing import Any


def metadata_impl(*, called_by_app: bool, field: str, compact: bool) -> None:
def metadata_impl(
*,
called_by_app: bool, # noqa: ARG001
field: str,
compact: bool,
) -> None:
import json
import os

from hatchling.bridge.app import get_application
from hatchling.bridge.app import Application
from hatchling.metadata.core import ProjectMetadata
from hatchling.metadata.utils import resolve_metadata_fields
from hatchling.plugin.manager import PluginManager

app = get_application(called_by_app=called_by_app)
app = Application()

root = os.getcwd()
plugin_manager = PluginManager()
Expand Down
10 changes: 7 additions & 3 deletions backend/src/hatchling/cli/version/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
from typing import Any


def version_impl(*, called_by_app: bool, desired_version: str) -> None:
def version_impl(
*,
called_by_app: bool, # noqa: ARG001
desired_version: str,
) -> None:
import os

from hatchling.bridge.app import get_application
from hatchling.bridge.app import Application
from hatchling.metadata.core import ProjectMetadata
from hatchling.plugin.manager import PluginManager

app = get_application(called_by_app=called_by_app)
app = Application()

root = os.getcwd()
plugin_manager = PluginManager()
Expand Down
3 changes: 3 additions & 0 deletions docs/history/hatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
***Changed:***

- Drop support for Python 3.7
- The `get_build_process` method of the `environment` interface has been removed; plugins should use the new `run_builder` method instead
- Remove `pyperclip` dependency and the `--copy` flag of the `config find` command
- When running the `build` command all output from builders is now displayed as-is real time without the stripping of ANSI codes
- Version information (for Hatch itself) is now derived from Git

***Added:***
Expand All @@ -26,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- The `build` command now supports backends other than Hatchling
- Allow the use of `features` for environments when `skip-install` is enabled
- The default is now `__TOKEN__` when prompting for a username for the `publish` command
- Added a new `run_builder` method to the `environment` interface
- Bump the minimum supported version of Hatchling to 1.17.1
- Bump the minimum supported version of `click` to 8.0.6

Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/environment/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ All environment types should [offer support](#hatch.env.plugin.interface.Environ
- dependency_hash
- build_environment
- build_environment_exists
- get_build_process
- run_builder
- construct_build_command
- command_context
- enter_shell
Expand Down
39 changes: 0 additions & 39 deletions src/hatch/cli/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,44 +173,6 @@ def run_shell_commands(
if first_error_code and force_continue:
self.abort(code=first_error_code)

def attach_builder(self, process):
import pickle

with process:
for line in self.platform.stream_process_output(process):
indicator, _, procedure = line.partition(':')
if indicator != '__HATCH__': # no cov
self.display_info(line, end='')
continue

method, args, kwargs = pickle.loads(bytes.fromhex(procedure.rstrip())) # noqa: S301
if method == 'abort':
process.communicate()

getattr(self, method)(*args, **kwargs)

if process.returncode:
self.abort(code=process.returncode)

def read_builder(self, process):
import pickle

lines = []
with process:
for line in self.platform.stream_process_output(process):
indicator, _, procedure = line.partition(':')
if indicator != '__HATCH__': # no cov
lines.append(line)
else:
_, args, _ = pickle.loads(bytes.fromhex(procedure)) # noqa: S301
lines.append(args[0])

output = ''.join(lines)
if process.returncode:
self.abort(output, code=process.returncode)

return output

def ensure_environment_plugin_dependencies(self) -> None:
self.ensure_plugin_dependencies(
self.project.config.env_requires_complex, wait_message='Syncing environment plugin requirements'
Expand Down Expand Up @@ -308,7 +270,6 @@ def __init__(self, app: Application):
self.confirm = app.confirm
self.status = app.status
self.status_if = app.status_if
self.read_builder = app.read_builder


class EnvironmentMetadata:
Expand Down
20 changes: 13 additions & 7 deletions src/hatch/cli/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def build(app: Application, location, targets, hooks_only, no_hooks, ext, clean,
from hatchling.builders.constants import BuildEnvVars
from hatchling.builders.plugin.interface import BuilderInterface

from hatch.config.constants import AppEnvVars
from hatch.utils.fs import Path
from hatch.utils.structures import EnvVars

Expand Down Expand Up @@ -86,6 +87,11 @@ def build(app: Application, location, targets, hooks_only, no_hooks, ext, clean,
if no_hooks:
env_vars[BuildEnvVars.NO_HOOKS] = 'true'

if app.verbose:
env_vars[AppEnvVars.VERBOSE] = str(app.verbosity)
elif app.quiet:
env_vars[AppEnvVars.QUIET] = str(abs(app.verbosity))

class Builder(BuilderInterface):
def get_version_api(self):
return {}
Expand All @@ -97,15 +103,14 @@ def get_version_api(self):
except Exception as e:
app.abort(f'Environment `{environment.name}` is incompatible: {e}')

for i, target in enumerate(targets):
# Separate targets with a blank line
if not clean_only and i != 0:
app.display_info()

for target in targets:
target_name, _, _ = target.partition(':')
builder = Builder(str(app.project.location))
builder.PLUGIN_NAME = target_name

if not clean_only:
app.display_header(target_name)

dependencies = list(app.project.metadata.build.requires)
with environment.get_env_vars(), EnvVars(env_vars):
dependencies.extend(builder.config.dependencies)
Expand All @@ -115,7 +120,7 @@ def get_version_api(self):
) as status, environment.build_environment(dependencies) as build_environment:
status.stop()

process = environment.get_build_process(
process = environment.run_builder(
build_environment,
directory=path,
targets=(target,),
Expand All @@ -125,4 +130,5 @@ def get_version_api(self):
clean_hooks_after=clean_hooks_after,
clean_only=clean_only,
)
app.attach_builder(process)
if process.returncode:
app.abort(code=process.returncode)
6 changes: 3 additions & 3 deletions src/hatch/cli/project/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def metadata(app, field):
) as status, environment.build_environment(app.project.metadata.build.requires):
status.stop()

command = ['python', '-u', '-m', 'hatchling', 'metadata', '--app', '--compact']
process = app.platform.capture_process(command)
project_metadata = json.loads(app.read_builder(process))
command = ['python', '-u', '-m', 'hatchling', 'metadata', '--compact']
output = app.platform.check_command_output(command)
project_metadata = json.loads(output)

if field:
if field not in project_metadata:
Expand Down
Loading

0 comments on commit 7d25d82

Please sign in to comment.