xref: /petsc/lib/petsc/bin/maint/petsclinter/petsclinter/pkg_consistency_checks.py (revision 4c7cc9c8e01791debde927bc0816c9a347055c8f)
1#!/usr/bin/env python3
2"""
3# Created: Mon Aug  7 14:41:01 2023 (-0400)
4# @author: Jacob Faibussowitsch
5"""
6from __future__ import annotations
7
8from typing import Any
9
10def assert_py_versions_match(config_file: str, toml_data: dict[str, Any]) -> None:
11  import re
12
13  def tuplify_version_str(version_str: str) -> tuple[int, int, int]:
14    assert isinstance(version_str, str)
15    version = list(map(int, version_str.split('.')))
16    while len(version) < 3:
17      version.append(0)
18    # type checkers complain:
19    #
20    # Incompatible return value type (got "Tuple[int, ...]", expected
21    # "Tuple[int, int, int]")  [return-value]
22    #
23    # but we know that version is of length 3, so we can safely ignore this
24    return tuple(version) # type: ignore[return-value]
25
26  project_data     = toml_data['project']
27  py_version_match = re.search(r'([\d.]+)', project_data['requires-python'])
28  assert py_version_match is not None # pacify type checkers
29  min_py_version = tuplify_version_str(py_version_match.group(1))
30
31  def assert_version_match(tool_name: str, version_str: str) -> None:
32    tup = tuplify_version_str(version_str)
33    if tup != min_py_version:
34      raise ValueError(
35        f'{tool_name} minimum python version {tup} in {config_file} does not match the projects '
36        f'minimum {min_py_version}. Likely you have bumped up the project version and forgotten '
37        'to update this one to match!'
38      )
39    return
40
41  assert_version_match('mypy', toml_data['tool']['mypy']['python_version'])
42  assert_version_match('vermin', str(toml_data['vermin']['targets']))
43  return
44
45def assert_requirements_match(toml_file: str, toml_data: dict[str, Any], req_file: str, req_lines: list[str]) -> None:
46  toml_reqs = toml_data['project']['dependencies']
47  if len(toml_reqs) != len(req_lines):
48    raise RuntimeError(f'Number of requirements don\'t match. {toml_file}::[project][dependencies] has {len(toml_reqs)}, {req_file} has {len(req_lines)}')
49  assert len(toml_reqs) == len(req_lines), f''
50  for toml_req, req_req in zip(toml_reqs, req_lines):
51    assert toml_req == req_req, f'{toml_file}: {toml_req} != {req_file}: {req_req}'
52  return
53
54def load_toml_data(toml_path: str) -> dict[str, Any]:
55  try:
56    # since 3.11
57    # novermin
58    import tomllib # type: ignore[import]
59  except (ModuleNotFoundError, ImportError):
60    try:
61      import tomli as tomllib # type: ignore[import]
62    except (ModuleNotFoundError, ImportError):
63      try:
64        from pip._vendor import tomli as tomllib
65      except (ModuleNotFoundError, ImportError) as mnfe:
66        raise RuntimeError(
67          f'No package installed to read the {toml_path} file! Install tomli via '
68          'python3 -m pip install tomli'
69        ) from mnfe
70
71  with open(toml_path, 'rb') as fd:
72    toml_data: dict[str, Any] = tomllib.load(fd)
73  return toml_data
74
75def load_requirements_data(req_path: str) -> list[str]:
76  ret: list[str] = []
77  with open(req_path) as fd:
78    for line in fd.readlines():
79      if lstrp := line.strip():
80        if not lstrp.startswith('#'):
81          ret.append(lstrp)
82  return ret
83
84def main() -> None:
85  import os
86
87  par_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
88  # open ./../pyproject.toml
89  toml_path = os.path.join(par_dir, 'pyproject.toml')
90  toml_data = load_toml_data(toml_path)
91  # open ./../requirements.txt
92  req_path = os.path.join(par_dir, 'requirements.txt')
93  req_data = load_requirements_data(req_path)
94  assert_py_versions_match(toml_path, toml_data)
95  assert_requirements_match(toml_path, toml_data, req_path, req_data)
96  return
97
98if __name__ == '__main__':
99  try:
100    main()
101  except Exception as exc:
102    import sys
103
104    print('=' * 90)
105    print('ERROR:', str(exc))
106    print('=' * 90)
107    sys.exit(1)
108