Skip to content

Commit d142416

Browse files
committed
feat(preset): use preset classes
1 parent 494030d commit d142416

File tree

18 files changed

+800
-268
lines changed

18 files changed

+800
-268
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ build-backend = "setuptools.build_meta"
2424
[dependency-groups]
2525
dev = ["pytest>=8.4.2"]
2626

27+
[tool.mypy]
28+
mypy_path = '$MYPY_CONFIG_FILE_DIR/src'
29+
2730
[tool.ruff]
2831
line-length = 120
2932
target-version = "py312"

src/debmagic/_build.py

Lines changed: 101 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,117 @@
1-
import shutil
2-
from dataclasses import dataclass
1+
from __future__ import annotations
2+
3+
import shlex
4+
import typing
5+
from dataclasses import dataclass, field
36
from pathlib import Path
4-
from typing import Callable
57

6-
from debmagic._utils import run_cmd
8+
from ._build_stage import BuildStage
9+
from ._utils import run_cmd
710

8-
from ._rules_file import RulesFile
9-
from ._types import PresetT
10-
from ._utils import Namespace
11+
if typing.TYPE_CHECKING:
12+
from ._package import BinaryPackage, PackageFilter, SourcePackage
13+
from ._preset import Preset
14+
from ._utils import Namespace
1115

1216

1317
@dataclass
1418
class Build:
19+
presets: list[Preset]
20+
source_package: SourcePackage
1521
source_dir: Path
16-
install_dir: Path
22+
binary_packages: list[BinaryPackage]
23+
install_base_dir: Path
1724
architecture_target: str
1825
architecture_host: str
1926
flags: Namespace
2027
parallel: int
21-
prefix: str = "/usr"
22-
23-
type BuildStep = Callable[[Build], None]
28+
prefix: Path
29+
dry_run: bool = False
30+
31+
_completed_stages: set[BuildStage] = field(default_factory=set)
32+
_selected_packages: list[BinaryPackage] | None = field(default_factory=list)
33+
34+
def cmd(self, cmd: list[str] | str, **kwargs):
35+
"""
36+
execute a command, auto-converts command strings/lists.
37+
use this to supports build dry-runs.
38+
"""
39+
cmd_args : list[str] | str = cmd
40+
is_shell = kwargs.get("shell")
41+
if not is_shell and isinstance(cmd, str):
42+
cmd_args = shlex.split(cmd)
43+
elif is_shell and not isinstance(cmd, str):
44+
cmd_args = shlex.join(cmd)
45+
46+
run_cmd(cmd_args, dry_run=self.dry_run, **kwargs)
47+
48+
@property
49+
def install_dirs(self) -> dict[str, Path]:
50+
""" return { binary_package_name: install_directory } """
51+
return {
52+
pkg.name: self.install_base_dir / pkg.name
53+
for pkg in self.binary_packages
54+
}
55+
56+
def select_packages(self, names: set[str]):
57+
""" only build those packages """
58+
if not names:
59+
self._selected_packages = None
60+
61+
self._selected_packages = list()
62+
for pkg in self.binary_packages:
63+
if pkg.name in names:
64+
self._selected_packages.append(pkg)
65+
66+
def filter_packages(self, package_filter: PackageFilter) -> None:
67+
""" apply filter to only build those packages """
68+
self.select_packages({pkg.name for pkg in
69+
package_filter.get_packages(self.binary_packages)})
70+
71+
def is_stage_completed(self, stage: BuildStage) -> bool:
72+
return stage in self._completed_stages
73+
74+
def _mark_stage_done(self, stage: BuildStage) -> None:
75+
self._completed_stages.add(stage)
76+
# TODO: persist state in build dir
77+
78+
def run(
79+
self,
80+
target_stage: BuildStage | None = None,
81+
) -> None:
82+
for stage in BuildStage:
83+
print(f"debmagic: stage {stage!s}", end="")
84+
85+
# skip done stages
86+
if self.is_stage_completed(stage):
87+
print(" already completed, skipping.")
88+
continue
89+
print(":")
90+
91+
# run stage function from debian/rules.py
92+
if rules_stage_function := self.source_package.stage_functions.get(stage):
93+
print(f"debmagic: running stage from rules file...")
94+
rules_stage_function(self)
95+
self._mark_stage_done(stage)
96+
97+
else:
98+
# run stage function from first providing preset
99+
for preset in self.presets:
100+
print(f"debmagic: trying preset {preset}...")
101+
if preset_stage_function := preset.get_stage(stage):
102+
print("debmagic: preset has function")
103+
preset_stage_function(self)
104+
self._mark_stage_done(stage)
105+
break # stop preset processing
106+
107+
if not self.is_stage_completed(stage):
108+
breakpoint()
109+
raise RuntimeError(f"{stage!s} stage was never executed")
110+
111+
if stage == target_stage:
112+
print(f"debmagic: target stage {stage!s} reached")
113+
break
24114

