|
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 |
3 | 6 | from pathlib import Path |
4 | | -from typing import Callable |
5 | 7 |
|
6 | | -from debmagic._utils import run_cmd |
| 8 | +from ._build_stage import BuildStage |
| 9 | +from ._utils import run_cmd |
7 | 10 |
|
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 |
11 | 15 |
|
12 | 16 |
|
13 | 17 | @dataclass |
14 | 18 | class Build: |
| 19 | + presets: list[Preset] |
| 20 | + source_package: SourcePackage |
15 | 21 | source_dir: Path |
16 | | - install_dir: Path |
| 22 | + binary_packages: list[BinaryPackage] |
| 23 | + install_base_dir: Path |
17 | 24 | architecture_target: str |
18 | 25 | architecture_host: str |
19 | 26 | flags: Namespace |
20 | 27 | 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 |
24 | 114 |
|
25 | 115 |
|
26 | 116 | class BuildError(RuntimeError): |
27 | 117 | 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) |
|
0 commit comments