diff --git a/hatch_build_scripts/__init__.py b/hatch_build_scripts/__init__.py index 27fdca4..81f0fde 100644 --- a/hatch_build_scripts/__init__.py +++ b/hatch_build_scripts/__init__.py @@ -1 +1 @@ -__version__ = "0.0.3" +__version__ = "0.0.4" diff --git a/hatch_build_scripts/plugin.py b/hatch_build_scripts/plugin.py index 89f51b0..7a8512e 100644 --- a/hatch_build_scripts/plugin.py +++ b/hatch_build_scripts/plugin.py @@ -3,16 +3,19 @@ import logging import os import shutil -from dataclasses import MISSING, dataclass, fields +from collections.abc import Sequence +from dataclasses import MISSING, asdict, dataclass, fields from functools import cached_property from pathlib import Path from subprocess import run -from typing import Any, Sequence +from typing import Any import pathspec from hatchling.builders.hooks.plugin.interface import BuildHookInterface -logger = logging.getLogger(__name__) +log = logging.getLogger(__name__) +log_level = logging.getLevelName(os.getenv("HATCH_BUILD_SCRIPTS_LOG_LEVEL", "INFO")) +log.setLevel(log_level) class BuildScriptsHook(BuildHookInterface): @@ -30,28 +33,34 @@ def initialize( for script in all_scripts: if script.clean_out_dir: out_dir = Path(self.root, script.out_dir) - logger.info(f"Cleaning {out_dir}") + log.debug(f"Cleaning {out_dir}") shutil.rmtree(out_dir, ignore_errors=True) elif script.clean_artifacts: for out_file in script.out_files(self.root): + log.debug(f"Cleaning {out_file}") out_file.unlink(missing_ok=True) for script in all_scripts: + log.debug(f"Script config: {asdict(script)}") work_dir = Path(self.root, script.work_dir) out_dir = Path(self.root, script.out_dir) out_dir.mkdir(parents=True, exist_ok=True) for cmd in script.commands: + log.info(f"Running command: {cmd}") run(cmd, cwd=str(work_dir), check=True, shell=True) # noqa: S602 - logger.info(f"Copying artifacts to {out_dir}") - for artifact_file in script.artifact_files(): - src_file = work_dir / artifact_file - out_file = out_dir / artifact_file + log.info(f"Copying artifacts to {out_dir}") + for work_file in script.work_files(self.root, relative=True): + src_file = work_dir / work_file + out_file = out_dir / work_file + log.debug(f"Copying {src_file} to {out_file}") if src_file not in created: out_file.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(src_file, out_file) created.add(out_file) + else: + log.debug(f"Skipping {src_file} - already exists") build_data["artifacts"].append(str(out_dir.relative_to(self.root))) @@ -91,27 +100,32 @@ def __post_init__(self) -> None: self.out_dir = conv_path(self.out_dir) self.work_dir = conv_path(self.work_dir) - def work_files(self, root: str | Path) -> Sequence[Path]: - """Get the files that will be used by the script.""" - work_dir = Path(root, self.work_dir) - if not work_dir.exists(): + def work_files(self, root: str | Path, *, relative: bool = False) -> Sequence[Path]: + """Get files in the work directory that match the artifacts spec.""" + abs_dir = Path(root, self.work_dir) + if not abs_dir.exists(): return [] - return [Path(root, self.work_dir, f) for f in self.artifacts_spec.match_tree(work_dir)] - - def out_files(self, root: str | Path) -> Sequence[Path]: - """Get the files that will be created by the script.""" - out_dir = Path(root, self.out_dir) - if not out_dir.exists(): + return [ + Path(f) if relative else abs_dir / f + for f in self.artifacts_spec.match_tree(abs_dir) + ] + + def out_files(self, root: str | Path, *, relative: bool = False) -> Sequence[Path]: + """Get files in the output directory that match the artifacts spec.""" + abs_dir = Path(root, self.out_dir) + if not abs_dir.exists(): return [] - return [Path(root, self.out_dir, f) for f in self.artifacts_spec.match_tree(out_dir)] - - def artifact_files(self) -> Sequence[Path]: - return [Path(conv_path(p)) for p in self.artifacts_spec.match_tree(self.work_dir)] + return [ + Path(f) if relative else abs_dir / f + for f in self.artifacts_spec.match_tree(abs_dir) + ] @cached_property def artifacts_spec(self) -> pathspec.PathSpec: """A pathspec for the artifacts.""" - return pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, self.artifacts) + return pathspec.PathSpec.from_lines( + pathspec.patterns.GitWildMatchPattern, self.artifacts + ) def dataclass_defaults(obj: Any) -> dict[str, Any]: diff --git a/pyproject.toml b/pyproject.toml index a5a3971..7725e48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ style = [ "ruff {args:.}", "black --check --diff {args:.}", ] -fmt = [ +fix = [ "black {args:.}", "ruff --fix {args:.}", "style", @@ -71,13 +71,11 @@ all = [ ] [tool.black] -target-version = ["py37"] -line-length = 100 +target-version = ["py39"] skip-string-normalization = true [tool.ruff] -target-version = "py37" -line-length = 100 +target-version = "py39" select = [ "A", "ARG", @@ -128,4 +126,4 @@ ban-relative-imports = "all" [tool.ruff.per-file-ignores] # Tests can use magic values, assertions, and relative imports -"tests/**/*" = ["PLR2004", "S101", "TID252"] +"tests/**/*" = ["PLR2004", "S101", "TID252", "S603"] diff --git a/tests/test_plugin.py b/tests/test_plugin.py index baea927..deafd9d 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -4,23 +4,26 @@ def test_plugin(tmpdir): - (tmpdir / "some-dir").mkdir() - (tmpdir / "another-dir").mkdir() + tmp_lib_dir = tmpdir / "lib" + tmp_lib_dir.mkdir() - some_dir_out = tmpdir / "some-dir-out" + (tmp_lib_dir / "some-dir").mkdir() + (tmp_lib_dir / "another-dir").mkdir() + + some_dir_out = tmp_lib_dir / "some-dir-out" some_dir_out.mkdir() # we expect that this file will not be cleaned (some_dir_out / "module.py").write_text('print("hello")', "utf-8") # we expect that this file will be cleaned (some_dir_out / "f3.txt").write_text("this should be cleaned", "utf-8") - another_dir_out = tmpdir / "another-dir-out" + another_dir_out = tmp_lib_dir / "another-dir-out" another_dir_out.mkdir() # we expect that this file will be cleaned (another_dir_out / "module.py").write_text('print("hello")', "utf-8") proj = create_project( - tmpdir, + tmp_lib_dir, [ OneScriptConfig( out_dir="fake", @@ -52,10 +55,14 @@ def test_plugin(tmpdir): proj.build() + extract_dir = tmpdir / "extract" + extract_dir.mkdir() + with proj.dist() as dist: - files = {file.filename for file in dist.filelist} + dist.extractall(extract_dir) + + assert (extract_dir / "fake" / "fake.txt").exists() + assert (extract_dir / "some-dir-out" / "module.py").exists() - assert "fake/fake.txt" in files - assert "some-dir-out/module.py" in files - assert "another-dir-out/module.py" not in files - assert "some-dir-out/f3.txt" not in files + assert not (extract_dir / "some-dir-out" / "f3.txt").exists() + assert not (extract_dir / "another-dir-out" / "module.py").exists() diff --git a/tests/utils.py b/tests/utils.py index 5ee5dd8..15abd39 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,10 +3,10 @@ import subprocess import sys import zipfile +from collections.abc import Iterator, Sequence from contextlib import contextmanager from dataclasses import asdict from pathlib import Path -from typing import Iterator, Sequence import toml @@ -53,7 +53,7 @@ def __init__(self, path: Path) -> None: def build(self) -> None: subprocess.run( - [ # noqa: S603 + [ sys.executable, "-m", "pip", @@ -65,7 +65,9 @@ def build(self) -> None: check=True, ) subprocess.run( - [sys.executable, "-m", "hatch", "build"], cwd=self.path, check=True # noqa: S603 + [sys.executable, "-m", "hatch", "build"], + cwd=self.path, + check=True, ) @contextmanager