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/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
- IN_CI = "GITHUB_ACTIONS" in os.environ
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
- @contextlib.contextmanager
56
- def ansible_collection_root():
79
+ @dataclass
80
+ class CollectionSetup:
57
81
  """
58
- Context manager that changes to the root directory and yields the path of
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
- def prefix_paths(paths: list[str], /, prefix: str) -> list[str]:
71
- """
72
- Prefix paths with the given prefix.
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
- def match_path(path: str, is_file: bool, paths: list[str]) -> bool:
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
- If ``extensions`` is specified, only files matching this extension
147
- will be considered when files need to be explicitly enumerated.
148
- """
149
- result = []
150
- for path in paths:
151
- is_file = os.path.isfile(path)
152
- if not is_file and not path.endswith("/"):
153
- path += "/"
154
- if match_path(path, is_file, remove):
155
- continue
156
- if not is_file and any(check.startswith(path) for check in remove):
157
- result.extend(_scan_remove_paths(path, remove, extensions))
158
- continue
159
- result.append(path)
160
- return result
161
-
162
-
163
- def filter_paths(
164
- paths: list[str],
165
- /,
166
- remove: list[str] | None = None,
167
- restrict: list[str] | None = None,
168
- extensions: list[str] | None = None,
169
- ) -> list[str]:
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
- Modifies a list of paths by restricting to and/or removing paths.
133
+ Install collections in site-packages.
172
134
  """
173
- if restrict:
174
- paths = restrict_paths(paths, restrict)
175
- if remove:
176
- paths = remove_paths(paths, remove, extensions)
177
- return [path for path in paths if os.path.exists(path)]
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 add_lint(has_formatters: bool, has_codeqa: bool, has_typing: bool) -> None:
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(lint, name="lint", default=True, requires=dependent_sessions) # type: ignore
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(session, filter_paths(CODE_FILES + ["noxfile.py"]))
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(session: nox.Session) -> None:
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
- command: list[str]
334
- with ansible_collection_root() as (root, prefix):
384
+
385
+ with session.chdir(prepared_collections.current_place):
335
386
  if module_paths:
336
- command = ["pylint"]
337
- config = pylint_modules_rcfile or pylint_rcfile
338
- if config is not None:
339
- command.extend(
340
- [
341
- "--rcfile",
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
- command = ["pylint"]
352
- if pylint_rcfile is not None:
353
- command.extend(
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(session: nox.Session) -> None:
398
- with ansible_collection_root() as (root, prefix):
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
- ["--config-file", os.path.join(root, prefix, mypy_config)]
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(prefix_paths(CODE_FILES, prefix=prefix))
407
- session.run(*command, env={"MYPYPATH": root})
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
- if run_mypy:
412
- execute_mypy(session)
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, has_codeqa=has_codeqa, has_typing=has_typing
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
- __all__ = ["add_lint_sessions"]
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
+ ]