Skip to content

Commit f30f4d9

Browse files
committed
repo-buildinfo: add CLI interface
Example to get all packages built with that crt version that contain a *.a file: msys2-repo-buildinfo /repo \ --built-with-package="mingw-w64-ucrt-x86_64-crt-git=12.0.0.r619.g850703ae4-1" \ --contains-file="*.a"
1 parent 04d420f commit f30f4d9

File tree

3 files changed

+214
-4
lines changed

3 files changed

+214
-4
lines changed

msys2-repo-buildinfo

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import os
66
import sys
77

88
from msys2_devtools.db import ExtTarFile
9+
from msys2_devtools.utils import vercmp
910
from fastprogress.fastprogress import progress_bar
11+
from concurrent.futures import ThreadPoolExecutor
1012

1113

1214
def find_dbs(target_dir: str) -> list[str]:
@@ -67,21 +69,92 @@ def get_buildinfo(path: str) -> dict:
6769
return parse_buildinfo(buildinfo), files
6870

6971

72+
def parse_vercmp(comparison_expression) -> tuple[str, str | None, str | None]:
73+
operators = ['>=', '<=', '=', '>', '<']
74+
75+
package_name = comparison_expression
76+
operator = None
77+
version = None
78+
79+
for op in operators:
80+
if op in comparison_expression:
81+
parts = comparison_expression.split(op, 1)
82+
package_name = parts[0].strip()
83+
version = parts[1].strip()
84+
operator = op
85+
break
86+
87+
return package_name, operator, version
88+
89+
90+
def parse_full_package_name(full_name: str) -> tuple[str, str]:
91+
name = full_name.rsplit("-", 3)[0]
92+
version = full_name[len(name) + 1:].rsplit("-", 1)[0]
93+
return name, version
94+
95+
96+
def package_matches(full_name: str, expression: str) -> bool:
97+
package_name, package_version = parse_full_package_name(full_name)
98+
ex_name, ex_operator, ex_version = parse_vercmp(expression)
99+
if ex_name != package_name:
100+
return False
101+
102+
if ex_operator is None or ex_version is None:
103+
return True
104+
105+
if ex_operator == "=":
106+
return ex_version == package_version
107+
elif ex_operator == ">":
108+
return vercmp(package_version, ex_version) > 0
109+
elif ex_operator == "<":
110+
return vercmp(package_version, ex_version) < 0
111+
elif ex_operator == ">=":
112+
return vercmp(package_version, ex_version) >= 0
113+
elif ex_operator == "<=":
114+
return vercmp(package_version, ex_version) <= 0
115+
else:
116+
raise ValueError(f"Unknown operator: {ex_operator}")
117+
118+
70119
def main(argv):
71120
parser = argparse.ArgumentParser(
72121
description="List things about the package contents", allow_abbrev=False
73122
)
74123
parser.add_argument("root", help="path to root dir")
124+
parser.add_argument("--built-with-package",
125+
help="filter packages that had this package installed during build time "
126+
"(optionally with a version constraint, e.g. 'foo>=1.0')")
127+
parser.add_argument("--contains-file",
128+
help="filter packages that contain files matching this glob pattern (e.g. '*.a')")
129+
75130
args = parser.parse_args(argv[1:])
76131

77-
something = "mingw-w64-ucrt-x86_64-crt-git-12.0.0.r619.g850703ae4-1-any"
132+
installed_filter = args.built_with_package
133+
file_pattern = args.contains_file
78134

79135
found = set()
80136
paths = get_package_paths(args.root)
81-
for p in progress_bar(paths, leave=False):
137+
138+
def process_path(p):
82139
buildinfo, files = get_buildinfo(p)
83-
has_archive = any(f.endswith(".a") for f in files)
84-
if something in buildinfo["installed"] and has_archive:
140+
return buildinfo, files
141+
142+
with ThreadPoolExecutor(4) as executor:
143+
for buildinfo, files in progress_bar(executor.map(process_path, paths), total=len(paths), leave=False):
144+
if installed_filter is not None:
145+
for installed_full_name in buildinfo["installed"]:
146+
if package_matches(installed_full_name, installed_filter):
147+
break
148+
else:
149+
continue
150+
151+
if file_pattern is not None:
152+
for file in files:
153+
if fnmatch.fnmatch(file, file_pattern):
154+
break
155+
else:
156+
continue
157+
85158
found.update(buildinfo["pkgbase"])
86159

87160
for f in sorted(found):