25115

26116
class BuildError(RuntimeError):
27117
pass
28-
29-
30-
def _get_func_from_preset(name: str, preset: PresetT) -> BuildStep | None:
31-
if preset is None:
32-
return None
33-
# TODO: allow sets, ...
34-
if isinstance(preset, list):
35-
for p in reversed(preset):
36-
func = getattr(p, name, None)
37-
if func is not None:
38-
return func
39-
return None
40-
41-
return getattr(preset, name, None)
42-
43-
44-
def strip(build: Build):
45-
run_cmd(["dh_dwz", "-a"], cwd=build.source_dir)
46-
run_cmd(["dh_strip", "-a"], cwd=build.source_dir)
47-
48-
49-
def gen_shlibs(build: Build):
50-
run_cmd(["dh_makeshlibs", "-a"], cwd=build.source_dir)
51-
run_cmd(["dh_shlibdeps", "-a"], cwd=build.source_dir)
52-
53-
54-
def install_deb(build: Build):
55-
run_cmd(["dh_installdeb"], cwd=build.source_dir)
56-
57-
58-
def gen_control_file(build: Build):
59-
run_cmd(["dh_gencontrol"], cwd=build.source_dir)
60-
61-
62-
def make_md5sums(build: Build):
63-
run_cmd(["dh_md5sums"], cwd=build.source_dir)
64-
65-
66-
def build_deb(build: Build):
67-
run_cmd(["dh_builddeb"], cwd=build.source_dir)
68-
69-
70-
def clean_package(
71-
build: Build,
72-
rules_frame: RulesFile,
73-
preset: PresetT,
74-
) -> None:
75-
76-
# clean install dir
77-
if build.install_dir.is_dir():
78-
shutil.rmtree(build.install_dir)
79-
80-
81-
def build_package(
82-
build: Build,
83-
rules_frame: RulesFile,
84-
preset: PresetT,
85-
) -> None:
86-
build.install_dir.mkdir(exist_ok=True)
87-
88-
for step_name in ["configure", "compile", "install"]:
89-
if step := rules_frame.local_vars.get(step_name):
90-
step(build)
91-
elif step := _get_func_from_preset(step_name, preset):
92-
step(build)
93-
else:
94-
# step not used
95-
pass
96-
97-
strip(build)
98-
gen_shlibs(build)
99-
install_deb(build)
100-
gen_control_file(build)
101-
make_md5sums(build)
102-
build_deb(build)

src/debmagic/_build_order.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from enum import StrEnum
2+
3+
4+
class BuildOrder(StrEnum):
5+
stages = "stages"
6+
""" iterate over all build stages, then work for all selected binary packages """
7+
packages = "packages"
8+
""" iterate over selected binary packages and perform each stage for it """

src/debmagic/_build_stage.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from enum import StrEnum
2+
3+
4+
class BuildStage(StrEnum):
5+
prepare = "prepare"
6+
configure = "configure"
7+
build = "build"
8+
test = "test"
9+
install = "install"
10+
package = "package"

src/debmagic/_build_step.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from typing import Callable
2+
3+
from ._build import Build
4+
5+
type BuildStep = Callable[[Build], None]

src/debmagic/_modules/_base.py

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
1-
from .._build import Build
1+
from .._preset import Preset as BasePreset
22

33
# TODO: detect build system
44
# autotools, cmake, python, rust, perl, go, ...
55

6-
def configure(build: Build, args: list[str] | None = None):
7-
# TODO
8-
pass
9-
10-
11-
def compile(build: Build, args: list[str] | None = None):
12-
# TODO
13-
pass
14-
15-
16-
def install(build: Build, args: list[str] | None = None):
17-
# TODO
6+
class Preset(BasePreset):
187
pass

