Skip to content

Commit 71e36b9

Browse files
authored
feat(poly build): setup and teardown for building, when using Maturin 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
1 parent 5c3d8ca commit 71e36b9

File tree

17 files changed

+619
-337
lines changed

17 files changed

+619
-337
lines changed

bases/polylith/cli/build.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from pathlib import Path
2+
3+
import tomlkit
4+
from polylith import building, repo, toml
5+
from polylith.cli import options
6+
from typer import Exit, Typer
7+
from typing_extensions import Annotated
8+
9+
app = Typer()
10+
11+
12+
def get_work_dir(root: Path, directory: str) -> Path:
13+
work_dir = building.get_work_dir({})
14+
work_path = Path(directory) / work_dir if directory else work_dir
15+
16+
return root / work_path
17+
18+
19+
def get_build_dir(root: Path, directory: str) -> Path:
20+
return root / Path(directory) if directory else root
21+
22+
23+
def get_project_data(build_dir: Path) -> tomlkit.TOMLDocument:
24+
fullpath = build_dir / repo.default_toml
25+
26+
if not fullpath.exists():
27+
raise Exit(code=1)
28+
29+
return toml.read_toml_document(fullpath)
30+
31+
32+
@app.command("setup")
33+
def setup_command(directory: Annotated[str, options.directory] = ""):
34+
"""Prepare a project before building a wheel or a source distribution (sdist).
35+
Run it before the build command of your Package & Dependency Management tool.
36+
37+
"""
38+
root = Path.cwd()
39+
build_dir = get_build_dir(root, directory)
40+
print(f"Build directory: {build_dir}")
41+
42+
data = get_project_data(build_dir)
43+
bricks = toml.get_project_packages_from_polylith_section(data)
44+
45+
if not bricks:
46+
print("No bricks found.")
47+
return
48+
49+
bricks_with_paths = {build_dir / k: v for k, v in bricks.items()}
50+
custom_top_ns = toml.get_custom_top_namespace_from_polylith_section(data)
51+
52+
if not custom_top_ns:
53+
building.copy_bricks_as_is(bricks_with_paths, build_dir)
54+
else:
55+
work_dir = get_work_dir(root, directory)
56+
print(f"Using temporary working directory: {work_dir}")
57+
58+
rewritten = building.copy_and_rewrite_bricks(
59+
bricks_with_paths, custom_top_ns, work_dir, build_dir
60+
)
61+
62+
for item in rewritten:
63+
print(f"Updated {item} with new top namespace for local imports.")
64+
65+
66+
@app.command("teardown")
67+
def teardown_command(directory: Annotated[str, options.directory] = ""):
68+
"""Clean up temporary directories. Run it after the build command of your Package & Dependency Management tool."""
69+
root = Path.cwd()
70+
71+
work_dir = get_work_dir(root, directory)
72+
build_dir = get_build_dir(root, directory)
73+
74+
data = get_project_data(build_dir)
75+
bricks = toml.get_project_packages_from_polylith_section(data)
76+
77+
if not bricks:
78+
return
79+
80+
destination_dir = building.calculate_destination_dir(data)
81+
82+
if work_dir.exists():
83+
print(f"Removing temporary working directory: {work_dir}")
84+
building.cleanup(work_dir)
85+
86+
if destination_dir:
87+
destination_path = build_dir / destination_dir
88+
print(f"Removing bricks path used during build: {destination_path}")
89+
building.cleanup(destination_path)

bases/polylith/cli/core.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import List, Union
33

44
from polylith import commands, configuration, info, repo
5-
from polylith.cli import create, options
5+
from polylith.cli import build, create, options
66
from typer import Exit, Option, Typer
77
from typing_extensions import Annotated
88

@@ -15,6 +15,13 @@
1515
)
1616

1717

