/
niceSOFT
/
python3-hatchling
Обзор
Документация
Войти
/
niceSOFT
/
python3-hatchling
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/cli/build/test_build.py
1 562 строки
54 KB
Cary Hawkins
Add --all flag to hatch build for building all workspace members (#2352)
10 авг 2026, 04:06
Не верифицирован
10 авг 2026, 04:06
ab3e000
Код
Авторство
О чём код?
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 ───────────────────────────────────── """ ) @pytest.mark.requires_internet 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 """ ) @pytest.mark.requires_internet 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 """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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} """ ) @pytest.mark.requires_internet 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} """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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 """ ) @pytest.mark.requires_internet 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 """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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 """ ) @pytest.mark.requires_internet 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` """, ) @pytest.mark.requires_internet 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` """, ) @pytest.mark.requires_internet 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` """, ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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)} """ ) @pytest.mark.requires_internet 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 ──────────────────────────────────── """ ) @pytest.mark.requires_internet 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]) class TestBuildAll: def _create_workspace(self, hatch, temp_dir): with temp_dir.as_cwd(): result = hatch("new", "workspace-root") assert result.exit_code == 0, result.output workspace_root = temp_dir / "workspace-root" with (workspace_root / "pyproject.toml").open("a") as f: f.write( """ [tool.hatch.envs.default] workspace.members = ["packages/*"] """ ) packages_dir = workspace_root / "packages" packages_dir.mkdir() with packages_dir.as_cwd(): for project_name in ("member1", "member2"): result = hatch("new", project_name) assert result.exit_code == 0, result.output return workspace_root def test_no_workspace_members(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" with path.as_cwd(): result = hatch("build", "--all") assert result.exit_code == 1, result.output assert result.output == helpers.dedent( """ The `--all` flag requires workspace members to be defined in field `tool.hatch.envs.default.workspace.members` """ ) def test_default(self, hatch, temp_dir): workspace_root = self._create_workspace(hatch, temp_dir) with workspace_root.as_cwd(): result = hatch("build", "--all") assert result.exit_code == 0, result.output build_directory = workspace_root / "dist" assert build_directory.is_dir() artifacts = sorted(artifact.name for artifact in build_directory.iterdir()) assert artifacts == [ "member1-0.0.1-py3-none-any.whl", "member1-0.0.1.tar.gz", "member2-0.0.1-py3-none-any.whl", "member2-0.0.1.tar.gz", "workspace_root-0.0.1-py3-none-any.whl", "workspace_root-0.0.1.tar.gz", ] for project_name in ("workspace-root", "member1", "member2"): assert f" {project_name} " in result.output for member_name in ("member1", "member2"): assert not (workspace_root / "packages" / member_name / "dist").is_dir() def test_virtual_root(self, hatch, temp_dir): workspace_root = temp_dir / "workspace" workspace_root.mkdir() (workspace_root / "pyproject.toml").write_text( """\ [tool.hatch.envs.default] workspace.members = ["packages/*"] """ ) packages_dir = workspace_root / "packages" packages_dir.mkdir() with packages_dir.as_cwd(): for project_name in ("member1", "member2"): result = hatch("new", project_name) assert result.exit_code == 0, result.output with workspace_root.as_cwd(): result = hatch("build", "--all") assert result.exit_code == 0, result.output build_directory = workspace_root / "dist" assert build_directory.is_dir() artifacts = sorted(artifact.name for artifact in build_directory.iterdir()) assert artifacts == [ "member1-0.0.1-py3-none-any.whl", "member1-0.0.1.tar.gz", "member2-0.0.1-py3-none-any.whl", "member2-0.0.1.tar.gz", ] def test_explicit_targets(self, hatch, temp_dir): workspace_root = self._create_workspace(hatch, temp_dir) with workspace_root.as_cwd(): result = hatch("build", "--all", "-t", "wheel") assert result.exit_code == 0, result.output build_directory = workspace_root / "dist" assert build_directory.is_dir() artifacts = sorted(artifact.name for artifact in build_directory.iterdir()) assert artifacts == [ "member1-0.0.1-py3-none-any.whl", "member2-0.0.1-py3-none-any.whl", "workspace_root-0.0.1-py3-none-any.whl", ] def test_explicit_directory(self, hatch, temp_dir): workspace_root = self._create_workspace(hatch, temp_dir) build_directory = temp_dir / "artifacts" with workspace_root.as_cwd(): result = hatch("build", "--all", str(build_directory)) assert result.exit_code == 0, result.output assert build_directory.is_dir() artifacts = sorted(artifact.name for artifact in build_directory.iterdir()) assert artifacts == [ "member1-0.0.1-py3-none-any.whl", "member1-0.0.1.tar.gz", "member2-0.0.1-py3-none-any.whl", "member2-0.0.1.tar.gz", "workspace_root-0.0.1-py3-none-any.whl", "workspace_root-0.0.1.tar.gz", ] assert not (workspace_root / "dist").is_dir()