msys2_devtools/utils.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from typing import Any
2+
from itertools import zip_longest
3+
4+
5+
def vercmp(v1: str, v2: str) -> int:
6+
7+
def cmp(a: Any, b: Any) -> int:
8+
res = (a > b) - (a < b)
9+
assert isinstance(res, int)
10+
return res
11+
12+
def split(v: str) -> tuple[str, str, str | None]:
13+
if "~" in v:
14+
e, v = v.split("~", 1)
15+
else:
16+
e, v = ("0", v)
17+
18+
r: str | None = None
19+
if "-" in v:
20+
v, r = v.rsplit("-", 1)
21+
else:
22+
v, r = (v, None)
23+
24+
return (e, v, r)
25+
26+
digit, alpha, other = range(3)
27+
28+
def get_type(c: str) -> int:
29+
assert c
30+
if c.isdigit():
31+
return digit
32+
elif c.isalpha():
33+
return alpha
34+
else:
35+
return other
36+
37+
def parse(v: str) -> list[str]:
38+
parts: list[str] = []
39+
current = ""
40+
for c in v:
41+
if not current:
42+
current += c
43+
else:
44+
if get_type(c) == get_type(current):
45+
current += c
46+
else:
47+
parts.append(current)
48+
current = c
49+
50+
if current:
51+
parts.append(current)
52+
53+
return parts
54+
55+
def rpmvercmp(v1: str, v2: str) -> int:
56+
for p1, p2 in zip_longest(parse(v1), parse(v2), fillvalue=None):
57+
if p1 is None:
58+
assert p2 is not None
59+
if get_type(p2) == alpha:
60+
return 1
61+
return -1
62+
elif p2 is None:
63+
assert p1 is not None
64+
if get_type(p1) == alpha:
65+
return -1
66+
return 1
67+
68+
t1 = get_type(p1)
69+
t2 = get_type(p2)
70+
if t1 != t2:
71+
if t1 == digit:
72+
return 1
73+
elif t2 == digit:
74+
return -1
75+
elif t1 == other:
76+
return 1
77+
elif t2 == other:
78+
return -1
79+
elif t1 == other:
80+
ret = cmp(len(p1), len(p2))
81+
if ret != 0:
82+
return ret
83+
elif t1 == digit:
84+
ret = cmp(int(p1), int(p2))
85+
if ret != 0:
86+
return ret
87+
elif t1 == alpha:
88+
ret = cmp(p1, p2)
89+
if ret != 0:
90+
return ret
91+
92+
return 0
93+
94+
e1, v1, r1 = split(v1)
95+
e2, v2, r2 = split(v2)
96+
97+
ret = rpmvercmp(e1, e2)
98+
if ret == 0:
99+
ret = rpmvercmp(v1, v2)
100+
if ret == 0 and r1 is not None and r2 is not None:
101+
ret = rpmvercmp(r1, r2)
102+
103+
return ret

tests/test_utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from msys2_devtools.utils import vercmp
2+
3+
4+
def test_vercmp():
5+
6+
def test_ver(a, b, res):
7+
assert vercmp(a, b) == res
8+
assert vercmp(b, a) == (res * -1)
9+
10+
test_ver("1.0.0", "2.0.0", -1)
11+
test_ver("1.0.0", "1.0.0.r101", -1)
12+
test_ver("1.0.0", "1.0.0", 0)
13+
test_ver("2019.10.06", "2020.12.07", -1)
14+
test_ver("1.3_20200327", "1.3_20210319", -1)
15+
test_ver("r2991.1771b556", "0.161.r3039.544c61f", -1)
16+
test_ver("6.8", "6.8.3", -1)
17+
test_ver("6.8", "6.8.", -1)
18+
test_ver("2.5.9.27149.9f6840e90c", "3.0.7.33374", -1)
19+
test_ver(".", "", 1)
20+
test_ver("0", "", 1)
21+
test_ver("0", "00", 0)
22+
test_ver(".", "..0", -1)
23+
test_ver(".0", "..0", -1)
24+
test_ver("1r", "1", -1)
25+
test_ver("r1", "r", 1)
26+
test_ver("1.1.0", "1.1.0a", 1)
27+
test_ver("1.1.0.", "1.1.0a", 1)
28+
test_ver("a", "1", -1)
29+
test_ver(".", "1", -1)
30+
test_ver(".", "a", 1)
31+
test_ver("a1", "1", -1)
32+
33+
# FIXME:
34+
# test_ver(".0", "0", 1)

0 commit comments

Comments
 (0)