gha-utils 4.13.4__tar.gz → 4.14.1__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.13.4 → gha_utils-4.14.1}/PKG-INFO +10 -4
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/__init__.py +1 -1
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/cli.py +42 -2
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/mailmap.py +1 -1
- gha_utils-4.14.1/gha_utils/matrix.py +279 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/metadata.py +48 -120
- gha_utils-4.14.1/gha_utils/test_plan.py +246 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils.egg-info/PKG-INFO +10 -4
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils.egg-info/SOURCES.txt +6 -1
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils.egg-info/requires.txt +5 -1
- {gha_utils-4.13.4 → gha_utils-4.14.1}/pyproject.toml +9 -3
- {gha_utils-4.13.4 → gha_utils-4.14.1}/readme.md +5 -2
- gha_utils-4.14.1/tests/test_mailmap.py +73 -0
- gha_utils-4.14.1/tests/test_matrix.py +662 -0
- gha_utils-4.14.1/tests/test_metadata.py +143 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/__main__.py +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/changelog.py +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils/py.typed +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils.egg-info/dependency_links.txt +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils.egg-info/entry_points.txt +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/gha_utils.egg-info/top_level.txt +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/setup.cfg +0 -0
- {gha_utils-4.13.4 → gha_utils-4.14.1}/tests/test_changelog.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: gha-utils
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.14.1
|
|
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,10 +48,11 @@ Description-Content-Type: text/markdown
|
|
|
48
48
|
Requires-Dist: backports.strenum~=1.3.1; python_version < "3.11"
|
|
49
49
|
Requires-Dist: boltons>=24.0.0
|
|
50
50
|
Requires-Dist: bump-my-version>=0.21.0
|
|
51
|
-
Requires-Dist: click-extra~=4.
|
|
51
|
+
Requires-Dist: click-extra~=4.14.1
|
|
52
52
|
Requires-Dist: packaging~=24.1
|
|
53
53
|
Requires-Dist: PyDriller~=2.6
|
|
54
54
|
Requires-Dist: pyproject-metadata~=0.9.0
|
|
55
|
+
Requires-Dist: pyyaml~=6.0.0
|
|
55
56
|
Requires-Dist: tomli~=2.0.1; python_version < "3.11"
|
|
56
57
|
Requires-Dist: wcmatch>=8.5
|
|
57
58
|
Provides-Extra: test
|
|
@@ -61,6 +62,8 @@ Requires-Dist: pytest-cases~=3.8.3; extra == "test"
|
|
|
61
62
|
Requires-Dist: pytest-cov~=6.0.0; extra == "test"
|
|
62
63
|
Requires-Dist: pytest-github-actions-annotate-failures~=0.3.0; extra == "test"
|
|
63
64
|
Requires-Dist: pytest-randomly~=3.16.0; extra == "test"
|
|
65
|
+
Provides-Extra: typing
|
|
66
|
+
Requires-Dist: types-PyYAML~=6.0.12.9; extra == "typing"
|
|
64
67
|
|
|
65
68
|
# `gha-utils` CLI + reusable workflows
|
|
66
69
|
|
|
@@ -100,7 +103,6 @@ Thanks to `uv`, you can install and run `gha-utils` in one command, without poll
|
|
|
100
103
|
|
|
101
104
|
```shell-session
|
|
102
105
|
$ uvx gha-utils
|
|
103
|
-
Installed 45 packages in 45ms
|
|
104
106
|
Usage: gha-utils [OPTIONS] COMMAND [ARGS]...
|
|
105
107
|
|
|
106
108
|
Options:
|
|
@@ -115,8 +117,11 @@ Options:
|
|
|
115
117
|
utils/*.{toml,yaml,yml,json,ini,xml}]
|
|
116
118
|
--show-params Show all CLI parameters, their provenance, defaults
|
|
117
119
|
and value, then exit.
|
|
118
|
-
|
|
120
|
+
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
|
|
119
121
|
[default: WARNING]
|
|
122
|
+
-v, --verbose Increase the default WARNING verbosity by one level
|
|
123
|
+
for each additional repetition of the option.
|
|
124
|
+
[default: 0]
|
|
120
125
|
--version Show the version and exit.
|
|
121
126
|
-h, --help Show this message and exit.
|
|
122
127
|
|
|
@@ -124,6 +129,7 @@ Commands:
|
|
|
124
129
|
changelog Maintain a Markdown-formatted changelog
|
|
125
130
|
mailmap-sync Update Git's .mailmap file with missing contributors
|
|
126
131
|
metadata Output project metadata
|
|
132
|
+
test-plan Run a test plan from a file against a binary
|
|
127
133
|
```
|
|
128
134
|
|
|
129
135
|
```shell-session
|
|
@@ -23,22 +23,24 @@ from datetime import datetime
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import IO
|
|
25
25
|
|
|
26
|
+
import click
|
|
26
27
|
from click_extra import (
|
|
27
28
|
Choice,
|
|
28
29
|
Context,
|
|
30
|
+
FloatRange,
|
|
29
31
|
argument,
|
|
30
32
|
echo,
|
|
31
33
|
extra_group,
|
|
32
34
|
file_path,
|
|
33
35
|
option,
|
|
34
36
|
pass_context,
|
|
35
|
-
path,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
from . import __version__
|
|
39
40
|
from .changelog import Changelog
|
|
40
41
|
from .mailmap import Mailmap
|
|
41
42
|
from .metadata import Dialects, Metadata
|
|
43
|
+
from .test_plan import DEFAULT_TEST_PLAN, parse_test_plan
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def is_stdout(filepath: Path) -> bool:
|
|
@@ -167,7 +169,7 @@ def metadata(ctx, format, overwrite, output_path):
|
|
|
167
169
|
@gha_utils.command(short_help="Maintain a Markdown-formatted changelog")
|
|
168
170
|
@option(
|
|
169
171
|
"--source",
|
|
170
|
-
type=
|
|
172
|
+
type=file_path(exists=True, readable=True, resolve_path=True),
|
|
171
173
|
default="changelog.md",
|
|
172
174
|
help="Changelog source file in Markdown format.",
|
|
173
175
|
)
|
|
@@ -268,3 +270,41 @@ def mailmap_sync(ctx, source, create_if_missing, destination_mailmap):
|
|
|
268
270
|
ctx.exit()
|
|
269
271
|
|
|
270
272
|
echo(generate_header(ctx) + new_content, file=prep_path(destination_mailmap))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@gha_utils.command(short_help="Run a test plan from a file against a binary")
|
|
276
|
+
@option(
|
|
277
|
+
"--binary",
|
|
278
|
+
# XXX Wait for https://github.com/janluke/cloup/issues/185 to use the
|
|
279
|
+
# `file_path` type.
|
|
280
|
+
type=click.Path(exists=True, executable=True, resolve_path=True),
|
|
281
|
+
required=True,
|
|
282
|
+
help="Path to the binary to test.",
|
|
283
|
+
)
|
|
284
|
+
@option(
|
|
285
|
+
"--plan",
|
|
286
|
+
type=file_path(exists=True, readable=True, resolve_path=True),
|
|
287
|
+
help="Test plan in YAML.",
|
|
288
|
+
)
|
|
289
|
+
@option(
|
|
290
|
+
"-t",
|
|
291
|
+
"--timeout",
|
|
292
|
+
# Timeout passed to subprocess.run() is a float that is silently clamped to
|
|
293
|
+
# 0.0 is negative values are provided, so we mimic this behavior here:
|
|
294
|
+
# https://github.com/python/cpython/blob/5740b95076b57feb6293cda4f5504f706a7d622d/Lib/subprocess.py#L1596-L1597
|
|
295
|
+
type=FloatRange(min=0, clamp=True),
|
|
296
|
+
help="Set the default timeout for each CLI call, if not specified in the "
|
|
297
|
+
"test plan.",
|
|
298
|
+
)
|
|
299
|
+
def test_plan(binary: Path, plan: Path | None, timeout: float | None) -> None:
|
|
300
|
+
# Load test plan from workflow input, or use a default one.
|
|
301
|
+
if plan:
|
|
302
|
+
logging.debug(f"Read test plan from {plan}")
|
|
303
|
+
test_plan = parse_test_plan(plan)
|
|
304
|
+
else:
|
|
305
|
+
logging.warning(f"No test plan provided. Default to: {DEFAULT_TEST_PLAN}")
|
|
306
|
+
test_plan = DEFAULT_TEST_PLAN # type: ignore[assignment]
|
|
307
|
+
|
|
308
|
+
for index, test_case in enumerate(test_plan):
|
|
309
|
+
logging.info(f"Run test #{index}")
|
|
310
|
+
test_case.check_cli_test(binary, default_timeout=timeout)
|
|
@@ -22,7 +22,7 @@ from dataclasses import dataclass, field
|
|
|
22
22
|
from functools import cached_property
|
|
23
23
|
from subprocess import run
|
|
24
24
|
|
|
25
|
-
from boltons.iterutils import unique
|
|
25
|
+
from boltons.iterutils import unique
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
@dataclass(order=True, frozen=True)
|
|
@@ -0,0 +1,279 @@
|
|
|
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 itertools
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Iterable, Iterator
|
|
23
|
+
|
|
24
|
+
from boltons.dictutils import FrozenDict
|
|
25
|
+
from boltons.iterutils import unique
|
|
26
|
+
|
|
27
|
+
RESERVED_MATRIX_KEYWORDS = ["include", "exclude"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Matrix(FrozenDict):
|
|
31
|
+
"""A matrix as defined by GitHub's actions workflows.
|
|
32
|
+
|
|
33
|
+
See GitHub official documentation on `how-to implement variations of jobs in a
|
|
34
|
+
workflow
|
|
35
|
+
<https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow>`_.
|
|
36
|
+
|
|
37
|
+
This Matrix behave like a ``dict`` and works everywhere a ``dict`` would. Only that
|
|
38
|
+
it is immutable and based on :class:`FrozenDict`. If you want to populate the matrix
|
|
39
|
+
you have to use the following methods:
|
|
40
|
+
|
|
41
|
+
- :meth:`add_variation`
|
|
42
|
+
- :meth:`add_includes`
|
|
43
|
+
- :meth:`add_excludes`
|
|
44
|
+
|
|
45
|
+
The implementation respects the order in which items were inserted. This provides a
|
|
46
|
+
natural and visual sorting that should ease the inspection and debugging of large
|
|
47
|
+
matrix.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Tuples are used to keep track of the insertion order and force immutability.
|
|
51
|
+
include: tuple[dict[str, str], ...] = tuple()
|
|
52
|
+
exclude: tuple[dict[str, str], ...] = tuple()
|
|
53
|
+
|
|
54
|
+
def matrix(
|
|
55
|
+
self, ignore_includes: bool = False, ignore_excludes: bool = False
|
|
56
|
+
) -> dict[str, str]:
|
|
57
|
+
"""Returns a copy of the matrix.
|
|
58
|
+
|
|
59
|
+
The special ``include`` and ``excludes`` directives will be added by default.
|
|
60
|
+
You can selectively ignore them by passing the corresponding boolean parameters.
|
|
61
|
+
"""
|
|
62
|
+
dict_copy = dict(self)
|
|
63
|
+
if not ignore_includes and self.include:
|
|
64
|
+
dict_copy["include"] = self.include
|
|
65
|
+
if not ignore_excludes and self.exclude:
|
|
66
|
+
dict_copy["exclude"] = self.exclude
|
|
67
|
+
return dict_copy
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
return (
|
|
71
|
+
f"<{self.__class__.__name__}: {super(FrozenDict, self).__repr__()}; "
|
|
72
|
+
f"include={self.include}; exclude={self.exclude}>"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def __str__(self) -> str:
|
|
76
|
+
"""Render matrix as a JSON string."""
|
|
77
|
+
return json.dumps(self.matrix())
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _check_ids(*var_ids: str) -> None:
|
|
81
|
+
for var_id in var_ids:
|
|
82
|
+
if var_id in RESERVED_MATRIX_KEYWORDS:
|
|
83
|
+
raise ValueError(f"{var_id} cannot be used as a variation ID")
|
|
84
|
+
|
|
85
|
+
def add_variation(self, variation_id: str, values: Iterable[str]) -> None:
|
|
86
|
+
self._check_ids(variation_id)
|
|
87
|
+
if not values:
|
|
88
|
+
raise ValueError(f"No variation values provided: {values}")
|
|
89
|
+
if any(type(v) is not str for v in values):
|
|
90
|
+
raise ValueError(f"Only strings are accepted in {values}")
|
|
91
|
+
# Extend variation with values, and deduplicate them along the way.
|
|
92
|
+
var_values = list(self.get(variation_id, [])) + list(values)
|
|
93
|
+
super(FrozenDict, self).__setitem__(variation_id, tuple(unique(var_values)))
|
|
94
|
+
|
|
95
|
+
def _add_and_dedup_dicts(
|
|
96
|
+
self, *new_dicts: dict[str, str]
|
|
97
|
+
) -> tuple[dict[str, str], ...]:
|
|
98
|
+
self._check_ids(*(k for d in new_dicts for k in d))
|
|
99
|
+
return tuple(
|
|
100
|
+
dict(items) for items in unique((tuple(d.items()) for d in new_dicts))
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def add_includes(self, *new_includes: dict[str, str]) -> None:
|
|
104
|
+
"""Add one or more ``include`` special directives to the matrix."""
|
|
105
|
+
self.include = self._add_and_dedup_dicts(*self.include, *new_includes)
|
|
106
|
+
|
|
107
|
+
def add_excludes(self, *new_excludes: dict[str, str]) -> None:
|
|
108
|
+
"""Add one or more ``exclude`` special directives to the matrix."""
|
|
109
|
+
self.exclude = self._add_and_dedup_dicts(*self.exclude, *new_excludes)
|
|
110
|
+
|
|
111
|
+
def all_variations(
|
|
112
|
+
self,
|
|
113
|
+
with_matrix: bool = True,
|
|
114
|
+
with_includes: bool = False,
|
|
115
|
+
with_excludes: bool = False,
|
|
116
|
+
) -> dict[str, tuple[str, ...]]:
|
|
117
|
+
"""Collect all variations encountered in the matrix.
|
|
118
|
+
|
|
119
|
+
Extra variations mentioned in the special ``include`` and ``exclude``
|
|
120
|
+
directives will be ignored by default.
|
|
121
|
+
|
|
122
|
+
You can selectively expand or restrict the resulting inventory of variations by
|
|
123
|
+
passing the corresponding ``with_matrix``, ``with_includes`` and
|
|
124
|
+
``with_excludes`` boolean filter parameters.
|
|
125
|
+
"""
|
|
126
|
+
variations = {}
|
|
127
|
+
if with_matrix:
|
|
128
|
+
variations = {k: list(v) for k, v in self.items()}
|
|
129
|
+
|
|
130
|
+
for expand, directives in (
|
|
131
|
+
(with_includes, self.include),
|
|
132
|
+
(with_excludes, self.exclude),
|
|
133
|
+
):
|
|
134
|
+
if expand:
|
|
135
|
+
for value in directives:
|
|
136
|
+
for k, v in value.items():
|
|
137
|
+
variations.setdefault(k, []).append(v)
|
|
138
|
+
|
|
139
|
+
return {k: tuple(unique(v)) for k, v in variations.items()}
|
|
140
|
+
|
|
141
|
+
def product(
|
|
142
|
+
self, with_includes: bool = False, with_excludes: bool = False
|
|
143
|
+
) -> Iterator[dict[str, str]]:
|
|
144
|
+
"""Only returns the combinations of the base matrix by default.
|
|
145
|
+
|
|
146
|
+
You can optionally add any variation referenced in the ``include`` and
|
|
147
|
+
``exclude`` special directives.
|
|
148
|
+
|
|
149
|
+
Respects the order of variations and their values.
|
|
150
|
+
"""
|
|
151
|
+
variations = self.all_variations(
|
|
152
|
+
with_includes=with_includes, with_excludes=with_excludes
|
|
153
|
+
)
|
|
154
|
+
if not variations:
|
|
155
|
+
return
|
|
156
|
+
yield from map(
|
|
157
|
+
dict,
|
|
158
|
+
itertools.product(
|
|
159
|
+
*(
|
|
160
|
+
tuple((variant_id, v) for v in variations)
|
|
161
|
+
for variant_id, variations in variations.items()
|
|
162
|
+
)
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def _count_job(self) -> None:
|
|
167
|
+
self._job_counter += 1
|
|
168
|
+
if self._job_counter > 256:
|
|
169
|
+
logging.critical("GitHub job matrix limit of 256 jobs reached")
|
|
170
|
+
|
|
171
|
+
def solve(self, strict: bool = False) -> Iterator[dict[str, str]]:
|
|
172
|
+
"""Returns all combinations and apply ``include`` and ``exclude`` constraints.
|
|
173
|
+
|
|
174
|
+
.. caution::
|
|
175
|
+
As per GitHub specifications, all ``include`` combinations are processed
|
|
176
|
+
after ``exclude``. This allows you to use ``include`` to add back
|
|
177
|
+
combinations that were previously excluded.
|
|
178
|
+
"""
|
|
179
|
+
# GitHub jobs fails with the following message if the exclude directive is
|
|
180
|
+
# referencing keys that are not present in the original base matrix:
|
|
181
|
+
# Invalid workflow file: .github/workflows/tests.yaml#L48
|
|
182
|
+
# The workflow is not valid.
|
|
183
|
+
# .github/workflows/tests.yaml (Line: 48, Col: 13): Matrix exclude key 'state'
|
|
184
|
+
# does not match any key within the matrix
|
|
185
|
+
if strict:
|
|
186
|
+
unreferenced_keys = set(
|
|
187
|
+
self.all_variations(
|
|
188
|
+
with_matrix=False, with_includes=True, with_excludes=True
|
|
189
|
+
)
|
|
190
|
+
).difference(self)
|
|
191
|
+
if unreferenced_keys:
|
|
192
|
+
raise ValueError(
|
|
193
|
+
f"Matrix exclude keys {list(unreferenced_keys)} does not match any "
|
|
194
|
+
f"{list(self)} key within the matrix"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Reset the number of combinations.
|
|
198
|
+
self._job_counter = 0
|
|
199
|
+
|
|
200
|
+
applicable_includes = []
|
|
201
|
+
leftover_includes: list[dict[str, str]] = []
|
|
202
|
+
|
|
203
|
+
# The matrix is empty, none of the include directive will match, so condider all
|
|
204
|
+
# directives as un-applicable.
|
|
205
|
+
if not self:
|
|
206
|
+
leftover_includes = list(self.include)
|
|
207
|
+
|
|
208
|
+
# Search for include directives that matches the original matrix variations
|
|
209
|
+
# without overwriting their values. Keep the left overs on the side.
|
|
210
|
+
else:
|
|
211
|
+
original_variations = self.all_variations()
|
|
212
|
+
for include in self.include:
|
|
213
|
+
# Keys shared between the include directive and the original matrix.
|
|
214
|
+
keys_overlap = set(include).intersection(original_variations)
|
|
215
|
+
# Collect include directives applicable to the original matrix.
|
|
216
|
+
if (
|
|
217
|
+
# If all overlapping keys in the directive exactly match any value
|
|
218
|
+
# of the original matrix, then we are certain the directive can be
|
|
219
|
+
# applied without overwriting the original variations.
|
|
220
|
+
all(include[k] in original_variations[k] for k in keys_overlap)
|
|
221
|
+
# Same if no keys are shared, in which case these extra variations
|
|
222
|
+
# will be added to all original ones.
|
|
223
|
+
or not keys_overlap
|
|
224
|
+
):
|
|
225
|
+
applicable_includes.append(include)
|
|
226
|
+
# Other directives are considered non-applicable and will be returned
|
|
227
|
+
# as-is at the end of the process.
|
|
228
|
+
else:
|
|
229
|
+
leftover_includes.append(include)
|
|
230
|
+
|
|
231
|
+
# Iterates through all the variations of the original matrix, and act on the
|
|
232
|
+
# matching exclude and include directives.
|
|
233
|
+
for base_variations in self.product():
|
|
234
|
+
# Skip the variation if it is fully matching at least one exclude directive.
|
|
235
|
+
exclusion_candidate = False
|
|
236
|
+
if any(
|
|
237
|
+
all(
|
|
238
|
+
exclude[k] == base_variations[k]
|
|
239
|
+
for k in set(exclude).intersection(base_variations)
|
|
240
|
+
)
|
|
241
|
+
for exclude in self.exclude
|
|
242
|
+
):
|
|
243
|
+
exclusion_candidate = True
|
|
244
|
+
|
|
245
|
+
# Expand and/or extend the original variation set with applicable include
|
|
246
|
+
# directives.
|
|
247
|
+
updated_variations = base_variations.copy()
|
|
248
|
+
for include in applicable_includes:
|
|
249
|
+
# Check if the include directive is completely disjoint to the
|
|
250
|
+
# variations of the original matrix. If that's the case, then we are
|
|
251
|
+
# supposed to augment the current variation with this include, at it has
|
|
252
|
+
# already been identified as applicable. But only do that if the updated
|
|
253
|
+
# variation has not been already updated with a previously evaluated,
|
|
254
|
+
# more targeted include directive.
|
|
255
|
+
if set(include).isdisjoint(base_variations):
|
|
256
|
+
if set(include).isdisjoint(updated_variations):
|
|
257
|
+
updated_variations.update(include)
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
# Expand the base variation set with the fully matching include
|
|
261
|
+
# directive.
|
|
262
|
+
if all(
|
|
263
|
+
include[k] == base_variations[k]
|
|
264
|
+
for k in set(include).intersection(base_variations)
|
|
265
|
+
):
|
|
266
|
+
# Re-instate the variation set as a valid candidate since we found
|
|
267
|
+
# an include directive that is explicitly referring to it,
|
|
268
|
+
# resurrecting it from the dead.
|
|
269
|
+
exclusion_candidate = False
|
|
270
|
+
updated_variations.update(include)
|
|
271
|
+
|
|
272
|
+
if not exclusion_candidate:
|
|
273
|
+
self._count_job()
|
|
274
|
+
yield updated_variations
|
|
275
|
+
|
|
276
|
+
# Return as-is all the includes that were not applied to the original matrix.
|
|
277
|
+
for variation in leftover_includes:
|
|
278
|
+
self._count_job()
|
|
279
|
+
yield variation
|