Skip to content

Commit

Permalink
feat(poly build): setup and teardown for building, when using Maturin…
Browse files Browse the repository at this point in the history
… or tools without hook or plugin support (#299)

* feat(poly build): build command for Package & Dependency Management tools without support for plugins or build hooks

* add support for passing in the directory

* add building.paths unit test

* bump hatch hook to 1.3.2

* bump PDM workspace and brick hooks to 1.1.2

* bump CLI to 1.24.0

* bump dev and cli lock files
  • Loading branch information
DavidVujic authored Dec 8, 2024
1 parent 5c3d8ca commit 71e36b9
Show file tree
Hide file tree
Showing 17 changed files with 619 additions and 337 deletions.
89 changes: 89 additions & 0 deletions bases/polylith/cli/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from pathlib import Path

import tomlkit
from polylith import building, repo, toml
from polylith.cli import options
from typer import Exit, Typer
from typing_extensions import Annotated

app = Typer()


def get_work_dir(root: Path, directory: str) -> Path:
work_dir = building.get_work_dir({})
work_path = Path(directory) / work_dir if directory else work_dir

return root / work_path


def get_build_dir(root: Path, directory: str) -> Path:
return root / Path(directory) if directory else root


def get_project_data(build_dir: Path) -> tomlkit.TOMLDocument:
fullpath = build_dir / repo.default_toml

if not fullpath.exists():
raise Exit(code=1)

return toml.read_toml_document(fullpath)


@app.command("setup")
def setup_command(directory: Annotated[str, options.directory] = ""):
"""Prepare a project before building a wheel or a source distribution (sdist).
Run it before the build command of your Package & Dependency Management tool.
"""
root = Path.cwd()
build_dir = get_build_dir(root, directory)
print(f"Build directory: {build_dir}")

data = get_project_data(build_dir)
bricks = toml.get_project_packages_from_polylith_section(data)

if not bricks:
print("No bricks found.")
return

bricks_with_paths = {build_dir / k: v for k, v in bricks.items()}
custom_top_ns = toml.get_custom_top_namespace_from_polylith_section(data)

if not custom_top_ns:
building.copy_bricks_as_is(bricks_with_paths, build_dir)
else:
work_dir = get_work_dir(root, directory)
print(f"Using temporary working directory: {work_dir}")

rewritten = building.copy_and_rewrite_bricks(
bricks_with_paths, custom_top_ns, work_dir, build_dir
)

for item in rewritten:
print(f"Updated {item} with new top namespace for local imports.")


@app.command("teardown")
def teardown_command(directory: Annotated[str, options.directory] = ""):
"""Clean up temporary directories. Run it after the build command of your Package & Dependency Management tool."""
root = Path.cwd()

work_dir = get_work_dir(root, directory)
build_dir = get_build_dir(root, directory)

data = get_project_data(build_dir)
bricks = toml.get_project_packages_from_polylith_section(data)

if not bricks:
return

destination_dir = building.calculate_destination_dir(data)

if work_dir.exists():
print(f"Removing temporary working directory: {work_dir}")
building.cleanup(work_dir)

if destination_dir:
destination_path = build_dir / destination_dir
print(f"Removing bricks path used during build: {destination_path}")
building.cleanup(destination_path)
9 changes: 8 additions & 1 deletion bases/polylith/cli/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import List, Union

from polylith import commands, configuration, info, repo
from polylith.cli import create, options
from polylith.cli import build, create, options
from typer import Exit, Option, Typer
from typing_extensions import Annotated

Expand All @@ -15,6 +15,13 @@
)


app.add_typer(
build.app,
name="build",
help="For Package & Dependency Management tools without support for plugins or build hooks.",
)


def filtered_projects_data(
projects_data: List[dict], directory: Union[str, None]
) -> List[dict]:
Expand Down
10 changes: 10 additions & 0 deletions components/polylith/building/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from polylith.building.core import cleanup, copy_and_rewrite_bricks, copy_bricks_as_is
from polylith.building.paths import calculate_destination_dir, get_work_dir

__all__ = [
"calculate_destination_dir",
"cleanup",
"copy_and_rewrite_bricks",
"copy_bricks_as_is",
"get_work_dir",
]
47 changes: 47 additions & 0 deletions components/polylith/building/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import operator
import shutil
from functools import reduce
from pathlib import Path
from typing import List

from polylith import parsing


def copy_bricks_as_is(bricks: dict, build_dir: Path) -> None:
for source, brick in bricks.items():
parsing.copy_brick(source, brick, build_dir)


