antsibull-nox 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- antsibull_nox/__init__.py +1 -1
- antsibull_nox/ansible.py +15 -0
- antsibull_nox/collection/data.py +12 -0
- antsibull_nox/collection/install.py +194 -79
- antsibull_nox/collection/search.py +128 -30
- antsibull_nox/config.py +4 -0
- antsibull_nox/data/action-groups.py +0 -1
- antsibull_nox/data/file-yamllint.py +138 -0
- antsibull_nox/data/plugin-yamllint.py +51 -24
- antsibull_nox/interpret_config.py +13 -0
- antsibull_nox/sessions/ansible_lint.py +2 -0
- antsibull_nox/sessions/ansible_test.py +23 -17
- antsibull_nox/sessions/collections.py +8 -0
- antsibull_nox/sessions/lint.py +88 -21
- {antsibull_nox-0.3.0.dist-info → antsibull_nox-0.5.0.dist-info}/METADATA +1 -1
- {antsibull_nox-0.3.0.dist-info → antsibull_nox-0.5.0.dist-info}/RECORD +19 -18
- {antsibull_nox-0.3.0.dist-info → antsibull_nox-0.5.0.dist-info}/WHEEL +0 -0
- {antsibull_nox-0.3.0.dist-info → antsibull_nox-0.5.0.dist-info}/entry_points.txt +0 -0
- {antsibull_nox-0.3.0.dist-info → antsibull_nox-0.5.0.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
antsibull_nox/config.py
CHANGED
@@ -275,6 +275,7 @@ class SessionAnsibleTestIntegrationWDefaultContainer(_BaseModel):
|
|
275
275
|
except_versions: list[PAnsibleCoreVersion] = []
|
276
276
|
core_python_versions: dict[t.Union[PAnsibleCoreVersion, str], list[PVersion]] = {}
|
277
277
|
controller_python_versions_only: bool = False
|
278
|
+
ansible_vars_from_env_vars: dict[str, str] = {}
|
278
279
|
|
279
280
|
@p.model_validator(mode="after")
|
280
281
|
def _validate_core_keys(self) -> t.Self:
|
@@ -340,6 +341,9 @@ class Config(_BaseModel):
|
|
340
341
|
"""
|
341
342
|
|
342
343
|
collection_sources: dict[CollectionName, CollectionSource] = {}
|
344
|
+
collection_sources_per_ansible: dict[
|
345
|
+
PAnsibleCoreVersion, dict[CollectionName, CollectionSource]
|
346
|
+
] = {}
|
343
347
|
sessions: Sessions = Sessions()
|
344
348
|
|
345
349
|
|
@@ -113,7 +113,6 @@ def scan(config: list[ActionGroup], errors: list[str]) -> None:
|
|
113
113
|
modules_directory = "plugins/modules/"
|
114
114
|
modules_suffix = ".py"
|
115
115
|
|
116
|
-
errors = []
|
117
116
|
for file in os.listdir(modules_directory):
|
118
117
|
if not file.endswith(modules_suffix):
|
119
118
|
continue
|
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
# Copyright (c) 2025, Felix Fontein <felix@fontein.de>
|
4
|
+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt
|
5
|
+
# or https://www.gnu.org/licenses/gpl-3.0.txt)
|
6
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
7
|
+
|
8
|
+
"""Make sure all plugin and module documentation adheres to yamllint."""
|
9
|
+
|
10
|
+
from __future__ import annotations
|
11
|
+
|
12
|
+
import io
|
13
|
+
import os
|
14
|
+
import sys
|
15
|
+
import traceback
|
16
|
+
import typing as t
|
17
|
+
|
18
|
+
from antsibull_nox_data_util import setup # type: ignore
|
19
|
+
from yamllint import linter
|
20
|
+
from yamllint.cli import find_project_config_filepath
|
21
|
+
from yamllint.config import YamlLintConfig
|
22
|
+
from yamllint.linter import PROBLEM_LEVELS
|
23
|
+
|
24
|
+
REPORT_LEVELS: set[PROBLEM_LEVELS] = {
|
25
|
+
"warning",
|
26
|
+
"error",
|
27
|
+
}
|
28
|
+
|
29
|
+
|
30
|
+
def lint(
|
31
|
+
*,
|
32
|
+
errors: list[dict[str, t.Any]],
|
33
|
+
path: str,
|
34
|
+
data: str,
|
35
|
+
config: YamlLintConfig,
|
36
|
+
) -> None:
|
37
|
+
try:
|
38
|
+
problems = linter.run(
|
39
|
+
io.StringIO(data),
|
40
|
+
config,
|
41
|
+
path,
|
42
|
+
)
|
43
|
+
for problem in problems:
|
44
|
+
if problem.level not in REPORT_LEVELS:
|
45
|
+
continue
|
46
|
+
msg = f"{problem.level}: {problem.desc}"
|
47
|
+
if problem.rule:
|
48
|
+
msg += f" ({problem.rule})"
|
49
|
+
errors.append(
|
50
|
+
{
|
51
|
+
"path": path,
|
52
|
+
"line": problem.line,
|
53
|
+
"col": problem.column,
|
54
|
+
"message": msg,
|
55
|
+
}
|
56
|
+
)
|
57
|
+
except Exception as exc:
|
58
|
+
error = str(exc).replace("\n", " / ")
|
59
|
+
errors.append(
|
60
|
+
{
|
61
|
+
"path": path,
|
62
|
+
"line": 1,
|
63
|
+
"col": 1,
|
64
|
+
"message": (
|
65
|
+
f"Internal error while linting YAML: exception {type(exc)}:"
|
66
|
+
f" {error}; traceback: {traceback.format_exc()!r}"
|
67
|
+
),
|
68
|
+
}
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
def process_yaml_file(
|
73
|
+
errors: list[dict[str, t.Any]],
|
74
|
+
path: str,
|
75
|
+
config: YamlLintConfig,
|
76
|
+
) -> None:
|
77
|
+
try:
|
78
|
+
with open(path, "rt", encoding="utf-8") as stream:
|
79
|
+
data = stream.read()
|
80
|
+
except Exception as exc:
|
81
|
+
errors.append(
|
82
|
+
{
|
83
|
+
"path": path,
|
84
|
+
"line": 1,
|
85
|
+
"col": 1,
|
86
|
+
"message": (
|
87
|
+
f"Error while parsing Python code: exception {type(exc)}:"
|
88
|
+
f" {exc}; traceback: {traceback.format_exc()!r}"
|
89
|
+
),
|
90
|
+
}
|
91
|
+
)
|
92
|
+
return
|
93
|
+
|
94
|
+
lint(
|
95
|
+
errors=errors,
|
96
|
+
path=path,
|
97
|
+
data=data,
|
98
|
+
config=config,
|
99
|
+
)
|
100
|
+
|
101
|
+
|
102
|
+
def main() -> int:
|
103
|
+
"""Main entry point."""
|
104
|
+
paths, extra_data = setup()
|
105
|
+
config: str | None = extra_data.get("config")
|
106
|
+
|
107
|
+
if config is None:
|
108
|
+
config = find_project_config_filepath()
|
109
|
+
|
110
|
+
if config:
|
111
|
+
yamllint_config = YamlLintConfig(file=config)
|
112
|
+
else:
|
113
|
+
yamllint_config = YamlLintConfig(content="extends: default")
|
114
|
+
|
115
|
+
errors: list[dict[str, t.Any]] = []
|
116
|
+
for path in paths:
|
117
|
+
if not os.path.isfile(path):
|
118
|
+
continue
|
119
|
+
process_yaml_file(errors, path, yamllint_config)
|
120
|
+
|
121
|
+
errors.sort(
|
122
|
+
key=lambda error: (error["path"], error["line"], error["col"], error["message"])
|
123
|
+
)
|
124
|
+
for error in errors:
|
125
|
+
prefix = f"{error['path']}:{error['line']}:{error['col']}: "
|
126
|
+
msg = error["message"]
|
127
|
+
if "note" in error:
|
128
|
+
msg = f"{msg}\nNote: {error['note']}"
|
129
|
+
for i, line in enumerate(msg.splitlines()):
|
130
|
+
print(f"{prefix}{line}")
|
131
|
+
if i == 0:
|
132
|
+
prefix = " " * len(prefix)
|
133
|
+
|
134
|
+
return len(errors) > 0
|
135
|
+
|
136
|
+
|
137
|
+
if __name__ == "__main__":
|
138
|
+
sys.exit(main())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env python
|
2
2
|
|
3
|
-
# Copyright (c)
|
3
|
+
# Copyright (c) 2025, Felix Fontein <felix@fontein.de>
|
4
4
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt
|
5
5
|
# or https://www.gnu.org/licenses/gpl-3.0.txt)
|
6
6
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
@@ -44,6 +44,13 @@ def lint(
|
|
44
44
|
config: YamlLintConfig,
|
45
45
|
extra_for_errors: dict[str, t.Any] | None = None,
|
46
46
|
) -> None:
|
47
|
+
# If the string start with optional whitespace + linebreak, skip that line
|
48
|
+
idx = data.find("\n")
|
49
|
+
if idx >= 0 and (idx == 0 or data[:idx].isspace()):
|
50
|
+
data = data[idx + 1 :]
|
51
|
+
row_offset += 1
|
52
|
+
col_offset = 0
|
53
|
+
|
47
54
|
try:
|
48
55
|
problems = linter.run(
|
49
56
|
io.StringIO(data),
|
@@ -84,6 +91,20 @@ def lint(
|
|
84
91
|
errors[-1].update(extra_for_errors)
|
85
92
|
|
86
93
|
|
94
|
+
def iterate_targets(
|
95
|
+
assignment: ast.Assign,
|
96
|
+
) -> t.Iterable[tuple[ast.Constant, str, str]]:
|
97
|
+
if not isinstance(assignment.value, ast.Constant):
|
98
|
+
return
|
99
|
+
if not isinstance(assignment.value.value, str):
|
100
|
+
return
|
101
|
+
for target in assignment.targets:
|
102
|
+
try:
|
103
|
+
yield assignment.value, assignment.value.value, target.id # type: ignore
|
104
|
+
except AttributeError:
|
105
|
+
continue
|
106
|
+
|
107
|
+
|
87
108
|
def process_python_file(
|
88
109
|
errors: list[dict[str, t.Any]],
|
89
110
|
path: str,
|
@@ -107,33 +128,39 @@ def process_python_file(
|
|
107
128
|
)
|
108
129
|
return
|
109
130
|
|
110
|
-
|
131
|
+
is_doc_fragment = path.startswith("plugins/doc_fragments/")
|
132
|
+
|
133
|
+
# We look for top-level assignments and classes
|
111
134
|
for child in root.body:
|
135
|
+
if (
|
136
|
+
is_doc_fragment
|
137
|
+
and isinstance(child, ast.ClassDef)
|
138
|
+
and child.name == "ModuleDocFragment"
|
139
|
+
):
|
140
|
+
for fragment in child.body:
|
141
|
+
if not isinstance(fragment, ast.Assign):
|
142
|
+
continue
|
143
|
+
for constant, data, fragment_name in iterate_targets(fragment):
|
144
|
+
lint(
|
145
|
+
errors=errors,
|
146
|
+
path=path,
|
147
|
+
data=data,
|
148
|
+
row_offset=constant.lineno - 1,
|
149
|
+
col_offset=constant.col_offset - 1,
|
150
|
+
section=fragment_name,
|
151
|
+
config=config,
|
152
|
+
)
|
112
153
|
if not isinstance(child, ast.Assign):
|
113
154
|
continue
|
114
|
-
|
115
|
-
continue
|
116
|
-
if not isinstance(child.value.value, str):
|
117
|
-
continue
|
118
|
-
for target in child.targets:
|
119
|
-
try:
|
120
|
-
section = target.id # type: ignore
|
121
|
-
except AttributeError:
|
122
|
-
continue
|
155
|
+
for constant, data, section in iterate_targets(child):
|
123
156
|
if section not in ("DOCUMENTATION", "EXAMPLES", "RETURN"):
|
124
157
|
continue
|
125
158
|
|
126
|
-
#
|
127
|
-
data
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
# If the string start with optional whitespace + linebreak, skip that line
|
132
|
-
idx = data.find("\n")
|
133
|
-
if idx >= 0 and (idx == 0 or data[:idx].isspace()):
|
134
|
-
data = data[idx + 1 :]
|
135
|
-
row_offset += 1
|
136
|
-
col_offset = 0
|
159
|
+
# Handle special values
|
160
|
+
if data in ("#", " # ") and section == "RETURN":
|
161
|
+
# Not skipping it here could result in all kind of linting errors,
|
162
|
+
# like no document start, or trailing space.
|
163
|
+
continue
|
137
164
|
|
138
165
|
# Check for non-YAML examples
|
139
166
|
if section == EXAMPLES_SECTION:
|
@@ -146,8 +173,8 @@ def process_python_file(
|
|
146
173
|
errors=errors,
|
147
174
|
path=path,
|
148
175
|
data=data,
|
149
|
-
row_offset=
|
150
|
-
col_offset=col_offset,
|
176
|
+
row_offset=constant.lineno - 1,
|
177
|
+
col_offset=constant.col_offset - 1,
|
151
178
|
section=section,
|
152
179
|
config=config_examples if section == EXAMPLES_SECTION else config,
|
153
180
|
)
|
@@ -46,6 +46,18 @@ def _interpret_config(config: Config) -> None:
|
|
46
46
|
for name, source in config.collection_sources.items()
|
47
47
|
}
|
48
48
|
)
|
49
|
+
if config.collection_sources_per_ansible:
|
50
|
+
for (
|
51
|
+
ansible_core_version,
|
52
|
+
collection_sources,
|
53
|
+
) in config.collection_sources_per_ansible.items():
|
54
|
+
setup_collection_sources(
|
55
|
+
{
|
56
|
+
name: CollectionSource(name=name, source=source.source)
|
57
|
+
for name, source in collection_sources.items()
|
58
|
+
},
|
59
|
+
ansible_core_version=ansible_core_version,
|
60
|
+
)
|
49
61
|
|
50
62
|
|
51
63
|
def _convert_action_groups(
|
@@ -226,6 +238,7 @@ def _add_sessions(sessions: Sessions) -> None:
|
|
226
238
|
cfg.core_python_versions
|
227
239
|
),
|
228
240
|
controller_python_versions_only=cfg.controller_python_versions_only,
|
241
|
+
ansible_vars_from_env_vars=cfg.ansible_vars_from_env_vars,
|
229
242
|
)
|
230
243
|
if sessions.ansible_lint:
|
231
244
|
add_ansible_lint(
|
@@ -16,12 +16,14 @@ from collections.abc import Callable
|
|
16
16
|
from pathlib import Path
|
17
17
|
|
18
18
|
import nox
|
19
|
+
from antsibull_fileutils.yaml import store_yaml_file
|
19
20
|
|
20
21
|
from ..ansible import (
|
21
22
|
AnsibleCoreVersion,
|
22
23
|
get_ansible_core_info,
|
23
24
|
get_ansible_core_package_name,
|
24
25
|
get_supported_core_versions,
|
26
|
+
parse_ansible_core_version,
|
25
27
|
)
|
26
28
|
from ..paths import copy_directory_tree_into
|
27
29
|
from ..python import get_installed_python_versions
|
@@ -33,17 +35,6 @@ from .utils import (
|
|
33
35
|
)
|
34
36
|
|
35
37
|
|
36
|
-
def _parse_ansible_core_version(
|
37
|
-
version: str | AnsibleCoreVersion,
|
38
|
-
) -> AnsibleCoreVersion:
|
39
|
-
if version in ("devel", "milestone"):
|
40
|
-
# For some reason mypy doesn't notice that
|
41
|
-
return t.cast(AnsibleCoreVersion, version)
|
42
|
-
if isinstance(version, Version):
|
43
|
-
return version
|
44
|
-
return Version.parse(version)
|
45
|
-
|
46
|
-
|
47
38
|
def add_ansible_test_session(
|
48
39
|
*,
|
49
40
|
name: str,
|
@@ -67,7 +58,7 @@ def add_ansible_test_session(
|
|
67
58
|
|
68
59
|
Returns a list of Python versions set for this session.
|
69
60
|
"""
|
70
|
-
parsed_ansible_core_version =
|
61
|
+
parsed_ansible_core_version = parse_ansible_core_version(ansible_core_version)
|
71
62
|
|
72
63
|
def compose_dependencies() -> list[str]:
|
73
64
|
deps = [
|
@@ -84,6 +75,7 @@ def add_ansible_test_session(
|
|
84
75
|
install(session, *compose_dependencies())
|
85
76
|
prepared_collections = prepare_collections(
|
86
77
|
session,
|
78
|
+
ansible_core_version=parsed_ansible_core_version,
|
87
79
|
install_in_site_packages=False,
|
88
80
|
extra_deps_files=extra_deps_files,
|
89
81
|
install_out_of_tree=True,
|
@@ -177,7 +169,7 @@ def add_ansible_test_sanity_test_session(
|
|
177
169
|
"""
|
178
170
|
Add generic ansible-test sanity test session.
|
179
171
|
"""
|
180
|
-
command = ["sanity", "--
|
172
|
+
command = ["sanity", "--color", "-v", "--docker"]
|
181
173
|
if skip_tests:
|
182
174
|
for test in skip_tests:
|
183
175
|
command.extend(["--skip", test])
|
@@ -210,7 +202,7 @@ def _parse_min_max_except(
|
|
210
202
|
max_version = Version.parse(max_version)
|
211
203
|
if except_versions is None:
|
212
204
|
return min_version, max_version, None
|
213
|
-
evs = tuple(
|
205
|
+
evs = tuple(parse_ansible_core_version(version) for version in except_versions)
|
214
206
|
return min_version, max_version, evs
|
215
207
|
|
216
208
|
|
@@ -311,7 +303,7 @@ def add_ansible_test_unit_test_session(
|
|
311
303
|
add_ansible_test_session(
|
312
304
|
name=name,
|
313
305
|
description=description,
|
314
|
-
ansible_test_params=["units", "--
|
306
|
+
ansible_test_params=["units", "--color", "-v", "--docker"],
|
315
307
|
extra_deps_files=["tests/unit/requirements.yml"],
|
316
308
|
default=default,
|
317
309
|
ansible_core_version=ansible_core_version,
|
@@ -406,6 +398,7 @@ def add_ansible_test_integration_sessions_default_container(
|
|
406
398
|
dict[str | AnsibleCoreVersion, list[str | Version]] | None
|
407
399
|
) = None,
|
408
400
|
controller_python_versions_only: bool = False,
|
401
|
+
ansible_vars_from_env_vars: dict[str, str] | None = None,
|
409
402
|
default: bool = False,
|
410
403
|
) -> None:
|
411
404
|
"""
|
@@ -418,6 +411,18 @@ def add_ansible_test_integration_sessions_default_container(
|
|
418
411
|
controller Python versions.
|
419
412
|
"""
|
420
413
|
|
414
|
+
def callback_before() -> None:
|
415
|
+
if not ansible_vars_from_env_vars:
|
416
|
+
return
|
417
|
+
|
418
|
+
path = Path("tests", "integration", "integration_config.yml")
|
419
|
+
content: dict[str, t.Any] = {}
|
420
|
+
for ans_var, env_var in ansible_vars_from_env_vars.items():
|
421
|
+
value = os.environ.get(env_var)
|
422
|
+
if value is not None:
|
423
|
+
content[ans_var] = env_var
|
424
|
+
store_yaml_file(path, content, nice=True, sort_keys=True)
|
425
|
+
|
421
426
|
def add_integration_tests(
|
422
427
|
ansible_core_version: AnsibleCoreVersion,
|
423
428
|
repo_name: str | None = None,
|
@@ -468,10 +473,10 @@ def add_ansible_test_integration_sessions_default_container(
|
|
468
473
|
description=description,
|
469
474
|
ansible_test_params=[
|
470
475
|
"integration",
|
476
|
+
"--color",
|
477
|
+
"-v",
|
471
478
|
"--docker",
|
472
479
|
"default",
|
473
|
-
"-v",
|
474
|
-
"--color",
|
475
480
|
"--python",
|
476
481
|
str(py_version),
|
477
482
|
],
|
@@ -479,6 +484,7 @@ def add_ansible_test_integration_sessions_default_container(
|
|
479
484
|
ansible_core_version=ansible_core_version,
|
480
485
|
ansible_core_repo_name=repo_name,
|
481
486
|
ansible_core_branch_name=branch_name,
|
487
|
+
callback_before=callback_before,
|
482
488
|
default=False,
|
483
489
|
register_name="integration",
|
484
490
|
register_extra_data={
|
@@ -18,6 +18,7 @@ from pathlib import Path
|
|
18
18
|
|
19
19
|
import nox
|
20
20
|
|
21
|
+
from ..ansible import AnsibleCoreVersion, parse_ansible_core_version
|
21
22
|
from ..collection import (
|
22
23
|
CollectionData,
|
23
24
|
setup_collections,
|
@@ -76,6 +77,7 @@ def _run_subprocess(args: list[str]) -> tuple[bytes, bytes]:
|
|
76
77
|
def prepare_collections(
|
77
78
|
session: nox.Session,
|
78
79
|
*,
|
80
|
+
ansible_core_version: AnsibleCoreVersion | str | None = None,
|
79
81
|
install_in_site_packages: bool,
|
80
82
|
extra_deps_files: list[str | os.PathLike] | None = None,
|
81
83
|
extra_collections: list[str] | None = None,
|
@@ -84,6 +86,11 @@ def prepare_collections(
|
|
84
86
|
"""
|
85
87
|
Install collections in site-packages.
|
86
88
|
"""
|
89
|
+
parsed_ansible_core_version = (
|
90
|
+
parse_ansible_core_version(ansible_core_version)
|
91
|
+
if ansible_core_version is not None
|
92
|
+
else "devel"
|
93
|
+
)
|
87
94
|
if install_out_of_tree and install_in_site_packages:
|
88
95
|
raise ValueError(
|
89
96
|
"install_out_of_tree=True cannot be combined with install_in_site_packages=True"
|
@@ -116,6 +123,7 @@ def prepare_collections(
|
|
116
123
|
setup = setup_collections(
|
117
124
|
place,
|
118
125
|
_run_subprocess,
|
126
|
+
ansible_core_version=parsed_ansible_core_version,
|
119
127
|
extra_deps_files=extra_deps_files,
|
120
128
|
extra_collections=extra_collections,
|
121
129
|
with_current=False,
|
antsibull_nox/sessions/lint.py
CHANGED
@@ -10,6 +10,7 @@ Create nox lint sessions.
|
|
10
10
|
|
11
11
|
from __future__ import annotations
|
12
12
|
|
13
|
+
import json
|
13
14
|
import os
|
14
15
|
import shlex
|
15
16
|
from pathlib import Path
|
@@ -29,6 +30,7 @@ from .utils import (
|
|
29
30
|
compose_description,
|
30
31
|
install,
|
31
32
|
run_bare_script,
|
33
|
+
silence_run_verbosity,
|
32
34
|
)
|
33
35
|
|
34
36
|
CODE_FILES = [
|
@@ -189,6 +191,28 @@ def add_formatters(
|
|
189
191
|
nox.session(name="formatters", default=False)(formatters)
|
190
192
|
|
191
193
|
|
194
|
+
def process_pylint_errors(
|
195
|
+
session: nox.Session,
|
196
|
+
prepared_collections: CollectionSetup,
|
197
|
+
output: str,
|
198
|
+
) -> None:
|
199
|
+
"""
|
200
|
+
Process errors reported by pylint in 'json2' format.
|
201
|
+
"""
|
202
|
+
data = json.loads(output)
|
203
|
+
found_error = False
|
204
|
+
if data["messages"]:
|
205
|
+
for message in data["messages"]:
|
206
|
+
path = os.path.relpath(
|
207
|
+
message["absolutePath"], prepared_collections.current_path
|
208
|
+
)
|
209
|
+
prefix = f"{path}:{message['line']}:{message['column']}: [{message['messageId']}]"
|
210
|
+
print(f"{prefix} {message['message']} [{message['symbol']}]")
|
211
|
+
found_error = True
|
212
|
+
if found_error:
|
213
|
+
session.error("Pylint failed")
|
214
|
+
|
215
|
+
|
192
216
|
def add_codeqa( # noqa: C901
|
193
217
|
*,
|
194
218
|
extra_code_files: list[str],
|
@@ -249,9 +273,17 @@ def add_codeqa( # noqa: C901
|
|
249
273
|
]
|
250
274
|
)
|
251
275
|
command.extend(["--source-roots", "."])
|
276
|
+
command.extend(["--output-format", "json2"])
|
252
277
|
command.extend(session.posargs)
|
253
278
|
command.extend(prepared_collections.prefix_current_paths(paths))
|
254
|
-
|
279
|
+
with silence_run_verbosity():
|
280
|
+
# Exit code is OR of some of 1, 2, 4, 8, 16
|
281
|
+
output = session.run(
|
282
|
+
*command, silent=True, success_codes=list(range(0, 32))
|
283
|
+
)
|
284
|
+
|
285
|
+
if output:
|
286
|
+
process_pylint_errors(session, prepared_collections, output)
|
255
287
|
|
256
288
|
def execute_pylint(
|
257
289
|
session: nox.Session, prepared_collections: CollectionSetup
|
@@ -335,29 +367,22 @@ def add_yamllint(
|
|
335
367
|
def execute_yamllint(session: nox.Session) -> None:
|
336
368
|
# Run yamllint
|
337
369
|
all_files = list_all_files()
|
338
|
-
cwd = Path.cwd()
|
339
370
|
all_yaml_filenames = [
|
340
|
-
|
341
|
-
for file in all_files
|
342
|
-
if file.name.lower().endswith((".yml", ".yaml"))
|
371
|
+
file for file in all_files if file.name.lower().endswith((".yml", ".yaml"))
|
343
372
|
]
|
344
373
|
if not all_yaml_filenames:
|
345
374
|
session.warn("Skipping yamllint since no YAML file was found...")
|
346
375
|
return
|
347
376
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
command.append("--")
|
358
|
-
command.extend(all_yaml_filenames)
|
359
|
-
command.extend(session.posargs)
|
360
|
-
session.run(*command)
|
377
|
+
run_bare_script(
|
378
|
+
session,
|
379
|
+
"file-yamllint",
|
380
|
+
use_session_python=True,
|
381
|
+
files=all_yaml_filenames,
|
382
|
+
extra_data={
|
383
|
+
"config": to_str(yamllint_config),
|
384
|
+
},
|
385
|
+
)
|
361
386
|
|
362
387
|
def execute_plugin_yamllint(session: nox.Session) -> None:
|
363
388
|
# Run yamllint
|
@@ -415,6 +440,40 @@ def add_yamllint(
|
|
415
440
|
nox.session(name="yamllint", default=False)(yamllint)
|
416
441
|
|
417
442
|
|
443
|
+
def process_mypy_errors(
|
444
|
+
session: nox.Session,
|
445
|
+
prepared_collections: CollectionSetup,
|
446
|
+
output: str,
|
447
|
+
) -> None:
|
448
|
+
"""
|
449
|
+
Process errors reported by mypy in 'json' format.
|
450
|
+
"""
|
451
|
+
found_error = False
|
452
|
+
for line in output.splitlines():
|
453
|
+
if not line.strip():
|
454
|
+
continue
|
455
|
+
try:
|
456
|
+
data = json.loads(line)
|
457
|
+
path = os.path.relpath(
|
458
|
+
prepared_collections.current_place / data["file"],
|
459
|
+
prepared_collections.current_path,
|
460
|
+
)
|
461
|
+
prefix = f"{path}:{data['line']}:{data['column']}: [{data['severity']}]"
|
462
|
+
if data["code"]:
|
463
|
+
print(f"{prefix} {data['message']} [{data['code']}]")
|
464
|
+
else:
|
465
|
+
print(f"{prefix} {data['message']}")
|
466
|
+
if data["hint"]:
|
467
|
+
prefix = " " * len(prefix)
|
468
|
+
for hint in data["hint"].splitlines():
|
469
|
+
print(f"{prefix} {hint}")
|
470
|
+
except Exception: # pylint: disable=broad-exception-caught
|
471
|
+
session.warn(f"Cannot parse mypy output: {line}")
|
472
|
+
found_error = True
|
473
|
+
if found_error:
|
474
|
+
session.error("Type checking failed")
|
475
|
+
|
476
|
+
|
418
477
|
def add_typing(
|
419
478
|
*,
|
420
479
|
extra_code_files: list[str],
|
@@ -459,13 +518,21 @@ def add_typing(
|
|
459
518
|
)
|
460
519
|
command.append("--namespace-packages")
|
461
520
|
command.append("--explicit-package-bases")
|
521
|
+
command.extend(["--output", "json"])
|
462
522
|
command.extend(session.posargs)
|
463
523
|
command.extend(
|
464
524
|
prepared_collections.prefix_current_paths(CODE_FILES + extra_code_files)
|
465
525
|
)
|
466
|
-
|
467
|
-
|
468
|
-
|
526
|
+
with silence_run_verbosity():
|
527
|
+
output = session.run(
|
528
|
+
*command,
|
529
|
+
env={"MYPYPATH": str(prepared_collections.current_place)},
|
530
|
+
silent=True,
|
531
|
+
success_codes=(0, 1, 2),
|
532
|
+
)
|
533
|
+
|
534
|
+
if output:
|
535
|
+
process_mypy_errors(session, prepared_collections, output)
|
469
536
|
|
470
537
|
def typing(session: nox.Session) -> None:
|
471
538
|
install(session, *compose_dependencies())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: antsibull-nox
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: Changelog tool for Ansible-core and Ansible collections
|
5
5
|
Project-URL: Documentation, https://ansible.readthedocs.io/projects/antsibull-nox/
|
6
6
|
Project-URL: Source code, https://github.com/ansible-community/antsibull-nox/
|