src/debmagic/_modules/autotools.py

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,68 @@
11
from .._build import Build, BuildError
2-
from .._utils import run_cmd
3-
4-
5-
def autoreconf(build: Build):
6-
configure_ac_path = build.source_dir / "configure.ac"
7-
if configure_ac_path.is_file():
8-
run_cmd(
9-
["autoreconf", "--force", "--install", "--verbose"], cwd=build.source_dir
2+
from .._preset import Preset as PresetBase
3+
4+
5+
class Preset(PresetBase):
6+
#def clean(self, build: Build):
7+
# TODO: if Makefile exists and has one of distclean, realclean, clean target, run it.
8+
9+
def configure(self, build: Build, args: list[str] | None = None):
10+
args = args or []
11+
12+
if not (build.source_dir / "configure").is_file():
13+
raise BuildError("no 'configure' file in build root - perhaps run autotools.autoreconf()?")
14+
15+
# as autotools-dev/README.Debian recommends
16+
default_args = [
17+
"./configure",
18+
f"--prefix={build.prefix}",
19+
"--includedir=${prefix}/include",
20+
"--mandir=${prefix}/share/man",
21+
"--infodir=${prefix}/share/info",
22+
"--sysconfdir=/etc",
23+
"--localstatedir=/var",
24+
"--runstatedir=/run",
25+
#"--disable-option-checking", # TODO: activate if not strict-mode?
26+
"--disable-maintainer-mode",
27+
"--disable-dependency-tracking",
28+
#"--with-bugurl=https://bugs.launchpad.net/ubuntu/+source/${srcpkg}",
29+
]
30+
31+
if multiarch := build.flags["DEB_HOST_MULTIARCH"]:
32+
default_args.append(f"--libdir=${{prefix}}/lib/{multiarch}")
33+
default_args.append(f"--libexecdir=${{prefix}}/lib/{multiarch}")
34+
else:
35+
default_args.append("--libexecdir=${prefix}/lib")
36+
37+
# cross-building
38+
default_args.append(f"--build={build.architecture_target}")
39+
if build.architecture_target != build.architecture_host:
40+
default_args.append(f"--host={build.architecture_target}")
41+
42+
build.cmd(
43+
default_args + args,
44+
cwd=build.source_dir,
1045
)
1146

47+
# TODO: show some config.log if configure failed
1248

13-
def configure(build: Build, args: list[str] | None = None):
14-
args = args or []
15-
16-
if not (build.source_dir / "configure").is_file():
17-
raise BuildError("no 'configure' file in build root - perhaps run autotools.autoreconf()?")
18-
19-
# as autotools-dev/README.Debian recommends
20-
default_args = [
21-
"./configure",
22-
f"--prefix={build.prefix}",
23-
"--includedir=${prefix}/include",
24-
"--mandir=${prefix}/share/man",
25-
"--infodir=${prefix}/share/info",
26-
"--sysconfdir=/etc",
27-
"--localstatedir=/var",
28-
"--runstatedir=/run",
29-
"--disable-option-checking", # TODO: if not strict-mode
30-
"--disable-maintainer-mode",
31-
"--disable-dependency-tracking",
32-
#"--with-bugurl=https://bugs.launchpad.net/ubuntu/+source/${srcpkg}",
33-
]
34-
35-
if multiarch := build.flags["DEB_HOST_MULTIARCH"]:
36-
default_args.append(f"--libdir=${{prefix}}/lib/{multiarch}")
37-
default_args.append(f"--libexecdir=${{prefix}}/lib/{multiarch}")
38-
else:
39-
default_args.append("--libexecdir=${prefix}/lib")
49+
def build(self, build: Build, args: list[str] = []):
50+
args = [f"-j{build.parallel}"] + args
4051

41-
# cross-building
42-
default_args.append(f"--build={build.architecture_target}")
43-
if build.architecture_target != build.architecture_host:
44-
default_args.append(f"--host={build.architecture_target}")
52+
build.cmd(["make"] + args, cwd=build.source_dir)
4553

46-
run_cmd(
47-
default_args + args,
48-
cwd=build.source_dir,
49-
)
5054

55+
# TODO: def test(): run make test or make check
5156

52-
def compile(build: Build, args: list[str] | None = None):
53-
args = args or []
54-
55-
run_cmd(["make"] + args, cwd=build.source_dir)
56-
57-
58-
def install(build: Build, args: list[str] | None = None):
59-
args = args or []
57+
def install(self, build: Build, args: list[str] = []):
58+
build.cmd(
59+
["make", f"DESTDIR={build.install_dir}", "install"] + args, cwd=build.source_dir
60+
)
6061

61-
run_cmd(
62-
["make", f"DESTDIR={build.install_dir}", "install"] + args, cwd=build.source_dir
63-
)
6462

65-
# TODO: def test(): run make test or make check
63+
def autoreconf(build: Build):
64+
configure_ac_path = build.source_dir / "configure.ac"
65+
if configure_ac_path.is_file():
66+
build.cmd(
67+
["autoreconf", "--force", "--install", "--verbose"], cwd=build.source_dir
68+
)

0 commit comments

Comments
 (0)