def copy_and_rewrite(source: str, brick: str, options: dict) -> List[str]:
work_dir = options["work_dir"]
build_dir = options["build_dir"]
top_ns = options["top_ns"]
ns = options["ns"]

path = parsing.copy_brick(source, brick, work_dir)
rewritten = parsing.rewrite_modules(path, ns, top_ns)

destination_dir = build_dir / top_ns
parsing.copy_brick(path.as_posix(), brick, destination_dir)

return rewritten


def copy_and_rewrite_bricks(
bricks: dict, top_ns: str, work_dir: Path, build_dir: Path
) -> List[str]:
ns = parsing.parse_brick_namespace_from_path(bricks)

options = {"ns": ns, "top_ns": top_ns, "work_dir": work_dir, "build_dir": build_dir}

res = [copy_and_rewrite(source, brick, options) for source, brick in bricks.items()]
flattened: List[str] = reduce(operator.iadd, res, [])

return sorted(flattened)


def cleanup(work_dir: Path) -> None:
if not work_dir.exists() or not work_dir.is_dir():
return

shutil.rmtree(work_dir.as_posix())
32 changes: 32 additions & 0 deletions components/polylith/building/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pathlib import Path
from typing import Union

from polylith import toml


def get_work_dir(options: dict) -> Path:
work_dir = options.get("work-dir", ".polylith_tmp")

return Path(work_dir)


def calculate_root_dir(bricks: dict) -> Union[str, None]:
brick_path = next((v for v in bricks.values()), None)

return str.split(brick_path, "/")[0] if brick_path else None


def calculate_destination_dir(data: dict) -> Union[Path, None]:
bricks = toml.get_project_packages_from_polylith_section(data)

if not bricks:
return None

custom_top_ns = toml.get_custom_top_namespace_from_polylith_section(data)

if custom_top_ns:
return Path(custom_top_ns)

root = calculate_root_dir(bricks)

return Path(root) if root else None
6 changes: 2 additions & 4 deletions components/polylith/hatch/core.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from pathlib import Path
from typing import Union

from polylith import toml
from polylith import building, toml


def get_work_dir(config: dict) -> Path:
work_dir = config.get("work-dir", ".polylith_tmp")

return Path(work_dir)
return building.get_work_dir(config)


def get_top_namespace(pyproject: dict, config: dict) -> Union[str, None]:
Expand Down
32 changes: 8 additions & 24 deletions components/polylith/pdm/core.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
import shutil
from pathlib import Path

from polylith import parsing
from polylith import building


def get_work_dir(config: dict) -> Path:
build_config = config.get("tool", {}).get("pdm", {}).get("build", {})

work_dir = build_config.get("work-dir", ".polylith_tmp")

return Path(work_dir)
return building.get_work_dir(build_config)


def copy_bricks_as_is(bricks: dict, build_dir: Path) -> None:
for source, brick in bricks.items():
parsing.copy_brick(source, brick, build_dir)
building.copy_bricks_as_is(bricks, build_dir)


def copy_and_rewrite_bricks(
def copy_and_rewrite(
bricks: dict, top_ns: str, work_dir: Path, build_dir: Path
) -> None:
ns = parsing.parse_brick_namespace_from_path(bricks)

for source, brick in bricks.items():
path = parsing.copy_brick(source, brick, work_dir)
rewritten_bricks = parsing.rewrite_modules(path, ns, top_ns)

destination_dir = build_dir / top_ns
parsing.copy_brick(path.as_posix(), brick, destination_dir)

for item in rewritten_bricks:
print(f"Updated {item} with new top namespace for local imports.")

rewritten = building.copy_and_rewrite_bricks(bricks, top_ns, work_dir, build_dir)

def cleanup(work_dir: Path) -> None:
if not work_dir.exists() or not work_dir.is_dir():
return
for item in rewritten:
print(f"Updated {item} with new top namespace for local imports.")

shutil.rmtree(work_dir.as_posix())
building.cleanup(work_dir)
3 changes: 1 addition & 2 deletions components/polylith/pdm/hooks/bricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ def build_initialize(root: Path, config_data: dict, build_dir: Path) -> None:
if not top_ns:
core.copy_bricks_as_is(bricks, build_dir)
else:
core.copy_and_rewrite_bricks(bricks, top_ns, work_dir, build_dir)
core.cleanup(work_dir)
core.copy_and_rewrite(bricks, top_ns, work_dir, build_dir)
Loading

0 comments on commit 71e36b9

Please sign in to comment.