18+
app.add_typer(
19+
build.app,
20+
name="build",
21+
help="For Package & Dependency Management tools without support for plugins or build hooks.",
22+
)
23+
24+
1825
def filtered_projects_data(
1926
projects_data: List[dict], directory: Union[str, None]
2027
) -> List[dict]:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from polylith.building.core import cleanup, copy_and_rewrite_bricks, copy_bricks_as_is
2+
from polylith.building.paths import calculate_destination_dir, get_work_dir
3+
4+
__all__ = [
5+
"calculate_destination_dir",
6+
"cleanup",
7+
"copy_and_rewrite_bricks",
8+
"copy_bricks_as_is",
9+
"get_work_dir",
10+
]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import operator
2+
import shutil
3+
from functools import reduce
4+
from pathlib import Path
5+
from typing import List
6+
7+
from polylith import parsing
8+
9+
10+
def copy_bricks_as_is(bricks: dict, build_dir: Path) -> None:
11+
for source, brick in bricks.items():
12+
parsing.copy_brick(source, brick, build_dir)
13+
14+
15+
def copy_and_rewrite(source: str, brick: str, options: dict) -> List[str]:
16+
work_dir = options["work_dir"]
17+
build_dir = options["build_dir"]
18+
top_ns = options["top_ns"]
19+
ns = options["ns"]
20+
21+
path = parsing.copy_brick(source, brick, work_dir)
22+
rewritten = parsing.rewrite_modules(path, ns, top_ns)
23+
24+
destination_dir = build_dir / top_ns
25+
parsing.copy_brick(path.as_posix(), brick, destination_dir)
26+
27+
return rewritten
28+
29+
30+
def copy_and_rewrite_bricks(
31+
bricks: dict, top_ns: str, work_dir: Path, build_dir: Path
32+
) -> List[str]:
33+
ns = parsing.parse_brick_namespace_from_path(bricks)
34+
35+
options = {"ns": ns, "top_ns": top_ns, "work_dir": work_dir, "build_dir": build_dir}
36+
37+
res = [copy_and_rewrite(source, brick, options) for source, brick in bricks.items()]
38+
flattened: List[str] = reduce(operator.iadd, res, [])
39+
40+
return sorted(flattened)
41+
42+
43+
def cleanup(work_dir: Path) -> None:
44+
if not work_dir.exists() or not work_dir.is_dir():
45+
return
46+
47+
shutil.rmtree(work_dir.as_posix())
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from pathlib import Path
2+
from typing import Union
3+
4+
from polylith import toml
5+
6+
7+
def get_work_dir(options: dict) -> Path:
8+
work_dir = options.get("work-dir", ".polylith_tmp")
9+
10+
return Path(work_dir)
11+
12+
13+
def calculate_root_dir(bricks: dict) -> Union[str, None]:
14+
brick_path = next((v for v in bricks.values()), None)
15+
16+
return str.split(brick_path, "/")[0] if brick_path else None
17+
18+
19+
def calculate_destination_dir(data: dict) -> Union[Path, None]:
20+
bricks = toml.get_project_packages_from_polylith_section(data)
21+
22+
if not bricks:
23+
return None
24+
25+
custom_top_ns = toml.get_custom_top_namespace_from_polylith_section(data)
26+
27+
if custom_top_ns:
28+
return Path(custom_top_ns)
29+
30+
root = calculate_root_dir(bricks)
31+
32+
return Path(root) if root else None

components/polylith/hatch/core.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
from pathlib import Path
22
from typing import Union
33

4-
from polylith import toml
4+
from polylith import building, toml
55

66

77
def get_work_dir(config: dict) -> Path:
8-
work_dir = config.get("work-dir", ".polylith_tmp")
9-
10-
return Path(work_dir)
8+
return building.get_work_dir(config)
119

1210

1311
def get_top_namespace(pyproject: dict, config: dict) -> Union[str, None]:

components/polylith/pdm/core.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,24 @@
1-
import shutil
21
from pathlib import Path
32

4-
from polylith import parsing
3+
from polylith import building
54

65

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

10-
work_dir = build_config.get("work-dir", ".polylith_tmp")
11-
12-
return Path(work_dir)
9+
return building.get_work_dir(build_config)
1310

1411

1512
def copy_bricks_as_is(bricks: dict, build_dir: Path) -> None:
16-
for source, brick in bricks.items():
17-
parsing.copy_brick(source, brick, build_dir)
13+
building.copy_bricks_as_is(bricks, build_dir)
1814

1915

20-
def copy_and_rewrite_bricks(
16+
def copy_and_rewrite(
2117
bricks: dict, top_ns: str, work_dir: Path, build_dir: Path
2218
) -> None:
23-
ns = parsing.parse_brick_namespace_from_path(bricks)
24-
25-
for source, brick in bricks.items():
26-
path = parsing.copy_brick(source, brick, work_dir)
27-
rewritten_bricks = parsing.rewrite_modules(path, ns, top_ns)
28-
29-
destination_dir = build_dir / top_ns
30-
parsing.copy_brick(path.as_posix(), brick, destination_dir)
31-
32-
for item in rewritten_bricks:
33-
print(f"Updated {item} with new top namespace for local imports.")
34-
19+
rewritten = building.copy_and_rewrite_bricks(bricks, top_ns, work_dir, build_dir)
3520

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

40-
shutil.rmtree(work_dir.as_posix())
24+
building.cleanup(work_dir)

components/polylith/pdm/hooks/bricks.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,4 @@ def build_initialize(root: Path, config_data: dict, build_dir: Path) -> None:
1717
if not top_ns:
1818
core.copy_bricks_as_is(bricks, build_dir)
1919
else:
20-
core.copy_and_rewrite_bricks(bricks, top_ns, work_dir, build_dir)
21-
core.cleanup(work_dir)
20+
core.copy_and_rewrite(bricks, top_ns, work_dir, build_dir)

0 commit comments

Comments
 (0)