antsibull-nox 0.0.1__py3-none-any.whl → 0.1.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 +19 -3
- antsibull_nox/collection.py +545 -0
- antsibull_nox/data/action-groups.py +199 -0
- antsibull_nox/data/license-check.py +144 -0
- antsibull_nox/data/license-check.py.license +3 -0
- antsibull_nox/data/no-unwanted-files.py +119 -0
- antsibull_nox/data_util.py +115 -0
- antsibull_nox/paths.py +201 -0
- antsibull_nox/sessions.py +507 -158
- {antsibull_nox-0.0.1.dist-info → antsibull_nox-0.1.0.dist-info}/METADATA +12 -4
- antsibull_nox-0.1.0.dist-info/RECORD +14 -0
- antsibull_nox-0.0.1.dist-info/RECORD +0 -7
- {antsibull_nox-0.0.1.dist-info → antsibull_nox-0.1.0.dist-info}/WHEEL +0 -0
- {antsibull_nox-0.0.1.dist-info → antsibull_nox-0.1.0.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
antsibull_nox/sessions.py
CHANGED
@@ -10,13 +10,37 @@ Create nox sessions.
|
|
10
10
|
|
11
11
|
from __future__ import annotations
|
12
12
|
|
13
|
-
import contextlib
|
14
13
|
import os
|
15
14
|
import shlex
|
15
|
+
import subprocess
|
16
|
+
import sys
|
17
|
+
import typing as t
|
18
|
+
from dataclasses import asdict, dataclass
|
19
|
+
from pathlib import Path
|
16
20
|
|
17
21
|
import nox
|
18
22
|
|
19
|
-
|
23
|
+
from .collection import (
|
24
|
+
CollectionData,
|
25
|
+
force_collection_version,
|
26
|
+
load_collection_data_from_disk,
|
27
|
+
setup_collections,
|
28
|
+
setup_current_tree,
|
29
|
+
)
|
30
|
+
from .data_util import prepare_data_script
|
31
|
+
from .paths import (
|
32
|
+
copy_collection,
|
33
|
+
create_temp_directory,
|
34
|
+
filter_paths,
|
35
|
+
find_data_directory,
|
36
|
+
list_all_files,
|
37
|
+
remove_path,
|
38
|
+
)
|
39
|
+
|
40
|
+
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
|
41
|
+
# https://docs.gitlab.com/ci/variables/predefined_variables/#predefined-variables
|
42
|
+
# https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
|
43
|
+
IN_CI = os.environ.get("CI") == "true"
|
20
44
|
ALLOW_EDITABLE = os.environ.get("ALLOW_EDITABLE", str(not IN_CI)).lower() in (
|
21
45
|
"1",
|
22
46
|
"true",
|
@@ -52,132 +76,130 @@ def install(session: nox.Session, *args: str, editable: bool = False, **kwargs):
|
|
52
76
|
session.install(*args, "-U", **kwargs)
|
53
77
|
|
54
78
|
|
55
|
-
@
|
56
|
-
|
79
|
+
@dataclass
|
80
|
+
class CollectionSetup:
|
57
81
|
"""
|
58
|
-
|
59
|
-
the root directory and the prefix to the current working directory from the root.
|
82
|
+
Information on the setup collections.
|
60
83
|
"""
|
61
|
-
cwd = os.getcwd()
|
62
|
-
root = os.path.normpath(os.path.join(cwd, "..", "..", ".."))
|
63
|
-
try:
|
64
|
-
os.chdir(root)
|
65
|
-
yield root, os.path.relpath(cwd, root)
|
66
|
-
finally:
|
67
|
-
os.chdir(cwd)
|
68
84
|
|
85
|
+
# The path of the ansible_collections directory where all dependent collections
|
86
|
+
# are installed. Is currently identical to current_root, but that might change
|
87
|
+
# or depend on options in the future.
|
88
|
+
collections_root: Path
|
69
89
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
"""
|
74
|
-
return [os.path.join(prefix, path) for path in paths]
|
90
|
+
# The directory in which ansible_collections can be found, as well as
|
91
|
+
# ansible_collections/<namespace>/<name> points to a copy of the current collection.
|
92
|
+
current_place: Path
|
75
93
|
|
94
|
+
# The path of the ansible_collections directory that contains the current collection.
|
95
|
+
# The following is always true:
|
96
|
+
# current_root == current_place / "ansible_collections"
|
97
|
+
current_root: Path
|
76
98
|
|
77
|
-
|
78
|
-
|
79
|
-
Check whether a path (that is a file or not) matches a given list of paths.
|
80
|
-
"""
|
81
|
-
for check in paths:
|
82
|
-
if check == path:
|
83
|
-
return True
|
84
|
-
if not is_file:
|
85
|
-
if not check.endswith("/"):
|
86
|
-
check += "/"
|
87
|
-
if path.startswith(check):
|
88
|
-
return True
|
89
|
-
return False
|
90
|
-
|
91
|
-
|
92
|
-
def restrict_paths(paths: list[str], restrict: list[str]) -> list[str]:
|
93
|
-
"""
|
94
|
-
Restrict a list of paths with a given set of restrictions.
|
95
|
-
"""
|
96
|
-
result = []
|
97
|
-
for path in paths:
|
98
|
-
is_file = os.path.isfile(path)
|
99
|
-
if not is_file and not path.endswith("/"):
|
100
|
-
path += "/"
|
101
|
-
if not match_path(path, is_file, restrict):
|
102
|
-
if not is_file:
|
103
|
-
for check in restrict:
|
104
|
-
if check.startswith(path) and os.path.exists(check):
|
105
|
-
result.append(check)
|
106
|
-
continue
|
107
|
-
result.append(path)
|
108
|
-
return result
|
109
|
-
|
110
|
-
|
111
|
-
def _scan_remove_paths(
|
112
|
-
path: str, remove: list[str], extensions: list[str] | None
|
113
|
-
) -> list[str]:
|
114
|
-
result = []
|
115
|
-
for root, dirs, files in os.walk(path, topdown=True):
|
116
|
-
if not root.endswith("/"):
|
117
|
-
root += "/"
|
118
|
-
if match_path(root, False, remove):
|
119
|
-
continue
|
120
|
-
if all(not check.startswith(root) for check in remove):
|
121
|
-
dirs[:] = []
|
122
|
-
result.append(root)
|
123
|
-
continue
|
124
|
-
for file in files:
|
125
|
-
if extensions and os.path.splitext(file)[1] not in extensions:
|
126
|
-
continue
|
127
|
-
filepath = os.path.normpath(os.path.join(root, file))
|
128
|
-
if not match_path(filepath, True, remove):
|
129
|
-
result.append(filepath)
|
130
|
-
for directory in list(dirs):
|
131
|
-
if directory == "__pycache__":
|
132
|
-
continue
|
133
|
-
dirpath = os.path.normpath(os.path.join(root, directory))
|
134
|
-
if match_path(dirpath, False, remove):
|
135
|
-
dirs.remove(directory)
|
136
|
-
continue
|
137
|
-
return result
|
138
|
-
|
139
|
-
|
140
|
-
def remove_paths(
|
141
|
-
paths: list[str], remove: list[str], extensions: list[str] | None
|
142
|
-
) -> list[str]:
|
143
|
-
"""
|
144
|
-
Restrict a list of paths by removing paths.
|
99
|
+
# Data on the current collection (as in the repository).
|
100
|
+
current_collection: CollectionData
|
145
101
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
102
|
+
# The path of the current collection inside the collection tree below current_root.
|
103
|
+
# The following is always true:
|
104
|
+
# current_path == current_root / current_collection.namespace / current_collection.name
|
105
|
+
current_path: Path
|
106
|
+
|
107
|
+
def prefix_current_paths(self, paths: list[str]) -> list[str]:
|
108
|
+
"""
|
109
|
+
Prefix the list of given paths with ``current_path``.
|
110
|
+
"""
|
111
|
+
result = []
|
112
|
+
for path in paths:
|
113
|
+
prefixed_path = (self.current_path / path).relative_to(self.current_place)
|
114
|
+
if prefixed_path.exists():
|
115
|
+
result.append(str(prefixed_path))
|
116
|
+
return result
|
117
|
+
|
118
|
+
|
119
|
+
def _run_subprocess(args: list[str]) -> tuple[bytes, bytes]:
|
120
|
+
p = subprocess.run(args, check=True, capture_output=True)
|
121
|
+
return p.stdout, p.stderr
|
122
|
+
|
123
|
+
|
124
|
+
def prepare_collections(
|
125
|
+
session: nox.Session,
|
126
|
+
*,
|
127
|
+
install_in_site_packages: bool,
|
128
|
+
extra_deps_files: list[str | os.PathLike] | None = None,
|
129
|
+
extra_collections: list[str] | None = None,
|
130
|
+
install_out_of_tree: bool = False, # can not be used with install_in_site_packages=True
|
131
|
+
) -> CollectionSetup | None:
|
170
132
|
"""
|
171
|
-
|
133
|
+
Install collections in site-packages.
|
172
134
|
"""
|
173
|
-
if
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
135
|
+
if install_out_of_tree and install_in_site_packages:
|
136
|
+
raise ValueError(
|
137
|
+
"install_out_of_tree=True cannot be combined with install_in_site_packages=True"
|
138
|
+
)
|
139
|
+
if isinstance(session.virtualenv, nox.virtualenv.PassthroughEnv):
|
140
|
+
session.warn("No venv. Skip preparing collections...")
|
141
|
+
return None
|
142
|
+
if install_in_site_packages:
|
143
|
+
purelib = (
|
144
|
+
session.run(
|
145
|
+
"python",
|
146
|
+
"-c",
|
147
|
+
"import sysconfig; print(sysconfig.get_path('purelib'))",
|
148
|
+
silent=True,
|
149
|
+
)
|
150
|
+
or ""
|
151
|
+
).strip()
|
152
|
+
if not purelib:
|
153
|
+
session.warn(
|
154
|
+
"Cannot find site-packages (probably due to install-only run)."
|
155
|
+
" Skip preparing collections..."
|
156
|
+
)
|
157
|
+
return None
|
158
|
+
place = Path(purelib)
|
159
|
+
elif install_out_of_tree:
|
160
|
+
place = create_temp_directory(f"antsibull-nox-{session.name}-collection-root-")
|
161
|
+
else:
|
162
|
+
place = Path(session.virtualenv.location) / "collection-root"
|
163
|
+
place.mkdir(exist_ok=True)
|
164
|
+
setup = setup_collections(
|
165
|
+
place,
|
166
|
+
_run_subprocess,
|
167
|
+
extra_deps_files=extra_deps_files,
|
168
|
+
extra_collections=extra_collections,
|
169
|
+
with_current=False,
|
170
|
+
)
|
171
|
+
current_setup = setup_current_tree(place, setup.current_collection)
|
172
|
+
return CollectionSetup(
|
173
|
+
collections_root=setup.root,
|
174
|
+
current_place=place,
|
175
|
+
current_root=current_setup.root,
|
176
|
+
current_collection=setup.current_collection,
|
177
|
+
current_path=t.cast(Path, current_setup.current_path),
|
178
|
+
)
|
178
179
|
|
179
180
|
|
180
|
-
def
|
181
|
+
def _run_bare_script(
|
182
|
+
session: nox.Session, /, name: str, *, extra_data: dict[str, t.Any] | None = None
|
183
|
+
) -> None:
|
184
|
+
files = list_all_files()
|
185
|
+
data = prepare_data_script(
|
186
|
+
session,
|
187
|
+
base_name=name,
|
188
|
+
paths=files,
|
189
|
+
extra_data=extra_data,
|
190
|
+
)
|
191
|
+
session.run(
|
192
|
+
sys.executable,
|
193
|
+
find_data_directory() / f"{name}.py",
|
194
|
+
"--data",
|
195
|
+
data,
|
196
|
+
external=True,
|
197
|
+
)
|
198
|
+
|
199
|
+
|
200
|
+
def add_lint(
|
201
|
+
*, make_lint_default: bool, has_formatters: bool, has_codeqa: bool, has_typing: bool
|
202
|
+
) -> None:
|
181
203
|
"""
|
182
204
|
Add nox meta session for linting.
|
183
205
|
"""
|
@@ -192,11 +214,14 @@ def add_lint(has_formatters: bool, has_codeqa: bool, has_typing: bool) -> None:
|
|
192
214
|
dependent_sessions.append("codeqa")
|
193
215
|
if has_typing:
|
194
216
|
dependent_sessions.append("typing")
|
195
|
-
nox.session(
|
217
|
+
nox.session( # type: ignore
|
218
|
+
lint, name="lint", default=make_lint_default, requires=dependent_sessions
|
219
|
+
)
|
196
220
|
|
197
221
|
|
198
222
|
def add_formatters(
|
199
223
|
*,
|
224
|
+
extra_code_files: list[str],
|
200
225
|
# isort:
|
201
226
|
run_isort: bool,
|
202
227
|
isort_config: str | os.PathLike | None,
|
@@ -231,13 +256,15 @@ def add_formatters(
|
|
231
256
|
if isort_config is not None:
|
232
257
|
command.extend(["--settings-file", str(isort_config)])
|
233
258
|
command.extend(session.posargs)
|
234
|
-
command.extend(filter_paths(CODE_FILES + ["noxfile.py"]))
|
259
|
+
command.extend(filter_paths(CODE_FILES + ["noxfile.py"] + extra_code_files))
|
235
260
|
session.run(*command)
|
236
261
|
|
237
262
|
def execute_black_for(session: nox.Session, paths: list[str]) -> None:
|
238
263
|
if not paths:
|
239
264
|
return
|
240
265
|
command = ["black"]
|
266
|
+
if run_check:
|
267
|
+
command.append("--check")
|
241
268
|
if black_config is not None:
|
242
269
|
command.extend(["--config", str(black_config)])
|
243
270
|
command.extend(session.posargs)
|
@@ -246,7 +273,9 @@ def add_formatters(
|
|
246
273
|
|
247
274
|
def execute_black(session: nox.Session) -> None:
|
248
275
|
if run_black and run_black_modules:
|
249
|
-
execute_black_for(
|
276
|
+
execute_black_for(
|
277
|
+
session, filter_paths(CODE_FILES + ["noxfile.py"] + extra_code_files)
|
278
|
+
)
|
250
279
|
return
|
251
280
|
if run_black:
|
252
281
|
paths = filter_paths(
|
@@ -273,8 +302,9 @@ def add_formatters(
|
|
273
302
|
nox.session(formatters, name="formatters", default=False) # type: ignore
|
274
303
|
|
275
304
|
|
276
|
-
def add_codeqa(
|
305
|
+
def add_codeqa( # noqa: C901
|
277
306
|
*,
|
307
|
+
extra_code_files: list[str],
|
278
308
|
# flake8:
|
279
309
|
run_flake8: bool,
|
280
310
|
flake8_config: str | os.PathLike | None,
|
@@ -314,10 +344,31 @@ def add_codeqa(
|
|
314
344
|
if flake8_config is not None:
|
315
345
|
command.extend(["--config", str(flake8_config)])
|
316
346
|
command.extend(session.posargs)
|
317
|
-
command.extend(filter_paths(CODE_FILES + ["noxfile.py"]))
|
347
|
+
command.extend(filter_paths(CODE_FILES + ["noxfile.py"] + extra_code_files))
|
348
|
+
session.run(*command)
|
349
|
+
|
350
|
+
def execute_pylint_impl(
|
351
|
+
session: nox.Session,
|
352
|
+
prepared_collections: CollectionSetup,
|
353
|
+
config: os.PathLike | str | None,
|
354
|
+
paths: list[str],
|
355
|
+
) -> None:
|
356
|
+
command = ["pylint"]
|
357
|
+
if config is not None:
|
358
|
+
command.extend(
|
359
|
+
[
|
360
|
+
"--rcfile",
|
361
|
+
os.path.join(prepared_collections.current_collection.path, config),
|
362
|
+
]
|
363
|
+
)
|
364
|
+
command.extend(["--source-roots", "."])
|
365
|
+
command.extend(session.posargs)
|
366
|
+
command.extend(prepared_collections.prefix_current_paths(paths))
|
318
367
|
session.run(*command)
|
319
368
|
|
320
|
-
def execute_pylint(
|
369
|
+
def execute_pylint(
|
370
|
+
session: nox.Session, prepared_collections: CollectionSetup
|
371
|
+
) -> None:
|
321
372
|
if pylint_modules_rcfile is not None and pylint_modules_rcfile != pylint_rcfile:
|
322
373
|
# Only run pylint twice when using different configurations
|
323
374
|
module_paths = filter_paths(
|
@@ -330,46 +381,43 @@ def add_codeqa(
|
|
330
381
|
# Otherwise run it only once using the general configuration
|
331
382
|
module_paths = []
|
332
383
|
other_paths = filter_paths(CODE_FILES)
|
333
|
-
|
334
|
-
with
|
384
|
+
|
385
|
+
with session.chdir(prepared_collections.current_place):
|
335
386
|
if module_paths:
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
os.path.join(root, prefix, config),
|
343
|
-
]
|
344
|
-
)
|
345
|
-
command.extend(["--source-roots", root])
|
346
|
-
command.extend(session.posargs)
|
347
|
-
command.extend(prefix_paths(module_paths, prefix=prefix))
|
348
|
-
session.run(*command)
|
387
|
+
execute_pylint_impl(
|
388
|
+
session,
|
389
|
+
prepared_collections,
|
390
|
+
pylint_modules_rcfile or pylint_rcfile,
|
391
|
+
module_paths,
|
392
|
+
)
|
349
393
|
|
350
394
|
if other_paths:
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
["--rcfile", os.path.join(root, prefix, pylint_rcfile)]
|
355
|
-
)
|
356
|
-
command.extend(["--source-roots", root])
|
357
|
-
command.extend(session.posargs)
|
358
|
-
command.extend(prefix_paths(other_paths, prefix=prefix))
|
359
|
-
session.run(*command)
|
395
|
+
execute_pylint_impl(
|
396
|
+
session, prepared_collections, pylint_rcfile, other_paths
|
397
|
+
)
|
360
398
|
|
361
399
|
def codeqa(session: nox.Session) -> None:
|
362
400
|
install(session, *compose_dependencies())
|
401
|
+
prepared_collections: CollectionSetup | None = None
|
402
|
+
if run_pylint:
|
403
|
+
prepared_collections = prepare_collections(
|
404
|
+
session,
|
405
|
+
install_in_site_packages=False,
|
406
|
+
extra_deps_files=["tests/unit/requirements.yml"],
|
407
|
+
)
|
408
|
+
if not prepared_collections:
|
409
|
+
session.warn("Skipping pylint...")
|
363
410
|
if run_flake8:
|
364
411
|
execute_flake8(session)
|
365
|
-
if run_pylint:
|
366
|
-
execute_pylint(session)
|
412
|
+
if run_pylint and prepared_collections:
|
413
|
+
execute_pylint(session, prepared_collections)
|
367
414
|
|
368
415
|
nox.session(codeqa, name="codeqa", default=False) # type: ignore
|
369
416
|
|
370
417
|
|
371
418
|
def add_typing(
|
372
419
|
*,
|
420
|
+
extra_code_files: list[str],
|
373
421
|
run_mypy: bool,
|
374
422
|
mypy_config: str | os.PathLike | None,
|
375
423
|
mypy_package: str,
|
@@ -394,28 +442,50 @@ def add_typing(
|
|
394
442
|
deps.extend(shlex.split(extra_dep))
|
395
443
|
return deps
|
396
444
|
|
397
|
-
def execute_mypy(
|
398
|
-
|
445
|
+
def execute_mypy(
|
446
|
+
session: nox.Session, prepared_collections: CollectionSetup
|
447
|
+
) -> None:
|
448
|
+
# Run mypy
|
449
|
+
with session.chdir(prepared_collections.current_place):
|
399
450
|
command = ["mypy"]
|
400
451
|
if mypy_config is not None:
|
401
452
|
command.extend(
|
402
|
-
[
|
453
|
+
[
|
454
|
+
"--config-file",
|
455
|
+
os.path.join(
|
456
|
+
prepared_collections.current_collection.path, mypy_config
|
457
|
+
),
|
458
|
+
]
|
403
459
|
)
|
460
|
+
command.append("--namespace-packages")
|
404
461
|
command.append("--explicit-package-bases")
|
405
462
|
command.extend(session.posargs)
|
406
|
-
command.extend(
|
407
|
-
|
463
|
+
command.extend(
|
464
|
+
prepared_collections.prefix_current_paths(CODE_FILES + extra_code_files)
|
465
|
+
)
|
466
|
+
session.run(
|
467
|
+
*command, env={"MYPYPATH": str(prepared_collections.current_place)}
|
468
|
+
)
|
408
469
|
|
409
470
|
def typing(session: nox.Session) -> None:
|
410
471
|
install(session, *compose_dependencies())
|
411
|
-
|
412
|
-
|
472
|
+
prepared_collections = prepare_collections(
|
473
|
+
session,
|
474
|
+
install_in_site_packages=False,
|
475
|
+
extra_deps_files=["tests/unit/requirements.yml"],
|
476
|
+
)
|
477
|
+
if not prepared_collections:
|
478
|
+
session.warn("Skipping mypy...")
|
479
|
+
if run_mypy and prepared_collections:
|
480
|
+
execute_mypy(session, prepared_collections)
|
413
481
|
|
414
482
|
nox.session(typing, name="typing", default=False) # type: ignore
|
415
483
|
|
416
484
|
|
417
485
|
def add_lint_sessions(
|
418
486
|
*,
|
487
|
+
make_lint_default: bool = True,
|
488
|
+
extra_code_files: list[str] | None = None,
|
419
489
|
# isort:
|
420
490
|
run_isort: bool = True,
|
421
491
|
isort_config: str | os.PathLike | None = None,
|
@@ -451,11 +521,15 @@ def add_lint_sessions(
|
|
451
521
|
has_typing = run_mypy
|
452
522
|
|
453
523
|
add_lint(
|
454
|
-
has_formatters=has_formatters,
|
524
|
+
has_formatters=has_formatters,
|
525
|
+
has_codeqa=has_codeqa,
|
526
|
+
has_typing=has_typing,
|
527
|
+
make_lint_default=make_lint_default,
|
455
528
|
)
|
456
529
|
|
457
530
|
if has_formatters:
|
458
531
|
add_formatters(
|
532
|
+
extra_code_files=extra_code_files or [],
|
459
533
|
run_isort=run_isort,
|
460
534
|
isort_config=isort_config,
|
461
535
|
isort_package=isort_package,
|
@@ -467,6 +541,7 @@ def add_lint_sessions(
|
|
467
541
|
|
468
542
|
if has_codeqa:
|
469
543
|
add_codeqa(
|
544
|
+
extra_code_files=extra_code_files or [],
|
470
545
|
run_flake8=run_flake8,
|
471
546
|
flake8_config=flake8_config,
|
472
547
|
flake8_package=flake8_package,
|
@@ -480,6 +555,7 @@ def add_lint_sessions(
|
|
480
555
|
|
481
556
|
if has_typing:
|
482
557
|
add_typing(
|
558
|
+
extra_code_files=extra_code_files or [],
|
483
559
|
run_mypy=run_mypy,
|
484
560
|
mypy_config=mypy_config,
|
485
561
|
mypy_package=mypy_package,
|
@@ -488,4 +564,277 @@ def add_lint_sessions(
|
|
488
564
|
)
|
489
565
|
|
490
566
|
|
491
|
-
|
567
|
+
def add_docs_check(
|
568
|
+
*,
|
569
|
+
make_docs_check_default: bool = True,
|
570
|
+
antsibull_docs_package: str = "antsibull-docs",
|
571
|
+
ansible_core_package: str = "ansible-core",
|
572
|
+
validate_collection_refs: t.Literal["self", "dependent", "all"] | None = None,
|
573
|
+
extra_collections: list[str] | None = None,
|
574
|
+
):
|
575
|
+
"""
|
576
|
+
Add docs-check session for linting.
|
577
|
+
"""
|
578
|
+
|
579
|
+
def compose_dependencies() -> list[str]:
|
580
|
+
deps = [antsibull_docs_package, ansible_core_package]
|
581
|
+
return deps
|
582
|
+
|
583
|
+
def execute_antsibull_docs(
|
584
|
+
session: nox.Session, prepared_collections: CollectionSetup
|
585
|
+
) -> None:
|
586
|
+
with session.chdir(prepared_collections.current_path):
|
587
|
+
collections_path = f"{prepared_collections.current_place}"
|
588
|
+
command = [
|
589
|
+
"antsibull-docs",
|
590
|
+
"lint-collection-docs",
|
591
|
+
"--plugin-docs",
|
592
|
+
"--skip-rstcheck",
|
593
|
+
".",
|
594
|
+
]
|
595
|
+
if validate_collection_refs:
|
596
|
+
command.extend(["--validate-collection-refs", validate_collection_refs])
|
597
|
+
session.run(*command, env={"ANSIBLE_COLLECTIONS_PATH": collections_path})
|
598
|
+
|
599
|
+
def docs_check(session: nox.Session) -> None:
|
600
|
+
install(session, *compose_dependencies())
|
601
|
+
prepared_collections = prepare_collections(
|
602
|
+
session,
|
603
|
+
install_in_site_packages=False,
|
604
|
+
extra_collections=extra_collections,
|
605
|
+
install_out_of_tree=True,
|
606
|
+
)
|
607
|
+
if not prepared_collections:
|
608
|
+
session.warn("Skipping antsibull-docs...")
|
609
|
+
if prepared_collections:
|
610
|
+
execute_antsibull_docs(session, prepared_collections)
|
611
|
+
|
612
|
+
nox.session( # type: ignore
|
613
|
+
docs_check, name="docs-check", default=make_docs_check_default
|
614
|
+
)
|
615
|
+
|
616
|
+
|
617
|
+
def add_license_check(
|
618
|
+
*,
|
619
|
+
make_license_check_default: bool = True,
|
620
|
+
run_reuse: bool = True,
|
621
|
+
reuse_package: str = "reuse",
|
622
|
+
run_license_check: bool = True,
|
623
|
+
license_check_extra_ignore_paths: list[str] | None = None,
|
624
|
+
):
|
625
|
+
"""
|
626
|
+
Add license-check session for license checks.
|
627
|
+
"""
|
628
|
+
|
629
|
+
def compose_dependencies() -> list[str]:
|
630
|
+
deps = []
|
631
|
+
if run_reuse:
|
632
|
+
deps.append(reuse_package)
|
633
|
+
return deps
|
634
|
+
|
635
|
+
def license_check(session: nox.Session) -> None:
|
636
|
+
install(session, *compose_dependencies())
|
637
|
+
if run_reuse:
|
638
|
+
session.run("reuse", "lint")
|
639
|
+
if run_license_check:
|
640
|
+
_run_bare_script(
|
641
|
+
session,
|
642
|
+
"license-check",
|
643
|
+
extra_data={
|
644
|
+
"extra_ignore_paths": license_check_extra_ignore_paths or [],
|
645
|
+
},
|
646
|
+
)
|
647
|
+
|
648
|
+
nox.session( # type: ignore
|
649
|
+
license_check, name="license-check", default=make_license_check_default
|
650
|
+
)
|
651
|
+
|
652
|
+
|
653
|
+
@dataclass
|
654
|
+
class ActionGroup:
|
655
|
+
"""
|
656
|
+
Defines an action group.
|
657
|
+
"""
|
658
|
+
|
659
|
+
# Name of the action group.
|
660
|
+
name: str
|
661
|
+
# Regex pattern to match modules that could belong to this action group.
|
662
|
+
pattern: str
|
663
|
+
# Doc fragment that members of the action group must have, but no other module
|
664
|
+
# must have
|
665
|
+
doc_fragment: str
|
666
|
+
# Exclusion list of modules that match the regex, but should not be part of the
|
667
|
+
# action group. All other modules matching the regex are assumed to be part of
|
668
|
+
# the action group.
|
669
|
+
exclusions: list[str] | None = None
|
670
|
+
|
671
|
+
|
672
|
+
def add_extra_checks(
|
673
|
+
*,
|
674
|
+
make_extra_checks_default: bool = True,
|
675
|
+
# no-unwanted-files:
|
676
|
+
run_no_unwanted_files: bool = True,
|
677
|
+
no_unwanted_files_module_extensions: (
|
678
|
+
list[str] | None
|
679
|
+
) = None, # default: .cs, .ps1, .psm1, .py
|
680
|
+
no_unwanted_files_other_extensions: list[str] | None = None, # default: .py, .pyi
|
681
|
+
no_unwanted_files_yaml_extensions: list[str] | None = None, # default: .yml, .yaml
|
682
|
+
no_unwanted_files_skip_paths: list[str] | None = None, # default: []
|
683
|
+
no_unwanted_files_skip_directories: list[str] | None = None, # default: []
|
684
|
+
no_unwanted_files_yaml_directories: (
|
685
|
+
list[str] | None
|
686
|
+
) = None, # default: plugins/test/, plugins/filter/
|
687
|
+
no_unwanted_files_allow_symlinks: bool = False,
|
688
|
+
# action-groups:
|
689
|
+
run_action_groups: bool = False,
|
690
|
+
action_groups_config: list[ActionGroup] | None = None,
|
691
|
+
):
|
692
|
+
"""
|
693
|
+
Add extra-checks session for extra checks.
|
694
|
+
"""
|
695
|
+
|
696
|
+
def execute_no_unwanted_files(session: nox.Session) -> None:
|
697
|
+
_run_bare_script(
|
698
|
+
session,
|
699
|
+
"no-unwanted-files",
|
700
|
+
extra_data={
|
701
|
+
"module_extensions": no_unwanted_files_module_extensions
|
702
|
+
or [".cs", ".ps1", ".psm1", ".py"],
|
703
|
+
"other_extensions": no_unwanted_files_other_extensions
|
704
|
+
or [".py", ".pyi"],
|
705
|
+
"yaml_extensions": no_unwanted_files_yaml_extensions
|
706
|
+
or [".yml", ".yaml"],
|
707
|
+
"skip_paths": no_unwanted_files_skip_paths or [],
|
708
|
+
"skip_directories": no_unwanted_files_skip_directories or [],
|
709
|
+
"yaml_directories": no_unwanted_files_yaml_directories
|
710
|
+
or ["plugins/test/", "plugins/filter/"],
|
711
|
+
"allow_symlinks": no_unwanted_files_allow_symlinks,
|
712
|
+
},
|
713
|
+
)
|
714
|
+
|
715
|
+
def execute_action_groups(session: nox.Session) -> None:
|
716
|
+
if action_groups_config is None:
|
717
|
+
session.warn("Skipping action-groups since config is not provided...")
|
718
|
+
return
|
719
|
+
_run_bare_script(
|
720
|
+
session,
|
721
|
+
"action-groups",
|
722
|
+
extra_data={
|
723
|
+
"config": [asdict(cfg) for cfg in action_groups_config],
|
724
|
+
},
|
725
|
+
)
|
726
|
+
|
727
|
+
def extra_checks(session: nox.Session) -> None:
|
728
|
+
if run_no_unwanted_files:
|
729
|
+
execute_no_unwanted_files(session)
|
730
|
+
if run_action_groups:
|
731
|
+
execute_action_groups(session)
|
732
|
+
|
733
|
+
nox.session( # type: ignore
|
734
|
+
extra_checks,
|
735
|
+
name="extra-checks",
|
736
|
+
python=False,
|
737
|
+
default=make_extra_checks_default,
|
738
|
+
)
|
739
|
+
|
740
|
+
|
741
|
+
def add_build_import_check(
|
742
|
+
*,
|
743
|
+
make_build_import_check_default: bool = True,
|
744
|
+
ansible_core_package: str = "ansible-core",
|
745
|
+
run_galaxy_importer: bool = True,
|
746
|
+
galaxy_importer_package: str = "galaxy-importer",
|
747
|
+
galaxy_importer_config_path: (
|
748
|
+
str | None
|
749
|
+
) = None, # https://github.com/ansible/galaxy-importer#configuration
|
750
|
+
):
|
751
|
+
"""
|
752
|
+
Add license-check session for license checks.
|
753
|
+
"""
|
754
|
+
|
755
|
+
def compose_dependencies() -> list[str]:
|
756
|
+
deps = [ansible_core_package]
|
757
|
+
if run_galaxy_importer:
|
758
|
+
deps.append(galaxy_importer_package)
|
759
|
+
return deps
|
760
|
+
|
761
|
+
def build_import_check(session: nox.Session) -> None:
|
762
|
+
install(session, *compose_dependencies())
|
763
|
+
|
764
|
+
tmp = Path(session.create_tmp())
|
765
|
+
collection_dir = tmp / "collection"
|
766
|
+
remove_path(collection_dir)
|
767
|
+
copy_collection(Path.cwd(), collection_dir)
|
768
|
+
|
769
|
+
collection = load_collection_data_from_disk(
|
770
|
+
collection_dir, accept_manifest=False
|
771
|
+
)
|
772
|
+
version = collection.version
|
773
|
+
if not version:
|
774
|
+
version = "0.0.1"
|
775
|
+
force_collection_version(collection_dir, version=version)
|
776
|
+
|
777
|
+
with session.chdir(collection_dir):
|
778
|
+
build_ran = session.run("ansible-galaxy", "collection", "build") is not None
|
779
|
+
|
780
|
+
tarball = (
|
781
|
+
collection_dir
|
782
|
+
/ f"{collection.namespace}-{collection.name}-{version}.tar.gz"
|
783
|
+
)
|
784
|
+
if build_ran and not tarball.is_file():
|
785
|
+
files = "\n".join(
|
786
|
+
f"* {path.name}"
|
787
|
+
for path in collection_dir.iterdir()
|
788
|
+
if not path.is_dir()
|
789
|
+
)
|
790
|
+
session.error(f"Cannot find file {tarball}! List of all files:\n{files}")
|
791
|
+
|
792
|
+
if run_galaxy_importer and tarball.is_file():
|
793
|
+
env = {}
|
794
|
+
if galaxy_importer_config_path:
|
795
|
+
env["GALAXY_IMPORTER_CONFIG"] = str(
|
796
|
+
Path.cwd() / galaxy_importer_config_path
|
797
|
+
)
|
798
|
+
with session.chdir(collection_dir):
|
799
|
+
import_log = (
|
800
|
+
session.run(
|
801
|
+
"python",
|
802
|
+
"-m",
|
803
|
+
"galaxy_importer.main",
|
804
|
+
tarball.name,
|
805
|
+
env=env,
|
806
|
+
silent=True,
|
807
|
+
)
|
808
|
+
or ""
|
809
|
+
)
|
810
|
+
if import_log:
|
811
|
+
print(import_log)
|
812
|
+
error_prefix = "ERROR:"
|
813
|
+
errors = []
|
814
|
+
for line in import_log.splitlines():
|
815
|
+
if line.startswith(error_prefix):
|
816
|
+
errors.append(line[len(error_prefix) :].strip())
|
817
|
+
if errors:
|
818
|
+
messages = "\n".join(f"* {error}" for error in errors)
|
819
|
+
session.warn(
|
820
|
+
"Galaxy importer emitted the following non-fatal"
|
821
|
+
f" error{'' if len(errors) == 1 else 's'}:\n{messages}"
|
822
|
+
)
|
823
|
+
|
824
|
+
nox.session( # type: ignore
|
825
|
+
build_import_check,
|
826
|
+
name="build-import-check",
|
827
|
+
default=make_build_import_check_default,
|
828
|
+
)
|
829
|
+
|
830
|
+
|
831
|
+
__all__ = [
|
832
|
+
"ActionGroup",
|
833
|
+
"add_build_import_check",
|
834
|
+
"add_docs_check",
|
835
|
+
"add_extra_checks",
|
836
|
+
"add_license_check",
|
837
|
+
"add_lint_sessions",
|
838
|
+
"install",
|
839
|
+
"prepare_collections",
|
840
|
+
]
|