Skip to content

Commit c8954c6

Browse files
authored
Merge pull request #7 from TheJJ/package-versions
package version objects
2 parents dd7e36f + 6f7bb70 commit c8954c6

File tree

6 files changed

+159
-27
lines changed

6 files changed

+159
-27
lines changed

src/debmagic/_dpkg/buildflags.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import json
22
import os
3-
import re
4-
import shlex
53
import subprocess
64
from pathlib import Path
75

6+
from .._package_version import PackageVersion
7+
from .._utils import run_cmd
88

9-
def _run_output(cmd: str, input: str | None = None, env: dict[str, str] | None = None, cwd: Path | None = None) -> str:
10-
input_data = None
11-
if input:
12-
input_data = input.encode()
13-
return subprocess.check_output(shlex.split(cmd), input=input_data, env=env).strip().decode()
149

10+
def _cmd(cmd: str, input_data: str | None = None, env: dict[str, str] | None = None, cwd: Path | None = None) -> str:
11+
return run_cmd(cmd, check=True, env=env, input=input_data, text=True, capture_output=True).stdout.strip()
1512

16-
def get_flags(package_dir: Path, maint_options: str | None = None) -> dict[str, str]:
13+
14+
def get_pkg_env(package_dir: Path, maint_options: str | None = None) -> tuple[dict[str, str], PackageVersion]:
1715
"""
1816
does what including "/usr/share/dpkg/buildflags.mk" would do.
1917
"""
@@ -24,7 +22,7 @@ def get_flags(package_dir: Path, maint_options: str | None = None) -> dict[str,
2422
# TODO more vars as parameters, e.g. DEB_CFLAGS_MAINT_APPEND
2523

2624
# get build flags
27-
flags_raw = _run_output("dpkg-buildflags", env=result)
25+
flags_raw = _cmd("dpkg-buildflags", env=result)
2826
for flag_line in flags_raw.splitlines():
2927
flag_name, _, flag_value = flag_line.partition("=")
3028
result[flag_name] = flag_value
@@ -37,29 +35,32 @@ def get_flags(package_dir: Path, maint_options: str | None = None) -> dict[str,
3735

3836
# architecture.mk
3937
if result.get("DEB_HOST_ARCH") is None:
40-
arch_flags_raw = _run_output("dpkg-architecture", env=result)
38+
arch_flags_raw = _cmd("dpkg-architecture", env=result)
4139
for arch_flag_line in arch_flags_raw.splitlines():
4240
arch_flag_name, _, arch_flag_value = arch_flag_line.partition("=")
4341
result[arch_flag_name] = arch_flag_value
4442

4543
# pkg-info.mk
4644
if result.get("DEB_SOURCE") is None or result.get("DEB_VERSION") is None:
47-
result["DEB_SOURCE"] = _run_output("dpkg-parsechangelog -SSource", env=result, cwd=package_dir)
48-
result["DEB_VERSION"] = _run_output("dpkg-parsechangelog -SVersion", env=result, cwd=package_dir)
45+
result["DEB_SOURCE"] = _cmd("dpkg-parsechangelog -SSource", env=result, cwd=package_dir)
46+
result["DEB_VERSION"] = _cmd("dpkg-parsechangelog -SVersion", env=result, cwd=package_dir)
47+
version = PackageVersion.from_str(result["DEB_VERSION"])
48+
4949
# this would return DEB_VERSION in pkg-info.mk if no epoch is in version.
5050
# instead, we return "0" as oritinally intended if no epoch is in version.
51-
result["DEB_VERSION_EPOCH"] = (
52-
"0" if ":" not in result["DEB_VERSION"] else re.sub(r"^([0-9]+):.*$", r"\1", result["DEB_VERSION"])
53-
)
54-
result["DEB_VERSION_EPOCH_UPSTREAM"] = re.sub(r"^(.*?)(-.*)?$", r"\1", result["DEB_VERSION"])
55-
result["DEB_VERSION_UPSTREAM_REVISION"] = re.sub(r"^([0-9]*:)?(.*?)$", r"\2", result["DEB_VERSION"])
56-
result["DEB_VERSION_UPSTREAM"] = re.sub(r"^([0-9]*:)(.*?)", r"\2", result["DEB_VERSION_EPOCH_UPSTREAM"])
57-
result["DEB_VERSION_REVISION"] = re.sub(r"^.*?-([^-]*)$", r"\1", result["DEB_VERSION"])
58-
result["DEB_DISTRIBUTION"] = _run_output("dpkg-parsechangelog -SDistribution", env=result, cwd=package_dir)
59-
result["DEB_TIMESTAMP"] = _run_output("dpkg-parsechangelog -STimestamp", env=result, cwd=package_dir)
51+
result["DEB_VERSION_EPOCH"] = version.epoch
52+
result["DEB_VERSION_EPOCH_UPSTREAM"] = version.epoch_upstream
53+
result["DEB_VERSION_UPSTREAM_REVISION"] = version.upstream_revision
54+
result["DEB_VERSION_UPSTREAM"] = version.upstream
55+
result["DEB_VERSION_REVISION"] = version.revision
56+
57+
result["DEB_DISTRIBUTION"] = _cmd("dpkg-parsechangelog -SDistribution", env=result, cwd=package_dir)
58+
result["DEB_TIMESTAMP"] = _cmd("dpkg-parsechangelog -STimestamp", env=result, cwd=package_dir)
6059

6160
if result.get("SOURCE_DATE_EPOCH") is None:
6261
result["SOURCE_DATE_EPOCH"] = result["DEB_TIMESTAMP"]
62+
else:
63+
version = PackageVersion.from_str(result["DEB_VERSION"])
6364

6465
if result.get("ELF_PACKAGE_METADATA") is None:
6566
elf_meta = {
@@ -74,4 +75,4 @@ def get_flags(package_dir: Path, maint_options: str | None = None) -> dict[str,
7475

7576
result["ELF_PACKAGE_METADATA"] = json.dumps(elf_meta, separators=(",", ":"))
7677

77-
return result
78+
return result, version

src/debmagic/_package.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ._build_stage import BuildStage
1717
from ._build_step import BuildStep
1818
from ._dpkg import buildflags
19+
from ._package_version import PackageVersion
1920
from ._preset import Preset, PresetsT, as_presets
2021
from ._rules_file import RulesFile, find_rules_file
2122
from ._types import CustomFuncArg, CustomFuncArgsT
@@ -118,6 +119,7 @@ class SourcePackage:
118119
rules_file: RulesFile
119120
presets: list[Preset]
120121
binary_packages: list[BinaryPackage]
122+
version: PackageVersion
121123
buildflags: Namespace
122124
stage_functions: dict[BuildStage, BuildStep] = field(default_factory=dict)
123125
custom_functions: dict[str, CustomFunction] = field(default_factory=dict)
@@ -261,7 +263,7 @@ def package(
261263
presets.append(DefaultPreset())
262264

263265
# set buildflags as environment variables
264-
flags = buildflags.get_flags(rules_file.package_dir, maint_options=maint_options)
266+
flags, version = buildflags.get_pkg_env(rules_file.package_dir, maint_options=maint_options)
265267
os.environ.update(flags)
266268

267269
src_pkg: SourcePackage | None = None
@@ -281,6 +283,7 @@ def package(
281283
rules_file,
282284
presets,
283285
bin_pkgs,
286+
version,
284287
buildflags=Namespace(**flags),
285288
)
286289

src/debmagic/_package_version.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import re
2+
from dataclasses import dataclass
3+
from typing import Self
4+
5+
6+
@dataclass
7+
class PackageVersion:
8+
"""
9+
debian version string, split into various subparts.
10+
"""
11+
12+
#: distro packaging override base version (default is 0)
13+
epoch: str
14+
15+
#: upstream package version
16+
upstream: str
17+
18+
#: packaging (linux distro) revision
19+
revision: str
20+
21+
@property
22+
def version(self) -> str:
23+
ret = ""
24+
if self.epoch != "0":
25+
ret += f"{self.epoch}:"
26+
ret += self.upstream
27+
if self.revision:
28+
ret += f"-{self.revision}"
29+
return ret
30+
31+
@property
32+
def epoch_upstream(self) -> str:
33+
"""
34+
distro epoch plus upstream version
35+
"""
36+
if self.epoch:
37+
return f"{self.epoch}:{self.upstream}"
38+
else:
39+
return self.upstream
40+
41+
@property
42+
def upstream_revision(self) -> str:
43+
"""
44+
upstream version including distro package revision
45+
"""
46+
if self.revision:
47+
return f"{self.upstream}-{self.revision}"
48+
else:
49+
return self.upstream
50+
51+
@classmethod
52+
def from_str(cls, version: str) -> Self:
53+
epoch_upstream = re.sub(r"^(.*?)(-[^-]*)?$", r"\1", version)
54+
return cls(
55+
# epoch = distro packaging override base version (default is 0)
56+
# pkg-info.mk uses the full version if no epoch is in it.
57+
# instead, we return "0" as oritinally intended if no epoch is in version.
58+
epoch="0" if ":" not in version else re.sub(r"^([0-9]+):.*$", r"\1", version),
59+
upstream=re.sub(r"^([0-9]*:)?(.*?)$", r"\2", epoch_upstream),
60+
revision=re.sub(r"^.*?(-([^-]*))?$", r"\2", version),
61+
)

src/debmagic/_utils.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,7 @@ def run_cmd(
6666
if dry_run:
6767
return subprocess.CompletedProcess(cmd_args, 0)
6868

69-
ret = subprocess.run(cmd_args, check=False, **kwargs)
70-
71-
if check and ret.returncode != 0:
72-
raise RuntimeError(f"failed to execute {cmd_pretty}")
69+
ret = subprocess.run(cmd_args, check=check, **kwargs)
7370

7471
return ret
7572

tests/unit/test_exec.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import subprocess
2+
from unittest.mock import patch
3+
4+
from debmagic import _utils
5+
6+
7+
@patch("subprocess.run", autospec=True)
8+
def test_util_exec_str(mock_run):
9+
mock_run.return_value = subprocess.CompletedProcess(["some", "command"], returncode=0)
10+
_utils.run_cmd("some command")
11+
mock_run.assert_called_once_with(["some", "command"], check=True)
12+
13+
14+
@patch("subprocess.run", autospec=True)
15+
def test_util_exec_list(mock_run):
16+
mock_run.return_value = subprocess.CompletedProcess(["some", "command"], returncode=0)
17+
_utils.run_cmd(["some", "command"])
18+
mock_run.assert_called_once_with(["some", "command"], check=True)
19+
20+
21+
@patch("subprocess.run", autospec=True)
22+
def test_util_exec_str_dryrun(mock_run):
23+
_utils.run_cmd("some command", dry_run=True)
24+
mock_run.assert_not_called()
25+
26+
27+
@patch("subprocess.run", autospec=True)
28+
def test_util_exec_str_shlex(mock_run):
29+
mock_run.return_value = subprocess.CompletedProcess(["nothing"], returncode=0)
30+
_utils.run_cmd("some command 'some arg'")
31+
mock_run.assert_called_once_with(["some", "command", "some arg"], check=True)
32+
33+
34+
@patch("subprocess.run", autospec=True)
35+
def test_util_exec_str_check(mock_run):
36+
mock_run.return_value = subprocess.CompletedProcess(["something"], returncode=0)
37+
_utils.run_cmd("something", check=True)
38+
mock_run.assert_called_once_with(["something"], check=True)
39+
40+
41+
@patch("subprocess.run", autospec=True)
42+
def test_util_exec_str_nocheck(mock_run):
43+
mock_run.return_value = subprocess.CompletedProcess(["something"], returncode=0)
44+
_utils.run_cmd("something", check=False)
45+
mock_run.assert_called_once_with(["something"], check=False)

tests/unit/test_versioning.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pytest
2+
3+
from debmagic._package_version import PackageVersion
4+
5+
6+
@pytest.mark.parametrize(
7+
"version, expected",
8+
[
9+
(
10+
"1.2.3a.4-42.2-14ubuntu2~20.04.1",
11+
PackageVersion(epoch="0", upstream="1.2.3a.4-42.2", revision="14ubuntu2~20.04.1"),
12+
),
13+
(
14+
"3:1.2.3a.4-42.2-14ubuntu2~20.04.1",
15+
PackageVersion(epoch="3", upstream="1.2.3a.4-42.2", revision="14ubuntu2~20.04.1"),
16+
),
17+
("3:1.2.3a.4ubuntu", PackageVersion(epoch="3", upstream="1.2.3a.4ubuntu", revision="")),
18+
("3:1.2.3a-4ubuntu", PackageVersion(epoch="3", upstream="1.2.3a", revision="4ubuntu")),
19+
("3:1.2.3a-4ubuntu1", PackageVersion(epoch="3", upstream="1.2.3a", revision="4ubuntu1")),
20+
],
21+
)
22+
def test_version_parsing(version: str, expected: PackageVersion):
23+
parsed_version = PackageVersion.from_str(version)
24+
25+
assert parsed_version == expected

0 commit comments

Comments
 (0)