pypa-hatch/tests/cli/build/test_build.py

1403 lines
49 KiB
Python

import os
import re
import pytest
from hatch.config.constants import ConfigEnvVars
from hatch.project.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE, BuildEnvVars
from hatch.project.core import Project
pytestmark = [pytest.mark.usefixtures('mock_backend_process')]
@pytest.mark.requires_internet
class TestOtherBackend:
def test_standard(self, hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
data_path = temp_dir / 'data'
data_path.mkdir()
project = Project(path)
config = dict(project.raw_config)
config['build-system']['requires'] = ['flit-core']
config['build-system']['build-backend'] = 'flit_core.buildapi'
config['project']['version'] = '0.0.1'
config['project']['dynamic'] = []
del config['project']['license']
project.save_config(config)
build_directory = path / 'dist'
assert not build_directory.is_dir()
with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build')
assert result.exit_code == 0, result.output
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
wheel_path = build_directory / 'my_app-0.0.1-py3-none-any.whl'
assert wheel_path.is_file()
sdist_path = build_directory / 'my_app-0.0.1.tar.gz'
assert sdist_path.is_file()
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
build_directory.remove()
with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build', '-t', 'wheel')
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
f"""
Inspecting build dependencies
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
assert build_directory.is_dir()
assert wheel_path.is_file()
assert not sdist_path.is_file()
build_directory.remove()
with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build', '-t', 'sdist')
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
f"""
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
"""
)
assert build_directory.is_dir()
assert not wheel_path.is_file()
assert sdist_path.is_file()
def test_legacy(self, hatch, temp_dir, helpers):
path = temp_dir / 'tmp'
path.mkdir()
data_path = temp_dir / 'data'
data_path.mkdir()
(path / 'pyproject.toml').write_text(
"""\
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
"""
)
(path / 'setup.py').write_text(
"""\
import setuptools
setuptools.setup(name="tmp", version="0.0.1")
"""
)
(path / 'tmp.py').write_text(
"""\
print("Hello World!")
"""
)
(path / 'README.md').touch()
build_directory = path / 'dist'
assert not build_directory.is_dir()
with path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build')
assert result.exit_code == 0, result.output
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
wheel_path = build_directory / 'tmp-0.0.1-py3-none-any.whl'
assert wheel_path.is_file()
sdist_path = build_directory / 'tmp-0.0.1.tar.gz'
assert sdist_path.is_file()
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
@pytest.mark.allow_backend_process
def test_incompatible_environment(hatch, temp_dir, helpers, build_env_config):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
project = Project(path)
helpers.update_project_environment(project, 'hatch-build', {'python': '9000', **build_env_config})
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 1, result.output
assert result.output == helpers.dedent(
"""
Environment `hatch-build` is incompatible: cannot locate Python: 9000
"""
)
@pytest.mark.allow_backend_process
@pytest.mark.requires_internet
def test_no_compatibility_check_if_exists(hatch, temp_dir, helpers, mocker):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
project_path = temp_dir / 'my-app'
data_path = temp_dir / 'data'
data_path.mkdir()
with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = project_path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
──────────────────────────────────── wheel ─────────────────────────────────────
"""
)
build_directory.remove()
mocker.patch('hatch.env.virtual.VirtualEnvironment.check_compatibility', side_effect=Exception('incompatible'))
with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build')
assert result.exit_code == 0, result.output
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
"""
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
──────────────────────────────────── wheel ─────────────────────────────────────
"""
)
def test_unknown_targets(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
with path.as_cwd():
result = hatch('build', '-t', 'foo')
assert result.exit_code == 1, result.output
assert result.output == helpers.dedent(
"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
───────────────────────────────────── foo ──────────────────────────────────────
Unknown build targets: foo
"""
)
def test_mutually_exclusive_hook_options(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
with path.as_cwd():
result = hatch('build', '--hooks-only', '--no-hooks')
assert result.exit_code == 1, result.output
assert result.output == helpers.dedent(
"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
Cannot use both --hooks-only and --no-hooks together
"""
)
def test_default(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_explicit_targets(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
with path.as_cwd():
result = hatch('build', '-t', 'wheel')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 1
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_explicit_directory(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_directory = temp_dir / 'dist'
with path.as_cwd():
result = hatch('build', str(build_directory))
assert result.exit_code == 0, result.output
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path}
"""
)
def test_explicit_directory_env_var(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_directory = temp_dir / 'dist'
with path.as_cwd({BuildEnvVars.LOCATION: str(build_directory)}):
result = hatch('build')
assert result.exit_code == 0, result.output
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path}
"""
)
def test_clean(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def clean(self, versions):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').unlink()
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
result = hatch('version', 'minor')
assert result.exit_code == 0, result.output
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
assert (path / 'my_app' / 'lib.so').is_file()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 4
test_file = build_directory / 'test.txt'
test_file.touch()
with path.as_cwd():
result = hatch('version', '9000')
assert result.exit_code == 0, result.output
result = hatch('build', '-c')
assert result.exit_code == 0, result.output
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 3
assert test_file in artifacts
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
assert '9000' in str(sdist_path)
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert '9000' in str(wheel_path)
assert result.output == helpers.dedent(
f"""
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_clean_env_var(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
result = hatch('version', 'minor')
assert result.exit_code == 0, result.output
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 4
test_file = build_directory / 'test.txt'
test_file.touch()
with path.as_cwd({BuildEnvVars.CLEAN: 'true'}):
result = hatch('version', '9000')
assert result.exit_code == 0, result.output
result = hatch('build')
assert result.exit_code == 0, result.output
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 3
assert test_file in artifacts
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
assert '9000' in str(sdist_path)
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert '9000' in str(wheel_path)
assert result.output == helpers.dedent(
f"""
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_clean_only(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def clean(self, versions):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').unlink()
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
build_artifact = path / 'my_app' / 'lib.so'
assert build_artifact.is_file()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
with path.as_cwd():
result = hatch('version', 'minor')
assert result.exit_code == 0, result.output
result = hatch('build', '--clean-only')
assert result.exit_code == 0, result.output
artifacts = list(build_directory.iterdir())
assert not artifacts
assert not build_artifact.exists()
assert result.output == helpers.dedent(
"""
Inspecting build dependencies
"""
)
def test_clean_only_hooks_only(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def clean(self, versions):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').unlink()
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
build_artifact = path / 'my_app' / 'lib.so'
assert build_artifact.is_file()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
with path.as_cwd():
result = hatch('version', 'minor')
assert result.exit_code == 0, result.output
result = hatch('build', '--clean-only', '--hooks-only')
assert result.exit_code == 0, result.output
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
assert not build_artifact.exists()
assert result.output == helpers.dedent(
"""
Inspecting build dependencies
"""
)
def test_clean_hooks_after(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def clean(self, versions):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').unlink()
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('build', '--clean-hooks-after')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
build_artifact = path / 'my_app' / 'lib.so'
assert not build_artifact.exists()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_clean_hooks_after_env_var(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def clean(self, versions):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').unlink()
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd({BuildEnvVars.CLEAN_HOOKS_AFTER: 'true'}):
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
build_artifact = path / 'my_app' / 'lib.so'
assert not build_artifact.exists()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_clean_only_no_hooks(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def clean(self, versions):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').unlink()
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
build_artifact = path / 'my_app' / 'lib.so'
assert build_artifact.is_file()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
with path.as_cwd():
result = hatch('version', 'minor')
assert result.exit_code == 0, result.output
result = hatch('build', '--clean-only', '--no-hooks')
assert result.exit_code == 0, result.output
artifacts = list(build_directory.iterdir())
assert not artifacts
assert build_artifact.is_file()
assert result.output == helpers.dedent(
"""
Inspecting build dependencies
"""
)
def test_hooks_only(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('-v', 'build', '-t', 'wheel', '--hooks-only')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 0
assert (path / 'my_app' / 'lib.so').is_file()
helpers.assert_output_match(
result.output,
r"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
cmd \[1\] \| python -u .+
──────────────────────────────────── wheel ─────────────────────────────────────
cmd \[1\] \| python -u -m hatchling build --target wheel --hooks-only
Building `wheel` version `standard`
Only ran build hooks for `wheel` version `standard`
""",
)
def test_hooks_only_env_var(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd({BuildEnvVars.HOOKS_ONLY: 'true'}):
result = hatch('-v', 'build', '-t', 'wheel')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 0
assert (path / 'my_app' / 'lib.so').is_file()
helpers.assert_output_match(
result.output,
r"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
cmd \[1\] \| python -u .+
──────────────────────────────────── wheel ─────────────────────────────────────
cmd \[1\] \| python -u -m hatchling build --target wheel --hooks-only
Building `wheel` version `standard`
Only ran build hooks for `wheel` version `standard`
""",
)
def test_extensions_only(hatch, temp_dir, helpers, config_file):
config_file.model.template.plugins['default']['src-layout'] = False
config_file.save()
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('-v', 'build', '--ext')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 0
assert (path / 'my_app' / 'lib.so').is_file()
helpers.assert_output_match(
result.output,
r"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
cmd \[1\] \| python -u .+
──────────────────────────────────── wheel ─────────────────────────────────────
cmd \[1\] \| python -u -m hatchling build --target wheel --hooks-only
Building `wheel` version `standard`
Only ran build hooks for `wheel` version `standard`
""",
)
def test_no_hooks(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd():
result = hatch('build', '-t', 'wheel', '--no-hooks')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 1
assert not (path / 'my_app' / 'lib.so').exists()
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_no_hooks_env_var(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
build_script = path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomHook(BuildHookInterface):
def initialize(self, version, build_data):
if self.target_name == 'wheel':
pathlib.Path('my_app', 'lib.so').touch()
"""
)
)
project = Project(path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {'hooks': {'custom': {'path': build_script.name}}}
project.save_config(config)
with path.as_cwd({BuildEnvVars.NO_HOOKS: 'true'}):
result = hatch('build', '-t', 'wheel')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 1
assert not (path / 'my_app' / 'lib.so').exists()
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
def test_debug_verbosity(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
with path.as_cwd():
result = hatch('-v', 'build', '-t', 'wheel:standard')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 1
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
helpers.assert_output_match(
result.output,
rf"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── wheel ─────────────────────────────────────
cmd \[1\] \| python -u -m hatchling build --target wheel:standard
Building `wheel` version `standard`
{re.escape(str(wheel_path.relative_to(path)))}
""",
)
@pytest.mark.allow_backend_process
@pytest.mark.requires_internet
def test_shipped(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
project_path = temp_dir / 'my-app'
data_path = temp_dir / 'data'
data_path.mkdir()
with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build')
assert result.exit_code == 0, result.output
env_data_path = data_path / 'env' / 'virtual'
assert env_data_path.is_dir()
project_data_path = env_data_path / project_path.name
assert project_data_path.is_dir()
storage_dirs = list(project_data_path.iterdir())
assert len(storage_dirs) == 1
storage_path = storage_dirs[0]
assert len(storage_path.name) == 8
env_dirs = list(storage_path.iterdir())
assert len(env_dirs) == 1
env_path = env_dirs[0]
assert env_path.name == 'hatch-build'
build_directory = project_path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
assert result.output == helpers.dedent(
"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
──────────────────────────────────── wheel ─────────────────────────────────────
"""
)
# Test removal while we're here
with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('env', 'remove', 'hatch-build')
assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
"""
Removing environment: hatch-build
"""
)
assert not storage_path.is_dir()
@pytest.mark.allow_backend_process
@pytest.mark.requires_internet
def test_build_dependencies(hatch, temp_dir, helpers):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
project_path = temp_dir / 'my-app'
data_path = temp_dir / 'data'
data_path.mkdir()
build_script = project_path / DEFAULT_BUILD_SCRIPT
build_script.write_text(
helpers.dedent(
"""
import pathlib
import binary
from hatchling.builders.wheel import WheelBuilder
def get_builder():
return CustomWheelBuilder
class CustomWheelBuilder(WheelBuilder):
def build(self, **kwargs):
pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))
yield from super().build(**kwargs)
"""
)
)
project = Project(project_path)
config = dict(project.raw_config)
config['tool']['hatch']['build'] = {
'targets': {'custom': {'dependencies': ['binary'], 'path': DEFAULT_BUILD_SCRIPT}},
}
project.save_config(config)
with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
result = hatch('build', '-t', 'custom')
assert result.exit_code == 0, result.output
build_directory = project_path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 1
output_file = project_path / 'test.txt'
assert output_file.is_file()
assert str(output_file.read_text()) == "(1.0, 'KiB')"
assert result.output == helpers.dedent(
"""
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
Syncing dependencies
──────────────────────────────────── custom ────────────────────────────────────
"""
)
def test_plugin_dependencies_unmet(hatch, temp_dir, helpers, mock_plugin_installation):
project_name = 'My.App'
with temp_dir.as_cwd():
result = hatch('new', project_name)
assert result.exit_code == 0, result.output
path = temp_dir / 'my-app'
dependency = os.urandom(16).hex()
(path / DEFAULT_CONFIG_FILE).write_text(
helpers.dedent(
f"""
[env]
requires = ["{dependency}"]
"""
)
)
with path.as_cwd():
result = hatch('build')
assert result.exit_code == 0, result.output
build_directory = path / 'dist'
assert build_directory.is_dir()
artifacts = list(build_directory.iterdir())
assert len(artifacts) == 2
sdist_path = next(artifact for artifact in artifacts if artifact.name.endswith('.tar.gz'))
wheel_path = next(artifact for artifact in artifacts if artifact.name.endswith('.whl'))
assert result.output == helpers.dedent(
f"""
Syncing environment plugin requirements
Creating environment: hatch-build
Checking dependencies
Syncing dependencies
Inspecting build dependencies
──────────────────────────────────── sdist ─────────────────────────────────────
{sdist_path.relative_to(path)}
──────────────────────────────────── wheel ─────────────────────────────────────
{wheel_path.relative_to(path)}
"""
)
helpers.assert_plugin_installation(mock_plugin_installation, [dependency])