from __future__ import annotations import importlib import shutil import json import typing as T from ..mesonlib import MesonException, Popen_safe if T.TYPE_CHECKING: from types import ModuleType # tomllib is present in python 3.11, before that it is a pypi module called tomli, # we try to import tomllib, then tomli, tomllib: T.Optional[ModuleType] = None toml2json: T.Optional[str] = None for t in ['tomllib', 'tomli']: try: tomllib = importlib.import_module(t) break except ImportError: pass else: # TODO: it would be better to use an Executable here, which could be looked # up in the cross file or provided by a wrap. However, that will have to be # passed in externally, since we don't have (and I don't think we should), # have access to the `Environment` for that in this module. toml2json = shutil.which('toml2json') class TomlImplementationMissing(MesonException): pass class CargoTomlError(MesonException): """Exception for TOML parsing errors, keeping proper location info.""" def load_toml(filename: str) -> T.Dict[str, object]: if tomllib: try: with open(filename, 'rb') as f: raw = tomllib.load(f) except tomllib.TOMLDecodeError as e: if hasattr(e, 'msg'): raise CargoTomlError(e.msg, file=filename, lineno=e.lineno, colno=e.colno) from e else: raise CargoTomlError(str(e), file=filename) from e else: if toml2json is None: raise TomlImplementationMissing('Could not find an implementation of tomllib, nor toml2json') p, out, err = Popen_safe([toml2json, filename]) if p.returncode != 0: error_msg = err.strip() or 'toml2json failed to decode TOML' raise CargoTomlError(error_msg, file=filename) raw = json.loads(out) # tomllib.load() returns T.Dict[str, T.Any] but not other implementations. return T.cast('T.Dict[str, object]', raw)