gha-utils 4.17.8__tar.gz → 4.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gha-utils might be problematic. Click here for more details.
- {gha_utils-4.17.8 → gha_utils-4.18.0}/PKG-INFO +3 -2
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/__init__.py +1 -1
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/cli.py +2 -2
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/matrix.py +31 -23
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/metadata.py +75 -26
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils.egg-info/PKG-INFO +3 -2
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils.egg-info/requires.txt +2 -1
- {gha_utils-4.17.8 → gha_utils-4.18.0}/pyproject.toml +4 -3
- {gha_utils-4.17.8 → gha_utils-4.18.0}/tests/test_matrix.py +12 -11
- gha_utils-4.18.0/tests/test_metadata.py +329 -0
- gha_utils-4.17.8/tests/test_metadata.py +0 -214
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/__main__.py +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/changelog.py +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/mailmap.py +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/py.typed +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils/test_plan.py +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils.egg-info/SOURCES.txt +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils.egg-info/dependency_links.txt +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils.egg-info/entry_points.txt +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/gha_utils.egg-info/top_level.txt +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/readme.md +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/setup.cfg +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/tests/test_changelog.py +0 -0
- {gha_utils-4.17.8 → gha_utils-4.18.0}/tests/test_mailmap.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gha-utils
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.18.0
|
|
4
4
|
Summary: ⚙️ CLI helpers for GitHub Actions + reuseable workflows
|
|
5
5
|
Author-email: Kevin Deldycke <kevin@deldycke.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/kdeldycke/workflows
|
|
@@ -48,13 +48,14 @@ Requires-Dist: boltons>=24.0.0
|
|
|
48
48
|
Requires-Dist: bump-my-version<1.1.1,>=0.32.2
|
|
49
49
|
Requires-Dist: click-extra~=5.0.2
|
|
50
50
|
Requires-Dist: extra-platforms~=3.2.0
|
|
51
|
+
Requires-Dist: gitignore-parser~=0.1.12
|
|
51
52
|
Requires-Dist: packaging~=25.0
|
|
52
53
|
Requires-Dist: PyDriller~=2.6
|
|
53
54
|
Requires-Dist: pyproject-metadata~=0.9.0
|
|
54
55
|
Requires-Dist: pyyaml~=6.0.0
|
|
55
56
|
Requires-Dist: wcmatch>=8.5
|
|
56
57
|
Provides-Extra: test
|
|
57
|
-
Requires-Dist: coverage[toml]~=7.
|
|
58
|
+
Requires-Dist: coverage[toml]~=7.10.0; extra == "test"
|
|
58
59
|
Requires-Dist: pytest~=8.4.0; extra == "test"
|
|
59
60
|
Requires-Dist: pytest-cases~=3.9.1; extra == "test"
|
|
60
61
|
Requires-Dist: pytest-cov~=6.2.1; extra == "test"
|
|
@@ -116,8 +116,8 @@ def gha_utils():
|
|
|
116
116
|
)
|
|
117
117
|
@option(
|
|
118
118
|
"--format",
|
|
119
|
-
type=Choice(
|
|
120
|
-
default=
|
|
119
|
+
type=Choice(Dialects, case_sensitive=False),
|
|
120
|
+
default=Dialects.github,
|
|
121
121
|
help="Rendering format of the metadata.",
|
|
122
122
|
)
|
|
123
123
|
@option(
|
|
@@ -27,7 +27,7 @@ from boltons.iterutils import unique
|
|
|
27
27
|
RESERVED_MATRIX_KEYWORDS = ["include", "exclude"]
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class Matrix
|
|
30
|
+
class Matrix:
|
|
31
31
|
"""A matrix as defined by GitHub's actions workflows.
|
|
32
32
|
|
|
33
33
|
See GitHub official documentation on `how-to implement variations of jobs in a
|
|
@@ -47,35 +47,43 @@ class Matrix(FrozenDict):
|
|
|
47
47
|
matrix.
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
def __init__(self, *args, **kwargs):
|
|
51
|
+
self.variations: dict = {}
|
|
52
|
+
|
|
53
|
+
# Tuples are used to keep track of the insertion order and force immutability.
|
|
54
|
+
self.include: tuple[dict[str, str], ...] = tuple()
|
|
55
|
+
self.exclude: tuple[dict[str, str], ...] = tuple()
|
|
56
|
+
|
|
57
|
+
self._job_counter = None
|
|
53
58
|
|
|
54
59
|
def matrix(
|
|
55
60
|
self, ignore_includes: bool = False, ignore_excludes: bool = False
|
|
56
|
-
) ->
|
|
61
|
+
) -> FrozenDict[str, str]:
|
|
57
62
|
"""Returns a copy of the matrix.
|
|
58
63
|
|
|
59
64
|
The special ``include`` and ``excludes`` directives will be added by default.
|
|
60
65
|
You can selectively ignore them by passing the corresponding boolean parameters.
|
|
61
66
|
"""
|
|
62
|
-
dict_copy =
|
|
67
|
+
dict_copy = self.variations.copy()
|
|
63
68
|
if not ignore_includes and self.include:
|
|
64
69
|
dict_copy["include"] = self.include
|
|
65
70
|
if not ignore_excludes and self.exclude:
|
|
66
71
|
dict_copy["exclude"] = self.exclude
|
|
67
|
-
return dict_copy
|
|
72
|
+
return FrozenDict(dict_copy)
|
|
68
73
|
|
|
69
74
|
def __repr__(self) -> str:
|
|
70
|
-
return (
|
|
71
|
-
f"<{self.__class__.__name__}: {super(FrozenDict, self).__repr__()}; "
|
|
72
|
-
f"include={self.include}; exclude={self.exclude}>"
|
|
73
|
-
)
|
|
75
|
+
return f"<{self.__class__.__name__}: {self.matrix()}>"
|
|
74
76
|
|
|
75
77
|
def __str__(self) -> str:
|
|
76
78
|
"""Render matrix as a JSON string."""
|
|
77
79
|
return json.dumps(self.matrix())
|
|
78
80
|
|
|
81
|
+
def __getitem__(self, key: str) -> tuple[str, ...]:
|
|
82
|
+
"""Returns the values of a variation by its ID."""
|
|
83
|
+
if key in self.variations:
|
|
84
|
+
return self.variations[key]
|
|
85
|
+
raise KeyError(f"Variation {key} not found in matrix")
|
|
86
|
+
|
|
79
87
|
@staticmethod
|
|
80
88
|
def _check_ids(*var_ids: str) -> None:
|
|
81
89
|
for var_id in var_ids:
|
|
@@ -89,8 +97,8 @@ class Matrix(FrozenDict):
|
|
|
89
97
|
if any(type(v) is not str for v in values):
|
|
90
98
|
raise ValueError(f"Only strings are accepted in {values}")
|
|
91
99
|
# Extend variation with values, and deduplicate them along the way.
|
|
92
|
-
var_values = list(self.get(variation_id, [])) + list(values)
|
|
93
|
-
|
|
100
|
+
var_values = list(self.variations.get(variation_id, [])) + list(values)
|
|
101
|
+
self.variations[variation_id] = tuple(unique(var_values))
|
|
94
102
|
|
|
95
103
|
def _add_and_dedup_dicts(
|
|
96
104
|
self, *new_dicts: dict[str, str]
|
|
@@ -123,9 +131,9 @@ class Matrix(FrozenDict):
|
|
|
123
131
|
passing the corresponding ``with_matrix``, ``with_includes`` and
|
|
124
132
|
``with_excludes`` boolean filter parameters.
|
|
125
133
|
"""
|
|
126
|
-
|
|
134
|
+
all_variations = {}
|
|
127
135
|
if with_matrix:
|
|
128
|
-
|
|
136
|
+
all_variations = {k: list(v) for k, v in self.variations.items()}
|
|
129
137
|
|
|
130
138
|
for expand, directives in (
|
|
131
139
|
(with_includes, self.include),
|
|
@@ -134,9 +142,9 @@ class Matrix(FrozenDict):
|
|
|
134
142
|
if expand:
|
|
135
143
|
for value in directives:
|
|
136
144
|
for k, v in value.items():
|
|
137
|
-
|
|
145
|
+
all_variations.setdefault(k, []).append(v)
|
|
138
146
|
|
|
139
|
-
return {k: tuple(unique(v)) for k, v in
|
|
147
|
+
return {k: tuple(unique(v)) for k, v in all_variations.items()}
|
|
140
148
|
|
|
141
149
|
def product(
|
|
142
150
|
self, with_includes: bool = False, with_excludes: bool = False
|
|
@@ -148,17 +156,17 @@ class Matrix(FrozenDict):
|
|
|
148
156
|
|
|
149
157
|
Respects the order of variations and their values.
|
|
150
158
|
"""
|
|
151
|
-
|
|
159
|
+
all_variations = self.all_variations(
|
|
152
160
|
with_includes=with_includes, with_excludes=with_excludes
|
|
153
161
|
)
|
|
154
|
-
if not
|
|
162
|
+
if not all_variations:
|
|
155
163
|
return
|
|
156
164
|
yield from map(
|
|
157
165
|
dict,
|
|
158
166
|
itertools.product(
|
|
159
167
|
*(
|
|
160
168
|
tuple((variant_id, v) for v in variations)
|
|
161
|
-
for variant_id, variations in
|
|
169
|
+
for variant_id, variations in all_variations.items()
|
|
162
170
|
)
|
|
163
171
|
),
|
|
164
172
|
)
|
|
@@ -187,11 +195,11 @@ class Matrix(FrozenDict):
|
|
|
187
195
|
self.all_variations(
|
|
188
196
|
with_matrix=False, with_includes=True, with_excludes=True
|
|
189
197
|
)
|
|
190
|
-
).difference(self)
|
|
198
|
+
).difference(self.variations)
|
|
191
199
|
if unreferenced_keys:
|
|
192
200
|
raise ValueError(
|
|
193
201
|
f"Matrix exclude keys {list(unreferenced_keys)} does not match any "
|
|
194
|
-
f"{
|
|
202
|
+
f"{self.variations.keys()} key within the matrix"
|
|
195
203
|
)
|
|
196
204
|
|
|
197
205
|
# Reset the number of combinations.
|
|
@@ -202,7 +210,7 @@ class Matrix(FrozenDict):
|
|
|
202
210
|
|
|
203
211
|
# The matrix is empty, none of the include directive will match, so condider all
|
|
204
212
|
# directives as un-applicable.
|
|
205
|
-
if not self:
|
|
213
|
+
if not self.variations:
|
|
206
214
|
leftover_includes = list(self.include)
|
|
207
215
|
|
|
208
216
|
# Search for include directives that matches the original matrix variations
|
|
@@ -164,12 +164,13 @@ from operator import itemgetter
|
|
|
164
164
|
from pathlib import Path
|
|
165
165
|
from random import randint
|
|
166
166
|
from re import escape
|
|
167
|
-
from typing import Any, Final,
|
|
167
|
+
from typing import Any, Final, cast
|
|
168
168
|
|
|
169
169
|
from bumpversion.config import get_configuration # type: ignore[import-untyped]
|
|
170
170
|
from bumpversion.config.files import find_config_file # type: ignore[import-untyped]
|
|
171
171
|
from bumpversion.show import resolve_name # type: ignore[import-untyped]
|
|
172
172
|
from extra_platforms import is_github_ci
|
|
173
|
+
from gitignore_parser import parse_gitignore
|
|
173
174
|
from packaging.specifiers import SpecifierSet
|
|
174
175
|
from packaging.version import Version
|
|
175
176
|
from pydriller import Commit, Git, Repository # type: ignore[import-untyped]
|
|
@@ -196,6 +197,7 @@ SHORT_SHA_LENGTH = 7
|
|
|
196
197
|
depends on the size of the repository.
|
|
197
198
|
"""
|
|
198
199
|
|
|
200
|
+
GITIGNORE_PATH = Path(".gitignore")
|
|
199
201
|
|
|
200
202
|
NUITKA_BUILD_TARGETS = {
|
|
201
203
|
"linux-arm64": {
|
|
@@ -315,7 +317,7 @@ WorkflowEvent = StrEnum(
|
|
|
315
317
|
"""
|
|
316
318
|
|
|
317
319
|
|
|
318
|
-
Dialects = StrEnum("Dialects", ("github", "
|
|
320
|
+
Dialects = StrEnum("Dialects", ("github", "json"))
|
|
319
321
|
"""Dialects in which metadata can be formatted to."""
|
|
320
322
|
|
|
321
323
|
|
|
@@ -347,6 +349,19 @@ MYPY_VERSION_MIN: Final = (3, 8)
|
|
|
347
349
|
"""
|
|
348
350
|
|
|
349
351
|
|
|
352
|
+
class JSONMetadata(json.JSONEncoder):
|
|
353
|
+
"""Custom JSON encoder for metadata serialization."""
|
|
354
|
+
|
|
355
|
+
def default(self, o: Any) -> str:
|
|
356
|
+
if isinstance(o, Matrix):
|
|
357
|
+
return o.matrix()
|
|
358
|
+
|
|
359
|
+
if isinstance(o, Path):
|
|
360
|
+
return str(o)
|
|
361
|
+
|
|
362
|
+
return super().default(o)
|
|
363
|
+
|
|
364
|
+
|
|
350
365
|
class Metadata:
|
|
351
366
|
"""Metadata class."""
|
|
352
367
|
|
|
@@ -685,8 +700,11 @@ class Metadata:
|
|
|
685
700
|
else None
|
|
686
701
|
)
|
|
687
702
|
|
|
688
|
-
@
|
|
689
|
-
def
|
|
703
|
+
@cached_property
|
|
704
|
+
def gitignore_exists(self) -> bool:
|
|
705
|
+
return GITIGNORE_PATH.is_file()
|
|
706
|
+
|
|
707
|
+
def glob_files(self, *patterns: str) -> list[Path]:
|
|
690
708
|
"""Return all file path matching the ``patterns``.
|
|
691
709
|
|
|
692
710
|
Patterns are glob patterns supporting ``**`` for recursive search, and ``!``
|
|
@@ -695,46 +713,77 @@ class Metadata:
|
|
|
695
713
|
All directories are traversed, whether they are hidden (i.e. starting with a
|
|
696
714
|
dot ``.``) or not, including symlinks.
|
|
697
715
|
|
|
698
|
-
|
|
716
|
+
Skips:
|
|
717
|
+
|
|
718
|
+
- files which does not exists
|
|
719
|
+
- directories
|
|
720
|
+
- broken symlinks
|
|
721
|
+
- files matching patterns specified by ``.gitignore`` file
|
|
722
|
+
|
|
723
|
+
Returns both hidden and non-hidden files.
|
|
699
724
|
|
|
700
725
|
All files are normalized to their absolute path, so that duplicates produced by
|
|
701
726
|
symlinks are ignored.
|
|
702
727
|
|
|
703
|
-
|
|
728
|
+
File path are returned as relative to the current working directory if
|
|
729
|
+
possible, or as absolute path otherwise.
|
|
730
|
+
|
|
731
|
+
The resulting list of file paths is sorted.
|
|
704
732
|
"""
|
|
733
|
+
current_dir = Path.cwd()
|
|
705
734
|
seen = set()
|
|
735
|
+
|
|
736
|
+
# If the .gitignore file exists, we parse it to filter out ignored files.
|
|
737
|
+
gitignore = None
|
|
738
|
+
if self.gitignore_exists:
|
|
739
|
+
logging.debug(f"Load {GITIGNORE_PATH} to filter out ignored files.")
|
|
740
|
+
gitignore = parse_gitignore(GITIGNORE_PATH)
|
|
741
|
+
|
|
706
742
|
for file_path in iglob(
|
|
707
743
|
patterns,
|
|
708
744
|
flags=NODIR | GLOBSTAR | DOTGLOB | GLOBTILDE | BRACE | FOLLOW | NEGATE,
|
|
709
745
|
):
|
|
710
746
|
# Normalize the path to avoid duplicates.
|
|
711
747
|
try:
|
|
712
|
-
|
|
713
|
-
# Skip files that do not
|
|
748
|
+
absolute_path = Path(file_path).resolve(strict=True)
|
|
749
|
+
# Skip files that do not exists and broken symlinks.
|
|
714
750
|
except OSError:
|
|
715
|
-
logging.warning(
|
|
716
|
-
f"Skipping non-existing file / broken symlink: {file_path}"
|
|
717
|
-
)
|
|
751
|
+
logging.warning(f"Skip non-existing file / broken symlink: {file_path}")
|
|
718
752
|
continue
|
|
753
|
+
|
|
754
|
+
# Simplify the path by trying to make it relative to the current location.
|
|
755
|
+
normalized_path = absolute_path
|
|
756
|
+
try:
|
|
757
|
+
normalized_path = absolute_path.relative_to(current_dir)
|
|
758
|
+
except ValueError:
|
|
759
|
+
# If the file is not relative to the current directory, keep its
|
|
760
|
+
# absolute path.
|
|
761
|
+
logging.debug(
|
|
762
|
+
f"{absolute_path} is not relative to {current_dir}. "
|
|
763
|
+
"Keeping the path absolute."
|
|
764
|
+
)
|
|
765
|
+
|
|
719
766
|
if normalized_path in seen:
|
|
720
|
-
logging.debug(f"
|
|
767
|
+
logging.debug(f"Skip duplicate file: {normalized_path}")
|
|
721
768
|
continue
|
|
722
|
-
seen.add(normalized_path)
|
|
723
|
-
yield normalized_path
|
|
724
769
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
770
|
+
# Skip files that are ignored by .gitignore.
|
|
771
|
+
if gitignore and gitignore(file_path):
|
|
772
|
+
logging.debug(f"Skip file matching {GITIGNORE_PATH}: {file_path}")
|
|
773
|
+
continue
|
|
774
|
+
|
|
775
|
+
seen.add(normalized_path)
|
|
776
|
+
return sorted(seen)
|
|
728
777
|
|
|
729
778
|
@cached_property
|
|
730
|
-
def python_files(self) ->
|
|
779
|
+
def python_files(self) -> list[Path]:
|
|
731
780
|
"""Returns a list of python files."""
|
|
732
|
-
|
|
781
|
+
return self.glob_files("**/*.py", "!.venv/**")
|
|
733
782
|
|
|
734
783
|
@cached_property
|
|
735
|
-
def doc_files(self) ->
|
|
784
|
+
def doc_files(self) -> list[Path]:
|
|
736
785
|
"""Returns a list of doc files."""
|
|
737
|
-
|
|
786
|
+
return self.glob_files("**/*.{md,markdown,rst,tex}", "!.venv/**")
|
|
738
787
|
|
|
739
788
|
@property
|
|
740
789
|
def is_python_project(self):
|
|
@@ -844,7 +893,7 @@ class Metadata:
|
|
|
844
893
|
return None
|
|
845
894
|
|
|
846
895
|
@cached_property
|
|
847
|
-
def blacken_docs_params(self) ->
|
|
896
|
+
def blacken_docs_params(self) -> str | None:
|
|
848
897
|
"""Generates ``blacken-docs`` parameters.
|
|
849
898
|
|
|
850
899
|
`Blacken-docs reuses Black's --target-version pyXY parameters
|
|
@@ -867,7 +916,7 @@ class Metadata:
|
|
|
867
916
|
<https://github.com/psf/black/issues/751#issuecomment-473066811>`_.
|
|
868
917
|
"""
|
|
869
918
|
if self.py_target_versions:
|
|
870
|
-
return
|
|
919
|
+
return " ".join(
|
|
871
920
|
f"--target-version py{version.major}{version.minor}"
|
|
872
921
|
for version in self.py_target_versions
|
|
873
922
|
)
|
|
@@ -1185,7 +1234,7 @@ class Metadata:
|
|
|
1185
1234
|
for variations in matrix.solve():
|
|
1186
1235
|
# We will re-attach back this binary name to the with an include directive,
|
|
1187
1236
|
# so we need a copy the main variants it corresponds to.
|
|
1188
|
-
bin_name_include = {k: variations[k] for k in matrix}
|
|
1237
|
+
bin_name_include = {k: variations[k] for k in matrix.variations}
|
|
1189
1238
|
bin_name_include["bin_name"] = (
|
|
1190
1239
|
"{cli_id}-{target}-{short_sha}.{extension}"
|
|
1191
1240
|
).format(**variations)
|
|
@@ -1316,8 +1365,8 @@ class Metadata:
|
|
|
1316
1365
|
delimiter = f"ghadelimiter_{randint(10**8, (10**9) - 1)}"
|
|
1317
1366
|
content += f"{env_name}<<{delimiter}\n{env_value}\n{delimiter}\n"
|
|
1318
1367
|
else:
|
|
1319
|
-
assert dialect == Dialects.
|
|
1320
|
-
content =
|
|
1368
|
+
assert dialect == Dialects.json
|
|
1369
|
+
content = json.dumps(metadata, cls=JSONMetadata, indent=2)
|
|
1321
1370
|
|
|
1322
1371
|
logging.debug(f"Formatted metadata:\n{content}")
|
|
1323
1372
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gha-utils
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.18.0
|
|
4
4
|
Summary: ⚙️ CLI helpers for GitHub Actions + reuseable workflows
|
|
5
5
|
Author-email: Kevin Deldycke <kevin@deldycke.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/kdeldycke/workflows
|
|
@@ -48,13 +48,14 @@ Requires-Dist: boltons>=24.0.0
|
|
|
48
48
|
Requires-Dist: bump-my-version<1.1.1,>=0.32.2
|
|
49
49
|
Requires-Dist: click-extra~=5.0.2
|
|
50
50
|
Requires-Dist: extra-platforms~=3.2.0
|
|
51
|
+
Requires-Dist: gitignore-parser~=0.1.12
|
|
51
52
|
Requires-Dist: packaging~=25.0
|
|
52
53
|
Requires-Dist: PyDriller~=2.6
|
|
53
54
|
Requires-Dist: pyproject-metadata~=0.9.0
|
|
54
55
|
Requires-Dist: pyyaml~=6.0.0
|
|
55
56
|
Requires-Dist: wcmatch>=8.5
|
|
56
57
|
Provides-Extra: test
|
|
57
|
-
Requires-Dist: coverage[toml]~=7.
|
|
58
|
+
Requires-Dist: coverage[toml]~=7.10.0; extra == "test"
|
|
58
59
|
Requires-Dist: pytest~=8.4.0; extra == "test"
|
|
59
60
|
Requires-Dist: pytest-cases~=3.9.1; extra == "test"
|
|
60
61
|
Requires-Dist: pytest-cov~=6.2.1; extra == "test"
|
|
@@ -2,6 +2,7 @@ boltons>=24.0.0
|
|
|
2
2
|
bump-my-version<1.1.1,>=0.32.2
|
|
3
3
|
click-extra~=5.0.2
|
|
4
4
|
extra-platforms~=3.2.0
|
|
5
|
+
gitignore-parser~=0.1.12
|
|
5
6
|
packaging~=25.0
|
|
6
7
|
PyDriller~=2.6
|
|
7
8
|
pyproject-metadata~=0.9.0
|
|
@@ -9,7 +10,7 @@ pyyaml~=6.0.0
|
|
|
9
10
|
wcmatch>=8.5
|
|
10
11
|
|
|
11
12
|
[test]
|
|
12
|
-
coverage[toml]~=7.
|
|
13
|
+
coverage[toml]~=7.10.0
|
|
13
14
|
pytest~=8.4.0
|
|
14
15
|
pytest-cases~=3.9.1
|
|
15
16
|
pytest-cov~=6.2.1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
# Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
|
|
3
3
|
name = "gha-utils"
|
|
4
|
-
version = "4.
|
|
4
|
+
version = "4.18.0"
|
|
5
5
|
# Python versions and their status: https://devguide.python.org/versions/
|
|
6
6
|
requires-python = ">= 3.11"
|
|
7
7
|
description = "⚙️ CLI helpers for GitHub Actions + reuseable workflows"
|
|
@@ -76,6 +76,7 @@ dependencies = [
|
|
|
76
76
|
"bump-my-version >= 0.32.2, < 1.1.1",
|
|
77
77
|
"click-extra ~= 5.0.2",
|
|
78
78
|
"extra-platforms ~= 3.2.0",
|
|
79
|
+
"gitignore-parser ~= 0.1.12",
|
|
79
80
|
"packaging ~= 25.0",
|
|
80
81
|
"PyDriller ~= 2.6",
|
|
81
82
|
"pyproject-metadata ~= 0.9.0",
|
|
@@ -86,7 +87,7 @@ dependencies = [
|
|
|
86
87
|
|
|
87
88
|
[project.optional-dependencies]
|
|
88
89
|
test = [
|
|
89
|
-
"coverage [toml] ~= 7.
|
|
90
|
+
"coverage [toml] ~= 7.10.0",
|
|
90
91
|
"pytest ~= 8.4.0",
|
|
91
92
|
# More pytest plugins at: https://docs.pytest.org/en/latest/reference/plugin_list.html
|
|
92
93
|
"pytest-cases ~= 3.9.1",
|
|
@@ -137,7 +138,7 @@ addopts = [
|
|
|
137
138
|
xfail_strict = true
|
|
138
139
|
|
|
139
140
|
[tool.bumpversion]
|
|
140
|
-
current_version = "4.
|
|
141
|
+
current_version = "4.18.0"
|
|
141
142
|
allow_dirty = true
|
|
142
143
|
ignore_missing_files = true
|
|
143
144
|
|
|
@@ -26,7 +26,11 @@ from gha_utils.matrix import Matrix
|
|
|
26
26
|
def test_matrix():
|
|
27
27
|
matrix = Matrix()
|
|
28
28
|
|
|
29
|
-
assert matrix
|
|
29
|
+
assert isinstance(matrix, Matrix)
|
|
30
|
+
assert not isinstance(matrix, dict)
|
|
31
|
+
|
|
32
|
+
assert hasattr(matrix, "variations")
|
|
33
|
+
assert isinstance(matrix.variations, dict)
|
|
30
34
|
|
|
31
35
|
assert hasattr(matrix, "include")
|
|
32
36
|
assert hasattr(matrix, "exclude")
|
|
@@ -34,23 +38,20 @@ def test_matrix():
|
|
|
34
38
|
assert matrix.exclude == tuple()
|
|
35
39
|
|
|
36
40
|
matrix.add_variation("foo", ["a", "b", "c"])
|
|
37
|
-
assert matrix == {"foo": ("a", "b", "c")}
|
|
41
|
+
assert matrix.variations == {"foo": ("a", "b", "c")}
|
|
38
42
|
assert not matrix.include
|
|
39
43
|
assert not matrix.exclude
|
|
40
44
|
|
|
41
45
|
# Natural deduplication.
|
|
42
46
|
matrix.add_variation("foo", ["a", "a", "d"])
|
|
43
|
-
assert matrix == {"foo": ("a", "b", "c", "d")}
|
|
47
|
+
assert matrix.variations == {"foo": ("a", "b", "c", "d")}
|
|
44
48
|
assert not matrix.include
|
|
45
49
|
assert not matrix.exclude
|
|
46
50
|
|
|
47
51
|
assert matrix.matrix() == {"foo": ("a", "b", "c", "d")}
|
|
48
52
|
|
|
49
53
|
assert str(matrix) == '{"foo": ["a", "b", "c", "d"]}'
|
|
50
|
-
assert (
|
|
51
|
-
repr(matrix)
|
|
52
|
-
== "<Matrix: {'foo': ('a', 'b', 'c', 'd')}; include=(); exclude=()>"
|
|
53
|
-
)
|
|
54
|
+
assert repr(matrix) == "<Matrix: FrozenDict({'foo': ('a', 'b', 'c', 'd')})>"
|
|
54
55
|
|
|
55
56
|
with pytest.raises(ValueError):
|
|
56
57
|
matrix.add_variation("variation_1", None)
|
|
@@ -101,8 +102,8 @@ def test_includes():
|
|
|
101
102
|
'"include": [{"foo": "a", "bar": "1"}, {"foo": "b", "bar": "2"}]}'
|
|
102
103
|
)
|
|
103
104
|
assert (
|
|
104
|
-
repr(matrix) == "<Matrix: {'foo': ('a', 'b', 'c')
|
|
105
|
-
"include
|
|
105
|
+
repr(matrix) == "<Matrix: FrozenDict({'foo': ('a', 'b', 'c'), "
|
|
106
|
+
"'include': ({'foo': 'a', 'bar': '1'}, {'foo': 'b', 'bar': '2'})})>"
|
|
106
107
|
)
|
|
107
108
|
|
|
108
109
|
# Multiple insertions.
|
|
@@ -157,8 +158,8 @@ def test_excludes():
|
|
|
157
158
|
'"exclude": [{"foo": "a", "bar": "1"}, {"foo": "b", "bar": "2"}]}'
|
|
158
159
|
)
|
|
159
160
|
assert (
|
|
160
|
-
repr(matrix) == "<Matrix: {'foo': ('a', 'b', 'c')
|
|
161
|
-
"
|
|
161
|
+
repr(matrix) == "<Matrix: FrozenDict({'foo': ('a', 'b', 'c'), "
|
|
162
|
+
"'exclude': ({'foo': 'a', 'bar': '1'}, {'foo': 'b', 'bar': '2'})})>"
|
|
162
163
|
)
|
|
163
164
|
|
|
164
165
|
# Multiple insertions.
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
|
|
2
|
+
#
|
|
3
|
+
# This program is Free Software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of the GNU General Public License
|
|
5
|
+
# as published by the Free Software Foundation; either version 2
|
|
6
|
+
# of the License, or (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
from string import ascii_lowercase, digits
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
from extra_platforms import ALL_IDS, is_windows
|
|
26
|
+
|
|
27
|
+
from gha_utils.metadata import NUITKA_BUILD_TARGETS, Dialects, Metadata
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize("target_id, target_data", NUITKA_BUILD_TARGETS.items())
|
|
31
|
+
def test_nuitka_targets(target_id: str, target_data: dict[str, str]) -> None:
|
|
32
|
+
assert isinstance(target_id, str)
|
|
33
|
+
assert isinstance(target_data, dict)
|
|
34
|
+
|
|
35
|
+
assert set(target_data) == {
|
|
36
|
+
"os",
|
|
37
|
+
"platform_id",
|
|
38
|
+
"arch",
|
|
39
|
+
"extension",
|
|
40
|
+
}, f"Unexpected keys in target data for {target_id}"
|
|
41
|
+
|
|
42
|
+
assert isinstance(target_data["os"], str)
|
|
43
|
+
assert isinstance(target_data["platform_id"], str)
|
|
44
|
+
assert isinstance(target_data["arch"], str)
|
|
45
|
+
assert isinstance(target_data["extension"], str)
|
|
46
|
+
|
|
47
|
+
assert set(target_data["os"]).issubset(ascii_lowercase + digits + "-.")
|
|
48
|
+
assert target_data["platform_id"] in ALL_IDS
|
|
49
|
+
assert target_data["arch"] in {"arm64", "x64"}
|
|
50
|
+
assert set(target_data["extension"]).issubset(ascii_lowercase)
|
|
51
|
+
|
|
52
|
+
assert target_id == target_data["platform_id"] + "-" + target_data["arch"]
|
|
53
|
+
assert set(target_id).issubset(ascii_lowercase + digits + "-")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def regex(pattern: str) -> re.Pattern:
|
|
57
|
+
"""Compile a regex pattern with DOTALL flag."""
|
|
58
|
+
return re.compile(pattern, re.DOTALL)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def iter_checks(metadata: Any, expected: Any) -> None:
|
|
62
|
+
"""Recursively iterate over expected content and check it matches in metadata."""
|
|
63
|
+
|
|
64
|
+
if isinstance(expected, re.Pattern):
|
|
65
|
+
assert isinstance(metadata, str)
|
|
66
|
+
assert re.fullmatch(expected, metadata) is not None
|
|
67
|
+
|
|
68
|
+
elif isinstance(expected, dict):
|
|
69
|
+
assert isinstance(metadata, dict)
|
|
70
|
+
assert set(metadata) == set(expected)
|
|
71
|
+
for key, value in expected.items():
|
|
72
|
+
iter_checks(metadata[key], value)
|
|
73
|
+
|
|
74
|
+
elif isinstance(expected, list):
|
|
75
|
+
assert isinstance(metadata, list)
|
|
76
|
+
assert len(metadata) == len(expected)
|
|
77
|
+
for item in expected:
|
|
78
|
+
iter_checks(metadata[expected.index(item)], item)
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
assert metadata == expected
|
|
82
|
+
assert type(metadata) is type(expected)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
expected = {
|
|
86
|
+
"new_commits": None,
|
|
87
|
+
"release_commits": None,
|
|
88
|
+
"gitignore_exists": True,
|
|
89
|
+
"python_files": [
|
|
90
|
+
"gha_utils\\__init__.py",
|
|
91
|
+
"gha_utils\\__main__.py",
|
|
92
|
+
"gha_utils\\changelog.py",
|
|
93
|
+
"gha_utils\\cli.py",
|
|
94
|
+
"gha_utils\\mailmap.py",
|
|
95
|
+
"gha_utils\\matrix.py",
|
|
96
|
+
"gha_utils\\metadata.py",
|
|
97
|
+
"gha_utils\\test_plan.py",
|
|
98
|
+
"tests\\__init__.py",
|
|
99
|
+
"tests\\test_changelog.py",
|
|
100
|
+
"tests\\test_mailmap.py",
|
|
101
|
+
"tests\\test_matrix.py",
|
|
102
|
+
"tests\\test_metadata.py",
|
|
103
|
+
]
|
|
104
|
+
if is_windows()
|
|
105
|
+
else [
|
|
106
|
+
"gha_utils/__init__.py",
|
|
107
|
+
"gha_utils/__main__.py",
|
|
108
|
+
"gha_utils/changelog.py",
|
|
109
|
+
"gha_utils/cli.py",
|
|
110
|
+
"gha_utils/mailmap.py",
|
|
111
|
+
"gha_utils/matrix.py",
|
|
112
|
+
"gha_utils/metadata.py",
|
|
113
|
+
"gha_utils/test_plan.py",
|
|
114
|
+
"tests/__init__.py",
|
|
115
|
+
"tests/test_changelog.py",
|
|
116
|
+
"tests/test_mailmap.py",
|
|
117
|
+
"tests/test_matrix.py",
|
|
118
|
+
"tests/test_metadata.py",
|
|
119
|
+
],
|
|
120
|
+
"doc_files": [
|
|
121
|
+
".github\\code-of-conduct.md",
|
|
122
|
+
"changelog.md",
|
|
123
|
+
"readme.md",
|
|
124
|
+
]
|
|
125
|
+
if is_windows()
|
|
126
|
+
else [
|
|
127
|
+
".github/code-of-conduct.md",
|
|
128
|
+
"changelog.md",
|
|
129
|
+
"readme.md",
|
|
130
|
+
],
|
|
131
|
+
"is_python_project": True,
|
|
132
|
+
"package_name": "gha-utils",
|
|
133
|
+
"blacken_docs_params": "--target-version py311 --target-version py312 --target-version py313",
|
|
134
|
+
"mypy_params": "--python-version 3.11",
|
|
135
|
+
"current_version": regex(r"[0-9\.]+"),
|
|
136
|
+
"released_version": None,
|
|
137
|
+
"is_sphinx": False,
|
|
138
|
+
"active_autodoc": False,
|
|
139
|
+
"release_notes": regex(
|
|
140
|
+
r"### Changes\n\n"
|
|
141
|
+
r"> \[\!IMPORTANT\]\n"
|
|
142
|
+
r"> This version is not released yet and is under active development\.\n\n"
|
|
143
|
+
r".+"
|
|
144
|
+
),
|
|
145
|
+
"new_commits_matrix": None,
|
|
146
|
+
"release_commits_matrix": None,
|
|
147
|
+
"nuitka_matrix": {
|
|
148
|
+
"os": [
|
|
149
|
+
"ubuntu-24.04-arm",
|
|
150
|
+
"ubuntu-24.04",
|
|
151
|
+
"macos-15",
|
|
152
|
+
"macos-13",
|
|
153
|
+
"windows-11-arm",
|
|
154
|
+
"windows-2025",
|
|
155
|
+
],
|
|
156
|
+
"entry_point": ["gha-utils"],
|
|
157
|
+
"commit": [regex(r"[a-z0-9]+")],
|
|
158
|
+
"include": [
|
|
159
|
+
{
|
|
160
|
+
"target": "linux-arm64",
|
|
161
|
+
"os": "ubuntu-24.04-arm",
|
|
162
|
+
"platform_id": "linux",
|
|
163
|
+
"arch": "arm64",
|
|
164
|
+
"extension": "bin",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"target": "linux-x64",
|
|
168
|
+
"os": "ubuntu-24.04",
|
|
169
|
+
"platform_id": "linux",
|
|
170
|
+
"arch": "x64",
|
|
171
|
+
"extension": "bin",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"target": "macos-arm64",
|
|
175
|
+
"os": "macos-15",
|
|
176
|
+
"platform_id": "macos",
|
|
177
|
+
"arch": "arm64",
|
|
178
|
+
"extension": "bin",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"target": "macos-x64",
|
|
182
|
+
"os": "macos-13",
|
|
183
|
+
"platform_id": "macos",
|
|
184
|
+
"arch": "x64",
|
|
185
|
+
"extension": "bin",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"target": "windows-arm64",
|
|
189
|
+
"os": "windows-11-arm",
|
|
190
|
+
"platform_id": "windows",
|
|
191
|
+
"arch": "arm64",
|
|
192
|
+
"extension": "exe",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"target": "windows-x64",
|
|
196
|
+
"os": "windows-2025",
|
|
197
|
+
"platform_id": "windows",
|
|
198
|
+
"arch": "x64",
|
|
199
|
+
"extension": "exe",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"entry_point": "gha-utils",
|
|
203
|
+
"cli_id": "gha-utils",
|
|
204
|
+
"module_id": "gha_utils.__main__",
|
|
205
|
+
"callable_id": "main",
|
|
206
|
+
"module_path": regex(r"gha_utils(/|\\\\)__main__\.py"),
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
210
|
+
"short_sha": regex(r"[a-z0-9]+"),
|
|
211
|
+
"current_version": regex(r"[0-9\.]+"),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"os": "ubuntu-24.04-arm",
|
|
215
|
+
"entry_point": "gha-utils",
|
|
216
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
217
|
+
"bin_name": regex(r"gha-utils-linux-arm64-[a-z0-9]+\.bin"),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"os": "ubuntu-24.04",
|
|
221
|
+
"entry_point": "gha-utils",
|
|
222
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
223
|
+
"bin_name": regex(r"gha-utils-linux-x64-[a-z0-9]+\.bin"),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"os": "macos-15",
|
|
227
|
+
"entry_point": "gha-utils",
|
|
228
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
229
|
+
"bin_name": regex(r"gha-utils-macos-arm64-[a-z0-9]+\.bin"),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"os": "macos-13",
|
|
233
|
+
"entry_point": "gha-utils",
|
|
234
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
235
|
+
"bin_name": regex(r"gha-utils-macos-x64-[a-z0-9]+\.bin"),
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"os": "windows-11-arm",
|
|
239
|
+
"entry_point": "gha-utils",
|
|
240
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
241
|
+
"bin_name": regex(r"gha-utils-windows-arm64-[a-z0-9]+\.exe"),
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"os": "windows-2025",
|
|
245
|
+
"entry_point": "gha-utils",
|
|
246
|
+
"commit": regex(r"[a-z0-9]+"),
|
|
247
|
+
"bin_name": regex(r"gha-utils-windows-x64-[a-z0-9]+\.exe"),
|
|
248
|
+
},
|
|
249
|
+
{"state": "stable"},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_metadata_json_format():
|
|
256
|
+
metadata = Metadata().dump(Dialects.json)
|
|
257
|
+
assert isinstance(metadata, str)
|
|
258
|
+
|
|
259
|
+
iter_checks(json.loads(metadata), expected)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_metadata_github_format():
|
|
263
|
+
raw_metadata = Metadata().dump()
|
|
264
|
+
assert isinstance(raw_metadata, str)
|
|
265
|
+
|
|
266
|
+
# Prepare metadata for checks
|
|
267
|
+
metadata = {}
|
|
268
|
+
# Accumulation states.
|
|
269
|
+
acc_key = None
|
|
270
|
+
acc_delimiter = None
|
|
271
|
+
acc_lines = []
|
|
272
|
+
for line in raw_metadata.splitlines():
|
|
273
|
+
# We are at the end of the accumulation for a key.
|
|
274
|
+
if line == acc_delimiter:
|
|
275
|
+
assert acc_delimiter
|
|
276
|
+
assert acc_key
|
|
277
|
+
assert acc_lines
|
|
278
|
+
metadata[acc_key] = "\n".join(acc_lines)
|
|
279
|
+
# Reset accumulation states.
|
|
280
|
+
acc_key = None
|
|
281
|
+
acc_delimiter = None
|
|
282
|
+
acc_lines = []
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
# We are accumulating lines for a key.
|
|
286
|
+
if acc_key:
|
|
287
|
+
acc_lines.append(line)
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# We should not have any accumulation state at this point.
|
|
291
|
+
assert acc_key is None
|
|
292
|
+
assert acc_delimiter is None
|
|
293
|
+
assert acc_lines == []
|
|
294
|
+
|
|
295
|
+
# We are starting a new accumulation for a key.
|
|
296
|
+
if "<<" in line:
|
|
297
|
+
# Check the delimiter syntax.
|
|
298
|
+
assert line.count("<<") == 1
|
|
299
|
+
acc_key, acc_delimiter = line.split("<<", 1)
|
|
300
|
+
assert re.fullmatch(r"ghadelimiter_[0-9]+", acc_delimiter)
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
# We are at a simple key-value pair.
|
|
304
|
+
if "=" in line:
|
|
305
|
+
key, value = line.split("=", 1)
|
|
306
|
+
# Convert dict-like JSON string into Python dict.
|
|
307
|
+
if value.startswith("{"):
|
|
308
|
+
value = json.loads(value)
|
|
309
|
+
metadata[key] = value
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
raise ValueError(
|
|
313
|
+
f"Unexpected line format in metadata: {line!r}. "
|
|
314
|
+
"Expecting a key-value pair or a delimited block."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Adapt expected values to match GitHub Actions format.
|
|
318
|
+
github_format_expected = {}
|
|
319
|
+
for key, value in expected.items():
|
|
320
|
+
new_value = value
|
|
321
|
+
if value is None:
|
|
322
|
+
new_value = ""
|
|
323
|
+
elif isinstance(value, bool):
|
|
324
|
+
new_value = str(value).lower()
|
|
325
|
+
elif isinstance(value, list):
|
|
326
|
+
new_value = " ".join(f'"{i}"' for i in value)
|
|
327
|
+
github_format_expected[key] = new_value
|
|
328
|
+
|
|
329
|
+
iter_checks(metadata, github_format_expected)
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
|
|
2
|
-
#
|
|
3
|
-
# This program is Free Software; you can redistribute it and/or
|
|
4
|
-
# modify it under the terms of the GNU General Public License
|
|
5
|
-
# as published by the Free Software Foundation; either version 2
|
|
6
|
-
# of the License, or (at your option) any later version.
|
|
7
|
-
#
|
|
8
|
-
# This program is distributed in the hope that it will be useful,
|
|
9
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
-
# GNU General Public License for more details.
|
|
12
|
-
#
|
|
13
|
-
# You should have received a copy of the GNU General Public License
|
|
14
|
-
# along with this program; if not, write to the Free Software
|
|
15
|
-
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
import re
|
|
20
|
-
from string import ascii_lowercase, digits
|
|
21
|
-
|
|
22
|
-
from extra_platforms import ALL_IDS
|
|
23
|
-
|
|
24
|
-
from gha_utils.metadata import NUITKA_BUILD_TARGETS, Dialects, Metadata
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_nuitka_targets():
|
|
28
|
-
for target_id, target_data in NUITKA_BUILD_TARGETS.items():
|
|
29
|
-
assert isinstance(target_id, str)
|
|
30
|
-
assert isinstance(target_data, dict)
|
|
31
|
-
|
|
32
|
-
assert set(target_data) == {
|
|
33
|
-
"os",
|
|
34
|
-
"platform_id",
|
|
35
|
-
"arch",
|
|
36
|
-
"extension",
|
|
37
|
-
}, f"Unexpected keys in target data for {target_id}"
|
|
38
|
-
|
|
39
|
-
assert isinstance(target_data["os"], str)
|
|
40
|
-
assert isinstance(target_data["platform_id"], str)
|
|
41
|
-
assert isinstance(target_data["arch"], str)
|
|
42
|
-
assert isinstance(target_data["extension"], str)
|
|
43
|
-
|
|
44
|
-
assert set(target_data["os"]).issubset(ascii_lowercase + digits + "-.")
|
|
45
|
-
assert target_data["platform_id"] in ALL_IDS
|
|
46
|
-
assert target_data["arch"] in {"arm64", "x64"}
|
|
47
|
-
assert set(target_data["extension"]).issubset(ascii_lowercase)
|
|
48
|
-
|
|
49
|
-
assert target_id == target_data["platform_id"] + "-" + target_data["arch"]
|
|
50
|
-
assert set(target_id).issubset(ascii_lowercase + digits + "-")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_metadata_github_format():
|
|
54
|
-
metadata = Metadata()
|
|
55
|
-
|
|
56
|
-
assert re.fullmatch(
|
|
57
|
-
(
|
|
58
|
-
r"new_commits=\n"
|
|
59
|
-
r"release_commits=\n"
|
|
60
|
-
r"gitignore_exists=true\n"
|
|
61
|
-
r"python_files=[\S ]*\n"
|
|
62
|
-
r"doc_files=[\S ]*\n"
|
|
63
|
-
r"is_python_project=true\n"
|
|
64
|
-
r"package_name=gha-utils\n"
|
|
65
|
-
r"blacken_docs_params=--target-version py311 "
|
|
66
|
-
r"--target-version py312 --target-version py313\n"
|
|
67
|
-
r"mypy_params=--python-version 3\.11\n"
|
|
68
|
-
r"current_version=[0-9\.]+\n"
|
|
69
|
-
r"released_version=\n"
|
|
70
|
-
r"is_sphinx=false\n"
|
|
71
|
-
r"active_autodoc=false\n"
|
|
72
|
-
r"release_notes<<ghadelimiter_[0-9]+\n"
|
|
73
|
-
r"### Changes\n\n"
|
|
74
|
-
r"> \[\!IMPORTANT\]\n"
|
|
75
|
-
r"> This version is not released yet and is under active development.\n\n"
|
|
76
|
-
r".+\n"
|
|
77
|
-
r"ghadelimiter_[0-9]+\n"
|
|
78
|
-
r"new_commits_matrix=\n"
|
|
79
|
-
r"release_commits_matrix=\n"
|
|
80
|
-
#
|
|
81
|
-
r"nuitka_matrix=\{"
|
|
82
|
-
#
|
|
83
|
-
r'"os": \["ubuntu-24\.04-arm", "ubuntu-24\.04", '
|
|
84
|
-
r'"macos-15", "macos-13", "windows-11-arm", "windows-2025"\], '
|
|
85
|
-
#
|
|
86
|
-
r'"entry_point": \["gha-utils"\], '
|
|
87
|
-
#
|
|
88
|
-
r'"commit": \["[a-z0-9]+"\], '
|
|
89
|
-
#
|
|
90
|
-
r'"include": \['
|
|
91
|
-
#
|
|
92
|
-
r'\{"target": "linux-arm64", "os": "ubuntu-24\.04-arm", '
|
|
93
|
-
r'"platform_id": "linux", "arch": "arm64", "extension": "bin"\}, '
|
|
94
|
-
r'\{"target": "linux-x64", "os": "ubuntu-24\.04", '
|
|
95
|
-
r'"platform_id": "linux", "arch": "x64", "extension": "bin"\}, '
|
|
96
|
-
r'\{"target": "macos-arm64", "os": "macos-15", '
|
|
97
|
-
r'"platform_id": "macos", "arch": "arm64", "extension": "bin"\}, '
|
|
98
|
-
r'\{"target": "macos-x64", "os": "macos-13", '
|
|
99
|
-
r'"platform_id": "macos", "arch": "x64", '
|
|
100
|
-
r'"extension": "bin"\}, '
|
|
101
|
-
r'\{"target": "windows-arm64", "os": "windows-11-arm", '
|
|
102
|
-
r'"platform_id": "windows", "arch": "arm64", "extension": "exe"\}, '
|
|
103
|
-
r'\{"target": "windows-x64", "os": "windows-2025", '
|
|
104
|
-
r'"platform_id": "windows", "arch": "x64", "extension": "exe"\}, '
|
|
105
|
-
#
|
|
106
|
-
r'\{"entry_point": "gha-utils", '
|
|
107
|
-
r'"cli_id": "gha-utils", "module_id": "gha_utils\.__main__", '
|
|
108
|
-
r'"callable_id": "main", '
|
|
109
|
-
r'"module_path": "gha_utils(/|\\\\)__main__\.py"\}, '
|
|
110
|
-
#
|
|
111
|
-
r'\{"commit": "[a-z0-9]+", "short_sha": "[a-z0-9]+", '
|
|
112
|
-
r'"current_version": "[0-9\.]+"\}, '
|
|
113
|
-
#
|
|
114
|
-
r'\{"os": "ubuntu-24\.04-arm", "entry_point": "gha-utils", '
|
|
115
|
-
r'"commit": "[a-z0-9]+", '
|
|
116
|
-
r'"bin_name": "gha-utils-linux-arm64-[a-z0-9]+\.bin"\}, '
|
|
117
|
-
r'\{"os": "ubuntu-24\.04", "entry_point": "gha-utils", '
|
|
118
|
-
r'"commit": "[a-z0-9]+", '
|
|
119
|
-
r'"bin_name": "gha-utils-linux-x64-[a-z0-9]+\.bin"\}, '
|
|
120
|
-
r'\{"os": "macos-15", "entry_point": "gha-utils", '
|
|
121
|
-
r'"commit": "[a-z0-9]+", '
|
|
122
|
-
r'"bin_name": "gha-utils-macos-arm64-[a-z0-9]+\.bin"\}, '
|
|
123
|
-
r'\{"os": "macos-13", "entry_point": "gha-utils", '
|
|
124
|
-
r'"commit": "[a-z0-9]+", '
|
|
125
|
-
r'"bin_name": "gha-utils-macos-x64-[a-z0-9]+\.bin"\}, '
|
|
126
|
-
r'\{"os": "windows-11-arm", "entry_point": "gha-utils", '
|
|
127
|
-
r'"commit": "[a-z0-9]+", '
|
|
128
|
-
r'"bin_name": "gha-utils-windows-arm64-[a-z0-9]+\.exe"\}, '
|
|
129
|
-
r'\{"os": "windows-2025", "entry_point": "gha-utils", '
|
|
130
|
-
r'"commit": "[a-z0-9]+", '
|
|
131
|
-
r'"bin_name": "gha-utils-windows-x64-[a-z0-9]+\.exe"\}, '
|
|
132
|
-
r'\{"state": "stable"\}\]\}\n'
|
|
133
|
-
),
|
|
134
|
-
metadata.dump(Dialects.github),
|
|
135
|
-
re.DOTALL,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def test_metadata_plain_format():
|
|
140
|
-
metadata = Metadata()
|
|
141
|
-
|
|
142
|
-
assert re.fullmatch(
|
|
143
|
-
(
|
|
144
|
-
r"\{"
|
|
145
|
-
r"'new_commits': None, "
|
|
146
|
-
r"'release_commits': None, "
|
|
147
|
-
r"'gitignore_exists': True, "
|
|
148
|
-
r"'python_files': <generator object Metadata\.python_files at \S+>, "
|
|
149
|
-
r"'doc_files': <generator object Metadata\.doc_files at \S+>, "
|
|
150
|
-
r"'is_python_project': True, "
|
|
151
|
-
r"'package_name': 'gha-utils', "
|
|
152
|
-
r"'blacken_docs_params': \("
|
|
153
|
-
r"'--target-version py311', "
|
|
154
|
-
r"'--target-version py312', "
|
|
155
|
-
r"'--target-version py313'\), "
|
|
156
|
-
r"'mypy_params': '--python-version 3\.11', "
|
|
157
|
-
r"'current_version': '[0-9\.]+', "
|
|
158
|
-
r"'released_version': None, "
|
|
159
|
-
r"'is_sphinx': False, "
|
|
160
|
-
r"'active_autodoc': False, "
|
|
161
|
-
r"'release_notes': '### Changes\\n\\n"
|
|
162
|
-
r"> \[\!IMPORTANT\]\\n"
|
|
163
|
-
r"> This version is not released yet and is under active development.\\n\\n"
|
|
164
|
-
r".+', "
|
|
165
|
-
r"'new_commits_matrix': None, "
|
|
166
|
-
r"'release_commits_matrix': None, "
|
|
167
|
-
r"'nuitka_matrix': <Matrix: \{"
|
|
168
|
-
r"'os': \('ubuntu-24\.04-arm', 'ubuntu-24\.04', "
|
|
169
|
-
r"'macos-15', 'macos-13', 'windows-11-arm', 'windows-2025'\), "
|
|
170
|
-
r"'entry_point': \('gha-utils',\), "
|
|
171
|
-
r"'commit': \('[a-z0-9]+',\)\}; "
|
|
172
|
-
#
|
|
173
|
-
r"include=\(\{'target': 'linux-arm64', 'os': 'ubuntu-24\.04-arm', "
|
|
174
|
-
r"'platform_id': 'linux', 'arch': 'arm64', 'extension': 'bin'\}, "
|
|
175
|
-
r"\{'target': 'linux-x64', 'os': 'ubuntu-24\.04', 'platform_id': 'linux', "
|
|
176
|
-
r"'arch': 'x64', 'extension': 'bin'\}, \{'target': 'macos-arm64', 'os': 'macos-15', "
|
|
177
|
-
r"'platform_id': 'macos', 'arch': 'arm64', 'extension': 'bin'\}, "
|
|
178
|
-
r"\{'target': 'macos-x64', 'os': 'macos-13', 'platform_id': 'macos', 'arch': 'x64', "
|
|
179
|
-
r"'extension': 'bin'\}, \{'target': 'windows-arm64', 'os': 'windows-11-arm', 'platform_id': "
|
|
180
|
-
r"'windows', 'arch': 'arm64', 'extension': 'exe'\}, "
|
|
181
|
-
r"\{'target': 'windows-x64', 'os': 'windows-2025', 'platform_id': "
|
|
182
|
-
r"'windows', 'arch': 'x64', 'extension': 'exe'\}, "
|
|
183
|
-
#
|
|
184
|
-
r"\{'entry_point': 'gha-utils', 'cli_id': 'gha-utils', "
|
|
185
|
-
r"'module_id': 'gha_utils\.__main__', 'callable_id': 'main', "
|
|
186
|
-
r"'module_path': 'gha_utils(/|\\\\)__main__\.py'\}, "
|
|
187
|
-
#
|
|
188
|
-
r"\{'commit': '[a-z0-9]+', 'short_sha': '[a-z0-9]+', "
|
|
189
|
-
r"'current_version': '[0-9\.]+'\}, "
|
|
190
|
-
#
|
|
191
|
-
r"\{'os': 'ubuntu-24\.04-arm', 'entry_point': 'gha-utils', "
|
|
192
|
-
r"'commit': '[a-z0-9]+', "
|
|
193
|
-
r"'bin_name': 'gha-utils-linux-arm64-[a-z0-9]+\.bin'\}, "
|
|
194
|
-
r"\{'os': 'ubuntu-24\.04', 'entry_point': 'gha-utils', "
|
|
195
|
-
r"'commit': '[a-z0-9]+', "
|
|
196
|
-
r"'bin_name': 'gha-utils-linux-x64-[a-z0-9]+\.bin'\}, "
|
|
197
|
-
r"\{'os': 'macos-15', 'entry_point': 'gha-utils', "
|
|
198
|
-
r"'commit': '[a-z0-9]+', "
|
|
199
|
-
r"'bin_name': 'gha-utils-macos-arm64-[a-z0-9]+\.bin'\}, "
|
|
200
|
-
r"\{'os': 'macos-13', 'entry_point': 'gha-utils', "
|
|
201
|
-
r"'commit': '[a-z0-9]+', 'bin_name': "
|
|
202
|
-
r"'gha-utils-macos-x64-[a-z0-9]+\.bin'\}, "
|
|
203
|
-
r"\{'os': 'windows-11-arm', 'entry_point': 'gha-utils', "
|
|
204
|
-
r"'commit': '[a-z0-9]+', "
|
|
205
|
-
r"'bin_name': 'gha-utils-windows-arm64-[a-z0-9]+\.exe'\}, "
|
|
206
|
-
r"\{'os': 'windows-2025', 'entry_point': 'gha-utils', "
|
|
207
|
-
r"'commit': '[a-z0-9]+', "
|
|
208
|
-
r"'bin_name': 'gha-utils-windows-x64-[a-z0-9]+\.exe'\}, "
|
|
209
|
-
r"\{'state': 'stable'\}\); "
|
|
210
|
-
r"exclude=\(\)>\}"
|
|
211
|
-
),
|
|
212
|
-
metadata.dump(Dialects.plain),
|
|
213
|
-
re.DOTALL,
|
|
214
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|