antsibull-nox 0.1.0__py3-none-any.whl → 0.2.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 +48 -1
- antsibull_nox/ansible.py +260 -0
- antsibull_nox/collection/__init__.py +56 -0
- antsibull_nox/collection/data.py +106 -0
- antsibull_nox/collection/extract.py +23 -0
- antsibull_nox/collection/install.py +523 -0
- antsibull_nox/{collection.py → collection/search.py} +164 -253
- antsibull_nox/config.py +332 -0
- antsibull_nox/data/action-groups.py +1 -1
- antsibull_nox/data/antsibull_nox_data_util.py +91 -0
- antsibull_nox/data/license-check.py +1 -1
- antsibull_nox/data/no-unwanted-files.py +5 -1
- antsibull_nox/data/plugin-yamllint.py +244 -0
- antsibull_nox/data_util.py +0 -77
- antsibull_nox/interpret_config.py +235 -0
- antsibull_nox/paths.py +19 -0
- antsibull_nox/python.py +81 -0
- antsibull_nox/sessions.py +898 -26
- antsibull_nox/utils.py +85 -0
- {antsibull_nox-0.1.0.dist-info → antsibull_nox-0.2.0.dist-info}/METADATA +3 -1
- antsibull_nox-0.2.0.dist-info/RECORD +25 -0
- antsibull_nox-0.1.0.dist-info/RECORD +0 -14
- {antsibull_nox-0.1.0.dist-info → antsibull_nox-0.2.0.dist-info}/WHEEL +0 -0
- {antsibull_nox-0.1.0.dist-info → antsibull_nox-0.2.0.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
antsibull_nox/data_util.py
CHANGED
@@ -11,89 +11,12 @@ Utility code for scripts in data.
|
|
11
11
|
from __future__ import annotations
|
12
12
|
|
13
13
|
import json
|
14
|
-
import sys
|
15
14
|
import typing as t
|
16
15
|
from pathlib import Path
|
17
16
|
|
18
17
|
import nox
|
19
18
|
|
20
19
|
|
21
|
-
def setup() -> tuple[list[str], dict[str, t.Any]]:
|
22
|
-
"""
|
23
|
-
Fetch list of paths and potential extra configuration.
|
24
|
-
|
25
|
-
First thing to call in an extra sanity check script in data/.
|
26
|
-
"""
|
27
|
-
if len(sys.argv) == 3 and sys.argv[1] == "--data":
|
28
|
-
# Preferred way: load information from JSON file
|
29
|
-
path = sys.argv[2]
|
30
|
-
try:
|
31
|
-
with open(path, "rb") as f:
|
32
|
-
data = json.load(f)
|
33
|
-
except Exception as exc:
|
34
|
-
raise ValueError(f"Error while reading JSON from {path}") from exc
|
35
|
-
try:
|
36
|
-
paths = get_list_of_strings(data, "paths")
|
37
|
-
except ValueError as exc:
|
38
|
-
raise ValueError(f"Invalid JSON content in {path}: {exc}") from exc
|
39
|
-
data.pop("paths")
|
40
|
-
return paths, data
|
41
|
-
if len(sys.argv) >= 2:
|
42
|
-
# It's also possible to pass a list of paths on the command line, to simplify
|
43
|
-
# testing these scripts.
|
44
|
-
return sys.argv[1:], {}
|
45
|
-
# Alternatively one can pass a list of files from stdin, for example by piping
|
46
|
-
# the output of 'git ls-files' into this script. This is also for testing these
|
47
|
-
# scripts.
|
48
|
-
return sys.stdin.read().splitlines(), {}
|
49
|
-
|
50
|
-
|
51
|
-
def get_list_of_strings(
|
52
|
-
data: dict[str, t.Any],
|
53
|
-
key: str,
|
54
|
-
*,
|
55
|
-
default: list[str] | None = None,
|
56
|
-
) -> list[str]:
|
57
|
-
"""
|
58
|
-
Retrieves a list of strings from key ``key`` of the JSON object ``data``.
|
59
|
-
|
60
|
-
If ``default`` is set to a list, a missing key results in this value being returned.
|
61
|
-
"""
|
62
|
-
sentinel = object()
|
63
|
-
value = data.get(key, sentinel)
|
64
|
-
if value is sentinel:
|
65
|
-
if default is not None:
|
66
|
-
return default
|
67
|
-
raise ValueError(f"{key!r} is not a present")
|
68
|
-
if not isinstance(value, list):
|
69
|
-
raise ValueError(f"{key!r} is not a list, but {type(key)}")
|
70
|
-
if not all(isinstance(entry, str) for entry in value):
|
71
|
-
raise ValueError(f"{key!r} is not a list of strings")
|
72
|
-
return t.cast(list[str], value)
|
73
|
-
|
74
|
-
|
75
|
-
def get_bool(
|
76
|
-
data: dict[str, t.Any],
|
77
|
-
key: str,
|
78
|
-
*,
|
79
|
-
default: bool | None = None,
|
80
|
-
) -> bool:
|
81
|
-
"""
|
82
|
-
Retrieves a boolean from key ``key`` of the JSON object ``data``.
|
83
|
-
|
84
|
-
If ``default`` is set to a boolean, a missing key results in this value being returned.
|
85
|
-
"""
|
86
|
-
sentinel = object()
|
87
|
-
value = data.get(key, sentinel)
|
88
|
-
if value is sentinel:
|
89
|
-
if default is not None:
|
90
|
-
return default
|
91
|
-
raise ValueError(f"{key!r} is not a present")
|
92
|
-
if not isinstance(value, bool):
|
93
|
-
raise ValueError(f"{key!r} is not a bool, but {type(key)}")
|
94
|
-
return value
|
95
|
-
|
96
|
-
|
97
20
|
def prepare_data_script(
|
98
21
|
session: nox.Session,
|
99
22
|
*,
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# Author: Felix Fontein <felix@fontein.de>
|
2
|
+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
|
3
|
+
# https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
5
|
+
# SPDX-FileCopyrightText: 2025, Ansible Project
|
6
|
+
|
7
|
+
"""
|
8
|
+
Interpret config.
|
9
|
+
"""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
import typing as t
|
14
|
+
|
15
|
+
from .ansible import AnsibleCoreVersion
|
16
|
+
from .collection import CollectionSource, setup_collection_sources
|
17
|
+
from .config import ActionGroup as ConfigActionGroup
|
18
|
+
from .config import (
|
19
|
+
Config,
|
20
|
+
DevelLikeBranch,
|
21
|
+
Sessions,
|
22
|
+
)
|
23
|
+
from .sessions import (
|
24
|
+
ActionGroup,
|
25
|
+
add_all_ansible_test_sanity_test_sessions,
|
26
|
+
add_all_ansible_test_unit_test_sessions,
|
27
|
+
add_ansible_lint,
|
28
|
+
add_ansible_test_integration_sessions_default_container,
|
29
|
+
add_build_import_check,
|
30
|
+
add_docs_check,
|
31
|
+
add_extra_checks,
|
32
|
+
add_license_check,
|
33
|
+
add_lint_sessions,
|
34
|
+
add_matrix_generator,
|
35
|
+
)
|
36
|
+
from .utils import Version
|
37
|
+
|
38
|
+
|
39
|
+
def _interpret_config(config: Config) -> None:
|
40
|
+
if config.collection_sources:
|
41
|
+
setup_collection_sources(
|
42
|
+
{
|
43
|
+
name: CollectionSource(name=name, source=source.source)
|
44
|
+
for name, source in config.collection_sources.items()
|
45
|
+
}
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
def _convert_action_groups(
|
50
|
+
action_groups: list[ConfigActionGroup] | None,
|
51
|
+
) -> list[ActionGroup] | None:
|
52
|
+
if action_groups is None:
|
53
|
+
return None
|
54
|
+
return [
|
55
|
+
ActionGroup(
|
56
|
+
name=action_group.name,
|
57
|
+
pattern=action_group.pattern,
|
58
|
+
doc_fragment=action_group.doc_fragment,
|
59
|
+
exclusions=action_group.exclusions,
|
60
|
+
)
|
61
|
+
for action_group in action_groups
|
62
|
+
]
|
63
|
+
|
64
|
+
|
65
|
+
def _convert_devel_like_branches(
|
66
|
+
devel_like_branches: list[DevelLikeBranch] | None,
|
67
|
+
) -> list[tuple[str | None, str]] | None:
|
68
|
+
if devel_like_branches is None:
|
69
|
+
return None
|
70
|
+
return [(branch.repository, branch.branch) for branch in devel_like_branches]
|
71
|
+
|
72
|
+
|
73
|
+
def _convert_except_versions(
|
74
|
+
except_versions: list[AnsibleCoreVersion] | None,
|
75
|
+
) -> list[AnsibleCoreVersion | str] | None:
|
76
|
+
return t.cast(t.Optional[list[AnsibleCoreVersion | str]], except_versions)
|
77
|
+
|
78
|
+
|
79
|
+
def _convert_core_python_versions(
|
80
|
+
core_python_versions: dict[AnsibleCoreVersion | str, list[Version]] | None,
|
81
|
+
) -> dict[str | AnsibleCoreVersion, list[str | Version]] | None:
|
82
|
+
return t.cast(
|
83
|
+
t.Optional[dict[str | AnsibleCoreVersion, list[str | Version]]],
|
84
|
+
core_python_versions,
|
85
|
+
)
|
86
|
+
|
87
|
+
|
88
|
+
def _add_sessions(sessions: Sessions) -> None:
|
89
|
+
if sessions.lint:
|
90
|
+
add_lint_sessions(
|
91
|
+
make_lint_default=sessions.lint.default,
|
92
|
+
extra_code_files=sessions.lint.extra_code_files,
|
93
|
+
run_isort=sessions.lint.run_isort,
|
94
|
+
isort_config=sessions.lint.isort_config,
|
95
|
+
isort_package=sessions.lint.isort_package,
|
96
|
+
run_black=sessions.lint.run_black,
|
97
|
+
run_black_modules=sessions.lint.run_black_modules,
|
98
|
+
black_config=sessions.lint.black_config,
|
99
|
+
black_package=sessions.lint.black_package,
|
100
|
+
run_flake8=sessions.lint.run_flake8,
|
101
|
+
flake8_config=sessions.lint.flake8_config,
|
102
|
+
flake8_package=sessions.lint.flake8_package,
|
103
|
+
run_pylint=sessions.lint.run_pylint,
|
104
|
+
pylint_rcfile=sessions.lint.pylint_rcfile,
|
105
|
+
pylint_modules_rcfile=sessions.lint.pylint_modules_rcfile,
|
106
|
+
pylint_package=sessions.lint.pylint_package,
|
107
|
+
pylint_ansible_core_package=sessions.lint.pylint_ansible_core_package,
|
108
|
+
pylint_extra_deps=sessions.lint.pylint_extra_deps,
|
109
|
+
run_yamllint=sessions.lint.run_yamllint,
|
110
|
+
yamllint_config=sessions.lint.yamllint_config,
|
111
|
+
yamllint_config_plugins=sessions.lint.yamllint_config_plugins,
|
112
|
+
yamllint_config_plugins_examples=sessions.lint.yamllint_config_plugins_examples,
|
113
|
+
yamllint_package=sessions.lint.yamllint_package,
|
114
|
+
run_mypy=sessions.lint.run_mypy,
|
115
|
+
mypy_config=sessions.lint.mypy_config,
|
116
|
+
mypy_package=sessions.lint.mypy_package,
|
117
|
+
mypy_ansible_core_package=sessions.lint.mypy_ansible_core_package,
|
118
|
+
mypy_extra_deps=sessions.lint.mypy_extra_deps,
|
119
|
+
)
|
120
|
+
if sessions.docs_check:
|
121
|
+
add_docs_check(
|
122
|
+
make_docs_check_default=sessions.docs_check.default,
|
123
|
+
antsibull_docs_package=sessions.docs_check.antsibull_docs_package,
|
124
|
+
ansible_core_package=sessions.docs_check.ansible_core_package,
|
125
|
+
validate_collection_refs=sessions.docs_check.validate_collection_refs,
|
126
|
+
extra_collections=sessions.docs_check.extra_collections,
|
127
|
+
)
|
128
|
+
if sessions.license_check:
|
129
|
+
add_license_check(
|
130
|
+
make_license_check_default=sessions.license_check.default,
|
131
|
+
run_reuse=sessions.license_check.run_reuse,
|
132
|
+
reuse_package=sessions.license_check.reuse_package,
|
133
|
+
run_license_check=sessions.license_check.run_license_check,
|
134
|
+
license_check_extra_ignore_paths=(
|
135
|
+
sessions.license_check.license_check_extra_ignore_paths
|
136
|
+
),
|
137
|
+
)
|
138
|
+
if sessions.extra_checks:
|
139
|
+
add_extra_checks(
|
140
|
+
make_extra_checks_default=sessions.extra_checks.default,
|
141
|
+
run_no_unwanted_files=sessions.extra_checks.run_no_unwanted_files,
|
142
|
+
no_unwanted_files_module_extensions=(
|
143
|
+
sessions.extra_checks.no_unwanted_files_module_extensions
|
144
|
+
),
|
145
|
+
no_unwanted_files_other_extensions=(
|
146
|
+
sessions.extra_checks.no_unwanted_files_other_extensions
|
147
|
+
),
|
148
|
+
no_unwanted_files_yaml_extensions=(
|
149
|
+
sessions.extra_checks.no_unwanted_files_yaml_extensions
|
150
|
+
),
|
151
|
+
no_unwanted_files_skip_paths=(
|
152
|
+
sessions.extra_checks.no_unwanted_files_skip_paths
|
153
|
+
),
|
154
|
+
no_unwanted_files_skip_directories=(
|
155
|
+
sessions.extra_checks.no_unwanted_files_skip_directories
|
156
|
+
),
|
157
|
+
no_unwanted_files_yaml_directories=(
|
158
|
+
sessions.extra_checks.no_unwanted_files_yaml_directories
|
159
|
+
),
|
160
|
+
no_unwanted_files_allow_symlinks=(
|
161
|
+
sessions.extra_checks.no_unwanted_files_allow_symlinks
|
162
|
+
),
|
163
|
+
run_action_groups=sessions.extra_checks.run_action_groups,
|
164
|
+
action_groups_config=_convert_action_groups(
|
165
|
+
sessions.extra_checks.action_groups_config
|
166
|
+
),
|
167
|
+
)
|
168
|
+
if sessions.build_import_check:
|
169
|
+
add_build_import_check(
|
170
|
+
make_build_import_check_default=sessions.build_import_check.default,
|
171
|
+
ansible_core_package=sessions.build_import_check.ansible_core_package,
|
172
|
+
run_galaxy_importer=sessions.build_import_check.run_galaxy_importer,
|
173
|
+
galaxy_importer_package=sessions.build_import_check.galaxy_importer_package,
|
174
|
+
galaxy_importer_config_path=sessions.build_import_check.galaxy_importer_config_path,
|
175
|
+
)
|
176
|
+
if sessions.ansible_test_sanity:
|
177
|
+
add_all_ansible_test_sanity_test_sessions(
|
178
|
+
default=sessions.ansible_test_sanity.default,
|
179
|
+
include_devel=sessions.ansible_test_sanity.include_devel,
|
180
|
+
include_milestone=sessions.ansible_test_sanity.include_milestone,
|
181
|
+
add_devel_like_branches=_convert_devel_like_branches(
|
182
|
+
sessions.ansible_test_sanity.add_devel_like_branches
|
183
|
+
),
|
184
|
+
min_version=sessions.ansible_test_sanity.min_version,
|
185
|
+
max_version=sessions.ansible_test_sanity.max_version,
|
186
|
+
except_versions=_convert_except_versions(
|
187
|
+
sessions.ansible_test_sanity.except_versions
|
188
|
+
),
|
189
|
+
)
|
190
|
+
if sessions.ansible_test_units:
|
191
|
+
add_all_ansible_test_unit_test_sessions(
|
192
|
+
default=sessions.ansible_test_units.default,
|
193
|
+
include_devel=sessions.ansible_test_units.include_devel,
|
194
|
+
include_milestone=sessions.ansible_test_units.include_milestone,
|
195
|
+
add_devel_like_branches=_convert_devel_like_branches(
|
196
|
+
sessions.ansible_test_units.add_devel_like_branches
|
197
|
+
),
|
198
|
+
min_version=sessions.ansible_test_units.min_version,
|
199
|
+
max_version=sessions.ansible_test_units.max_version,
|
200
|
+
except_versions=_convert_except_versions(
|
201
|
+
sessions.ansible_test_units.except_versions
|
202
|
+
),
|
203
|
+
)
|
204
|
+
if sessions.ansible_test_integration_w_default_container:
|
205
|
+
cfg = sessions.ansible_test_integration_w_default_container
|
206
|
+
add_ansible_test_integration_sessions_default_container(
|
207
|
+
default=cfg.default,
|
208
|
+
include_devel=cfg.include_devel,
|
209
|
+
include_milestone=(cfg.include_milestone),
|
210
|
+
add_devel_like_branches=_convert_devel_like_branches(
|
211
|
+
cfg.add_devel_like_branches
|
212
|
+
),
|
213
|
+
min_version=cfg.min_version,
|
214
|
+
max_version=cfg.max_version,
|
215
|
+
except_versions=_convert_except_versions(cfg.except_versions),
|
216
|
+
core_python_versions=_convert_core_python_versions(
|
217
|
+
cfg.core_python_versions
|
218
|
+
),
|
219
|
+
controller_python_versions_only=cfg.controller_python_versions_only,
|
220
|
+
)
|
221
|
+
if sessions.ansible_lint:
|
222
|
+
add_ansible_lint(
|
223
|
+
make_ansible_lint_default=sessions.ansible_lint.default,
|
224
|
+
ansible_lint_package=sessions.ansible_lint.ansible_lint_package,
|
225
|
+
strict=sessions.ansible_lint.strict,
|
226
|
+
)
|
227
|
+
add_matrix_generator()
|
228
|
+
|
229
|
+
|
230
|
+
def interpret_config(config: Config) -> None:
|
231
|
+
"""
|
232
|
+
Interpret the config file's contents.
|
233
|
+
"""
|
234
|
+
_interpret_config(config)
|
235
|
+
_add_sessions(config.sessions)
|
antsibull_nox/paths.py
CHANGED
@@ -191,8 +191,27 @@ def create_temp_directory(basename: str) -> Path:
|
|
191
191
|
return path
|
192
192
|
|
193
193
|
|
194
|
+
def copy_directory_tree_into(source: Path, destination: Path) -> None:
|
195
|
+
"""
|
196
|
+
Copy the directory tree from ``source`` into the tree at ``destination``.
|
197
|
+
|
198
|
+
If ``destination`` does not yet exist, it will be created first.
|
199
|
+
"""
|
200
|
+
if not source.is_dir():
|
201
|
+
return
|
202
|
+
destination.mkdir(parents=True, exist_ok=True)
|
203
|
+
for root, _, files in source.walk():
|
204
|
+
path = destination / root.relative_to(source)
|
205
|
+
path.mkdir(exist_ok=True)
|
206
|
+
for file in files:
|
207
|
+
dest = path / file
|
208
|
+
remove_path(dest)
|
209
|
+
shutil.copy2(root / file, dest, follow_symlinks=False)
|
210
|
+
|
211
|
+
|
194
212
|
__all__ = [
|
195
213
|
"copy_collection",
|
214
|
+
"copy_directory_tree_into",
|
196
215
|
"create_temp_directory",
|
197
216
|
"filter_paths",
|
198
217
|
"find_data_directory",
|
antsibull_nox/python.py
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Author: Felix Fontein <felix@fontein.de>
|
2
|
+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
|
3
|
+
# https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
5
|
+
# SPDX-FileCopyrightText: 2025, Ansible Project
|
6
|
+
|
7
|
+
"""
|
8
|
+
Python version utilities.
|
9
|
+
"""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
import functools
|
14
|
+
import shutil
|
15
|
+
import subprocess
|
16
|
+
from pathlib import Path
|
17
|
+
|
18
|
+
from .utils import Version
|
19
|
+
|
20
|
+
# The following contains Python version candidates
|
21
|
+
_PYTHON_VERSIONS_TO_TRY: tuple[Version, ...] = tuple(
|
22
|
+
Version.parse(v)
|
23
|
+
for v in [
|
24
|
+
# Python 2:
|
25
|
+
"2.6",
|
26
|
+
"2.7",
|
27
|
+
# Python 3:
|
28
|
+
"3.5",
|
29
|
+
"3.6",
|
30
|
+
"3.7",
|
31
|
+
"3.8",
|
32
|
+
"3.9",
|
33
|
+
"3.10",
|
34
|
+
"3.11",
|
35
|
+
"3.12",
|
36
|
+
"3.13",
|
37
|
+
"3.14",
|
38
|
+
# "3.15",
|
39
|
+
# "3.16",
|
40
|
+
]
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
@functools.cache
|
45
|
+
def get_installed_python_versions() -> dict[Version, Path]:
|
46
|
+
"""
|
47
|
+
Return a map of supported Python versions for which interpreters exist and are on the path,
|
48
|
+
mapped to an executable.
|
49
|
+
"""
|
50
|
+
result = {}
|
51
|
+
|
52
|
+
# Look for pythonX.Y binaries
|
53
|
+
for candidate in _PYTHON_VERSIONS_TO_TRY:
|
54
|
+
if exe := shutil.which(f"python{candidate}"):
|
55
|
+
result[candidate] = Path(exe)
|
56
|
+
|
57
|
+
# Look for python, python2, python3 binaries and determine their version
|
58
|
+
for executable in ("python", "python2", "python3"):
|
59
|
+
exe = shutil.which(executable)
|
60
|
+
if exe:
|
61
|
+
script = "import platform; print('.'.join(platform.python_version().split('.')[:2]))"
|
62
|
+
exe_result = subprocess.run(
|
63
|
+
[exe, "-c", script],
|
64
|
+
check=False,
|
65
|
+
text=True,
|
66
|
+
capture_output=True,
|
67
|
+
)
|
68
|
+
if exe_result.returncode == 0 and exe_result.stdout:
|
69
|
+
try:
|
70
|
+
version = Version.parse(exe_result.stdout.strip())
|
71
|
+
except (AttributeError, ValueError):
|
72
|
+
continue
|
73
|
+
else:
|
74
|
+
result.setdefault(version, Path(exe))
|
75
|
+
|
76
|
+
return result
|
77
|
+
|
78
|
+
|
79
|
+
__all__ = [
|
80
|
+
"get_installed_python_versions",
|
81
|
+
]
|