pypa-hatch/hatch/cli/build/__init__.py

128 lines
4.4 KiB
Python

import click
@click.command(short_help='Build a project')
@click.argument('location', required=False)
@click.option(
'--target',
'-t',
'targets',
multiple=True,
help=(
'The target to build, overriding project defaults. '
'This may be selected multiple times e.g. `-t sdist -t wheel`'
),
)
@click.option(
'--clean',
'-c',
is_flag=True,
help=(
'Whether or not each build target should first remove any of its existing artifacts '
'[env var: `HATCH_BUILD_CLEAN`]'
),
)
@click.option(
'--hooks-only', is_flag=True, help='Whether or not to only execute build hooks [env var: `HATCH_BUILD_HOOKS_ONLY`]'
)
@click.option(
'--no-hooks', is_flag=True, help='Whether or not to disable build hooks [env var: `HATCH_BUILD_NO_HOOKS`]'
)
@click.option(
'--ext',
is_flag=True,
help=(
'Whether or not to only execute build hooks for distributing binary Python packages, such as '
'compiling extensions. Equivalent to `--hooks-only -t wheel`'
),
)
@click.option('--clean-only', is_flag=True, hidden=True)
@click.pass_obj
def build(app, location, targets, clean, hooks_only, no_hooks, ext, clean_only):
"""Build a project."""
import pickle
from hatchling.builders.plugin.interface import BuilderInterface
from ...utils.fs import Path
if app.project.metadata.build.build_backend != 'hatchling.build':
app.abort('Field `build-system.build-backend` must be set to `hatchling.build`')
backend_requirement = ''
for requirement in app.project.metadata.build.requires_complex:
if requirement.name == 'hatchling':
backend_requirement = str(requirement)
break
else:
app.abort('Field `build-system.requires` must specify `hatchling` as a requirement')
if len(app.project.metadata.build.requires) > 1:
app.abort('Field `build-system.requires` must only specify `hatchling` as a requirement')
if location:
path = str(Path(location).resolve())
else:
path = None
if ext:
hooks_only = True
targets = ('wheel',)
elif not targets:
targets = tuple(app.project.metadata.hatch.build_targets)
if not targets:
app.display_error('No targets defined in project configuration.')
app.display_error('Add one or more of the following build targets to pyproject.toml:\n')
builders = app.plugins.builder.collect(include_third_party=False)
for target_name in sorted(builders):
app.display_error(f'[tool.hatch.build.targets.{target_name}]')
app.abort()
with app.project.location.as_cwd():
environment = app.get_environment()
for i, target in enumerate(targets):
# Separate targets with a blank line
if not clean_only and i != 0:
app.display_info()
target_name, _, _ = target.partition(':')
builder = BuilderInterface(str(app.project.location))
builder.PLUGIN_NAME = target_name
dependencies = [backend_requirement]
dependencies.extend(builder.dependencies)
with app.status_waiting('Setting up build environment') as status:
with environment.build_environment(dependencies) as build_environment:
status.stop()
build_process = environment.get_build_process(
build_environment,
directory=path,
targets=(target,),
clean=clean,
hooks_only=hooks_only,
no_hooks=no_hooks,
clean_only=clean_only,
)
with build_process:
for line in app.platform.stream_process_output(build_process):
indicator, _, procedure = line.partition(':')
if indicator != '__HATCH__': # no cov
app.display_info(line, end='')
continue
method, args, kwargs = pickle.loads(bytes.fromhex(procedure.rstrip()))
if method == 'abort':
build_process.communicate()
getattr(app, method)(*args, **kwargs)
if build_process.returncode:
app.abort(code=build_process.returncode)