Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mesonbuild/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

__all__ = [
'Protocol',
'DataclassInstance',
'ImmutableListProtocol'
]

import dataclasses
import typing

# We can change this to typing when we require python 3.8
Expand All @@ -21,6 +23,10 @@
T = typing.TypeVar('T')


# Copied from typeshed. Blarg that they don't expose this
class DataclassInstance(Protocol):
__dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field[typing.Any]]]

class StringProtocol(Protocol):
def __str__(self) -> str: ...

Expand Down
10 changes: 8 additions & 2 deletions mesonbuild/cargo/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@
import typing as T
from pathlib import PurePath

from . import builder, version
from . import builder, raw, version
from .cfg import eval_cfg
from .toml import load_toml
from .manifest import Manifest, CargoLock, CargoLockPackage, Workspace, fixup_meson_varname
from ..mesonlib import (
is_parent_path, lazy_property, MesonException, MachineChoice,
PerMachine, unique_list, SubProject,
)
from .validate import validator
from .. import coredata, mlog
from ..wrap.wrap import PackageDefinition

if T.TYPE_CHECKING:
from . import raw
from .. import mparser
from typing_extensions import Literal

Expand Down Expand Up @@ -634,8 +634,12 @@ def _load_manifest(self, subdir: str, workspace: T.Optional[Workspace] = None, m

self.build_def_files.append(filename)
if 'workspace' in raw_manifest:
if not (result := validator(raw.Workspace)(raw_manifest)):
raise MesonException(f'invalid {subdir}/Cargo.toml at {".".join(result.path)}')
manifest_ = Workspace.from_raw(raw_manifest, path)
elif 'package' in raw_manifest:
if not (result := validator(raw.Manifest)(raw_manifest)):
raise MesonException(f'invalid {subdir}/Cargo.toml at {".".join(result.path)}')
manifest_ = Manifest.from_raw(raw_manifest, path, workspace, member_path)
else:
raise MesonException(f'{subdir}/Cargo.toml does not have [package] or [workspace] section')
Expand Down Expand Up @@ -850,6 +854,8 @@ def load_cargo_lock(filename: str, subproject_dir: str) -> T.Optional[CargoLock]
# provides multiple dependency names.
if os.path.exists(filename):
toml = load_toml(filename)
if not validator(raw.CargoLock)(toml):
raise MesonException(f'invalid {filename}')
raw_cargolock = T.cast('raw.CargoLock', toml)
cargolock = CargoLock.from_raw(raw_cargolock)
packagefiles_dir = os.path.join(subproject_dir, 'packagefiles')
Expand Down
45 changes: 28 additions & 17 deletions mesonbuild/cargo/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,17 @@
from pathlib import PurePath


from . import version
from ..mesonlib import MesonException, lazy_property, MachineChoice
from . import raw, version
from .raw import EDITION, CRATE_TYPE, LINT_LEVEL
from .validate import dataclass_field_validators
from .. import mlog
from ..mesonlib import MesonException, lazy_property, MachineChoice, MesonBugException
from ..wrap.wrap import PackageDefinition

if T.TYPE_CHECKING:
from typing_extensions import Protocol, Self
from typing_extensions import Self
from .._typing import DataclassInstance

from . import raw
from .raw import EDITION, CRATE_TYPE, LINT_LEVEL
from ..wrap.wrap import PackageDefinition

# Copied from typeshed. Blarg that they don't expose this
class DataclassInstance(Protocol):
__dataclass_fields__: T.ClassVar[dict[str, dataclasses.Field[T.Any]]]

_DI = T.TypeVar('_DI', bound='DataclassInstance')

Expand Down Expand Up @@ -151,10 +148,10 @@ def _raw_to_dataclass(raw: T.Mapping[str, object], cls: T.Type[_DI], msg: str,
"""
new_dict = {}
unexpected = set()
fields = {x.name for x in dataclasses.fields(cls)}
raw_from_workspace = raw_from_workspace or {}
ignored_fields = ignored_fields or []
inherit = raw.get('workspace', False)
typedict = dataclass_field_validators(cls)

for orig_k, v in raw.items():
if orig_k == 'workspace':
Expand All @@ -169,29 +166,43 @@ def _raw_to_dataclass(raw: T.Mapping[str, object], cls: T.Type[_DI], msg: str,
# function in the case it wants to merge values.
ws_v = raw_from_workspace.get(orig_k)
k = fixup_meson_varname(orig_k)
if k not in fields:
if k not in typedict:
if orig_k not in ignored_fields:
unexpected.add(orig_k)
continue
if k in kwargs:
new_dict[k] = kwargs[k].convert(v, ws_v)
v = kwargs[k].convert(v, ws_v)
else:
new_dict[k] = v if v is not None else ws_v
v = v if v is not None else ws_v
if not typedict[k](v):
# treat it as a Meson bug if the type is not TOML-native
if isinstance(v, (int, str, bool, list, dict)):
raise MesonException(f'unexpected type for key "{k}": "{type(v)}"')
else:
raise Exception(f'unexpected type for key "{k}": "{type(v)}"')
new_dict[k] = v

if inherit:
# Inherit any keys from the workspace that we don't have yet.
for orig_k, ws_v in raw_from_workspace.items():
k = fixup_meson_varname(orig_k)
if k not in fields:
if k not in typedict:
if orig_k not in ignored_fields:
unexpected.add(orig_k)
continue
if k in new_dict:
continue
if k in kwargs:
new_dict[k] = kwargs[k].convert(None, ws_v)
v = kwargs[k].convert(None, ws_v)
else:
new_dict[k] = ws_v
v = ws_v
if not typedict[k](v):
# treat it as a Meson bug if the type is not TOML-native
if isinstance(v, (int, str, bool, list, dict)):
raise MesonException(f'unexpected type for key "{k}": "{type(v)}"')
else:
raise MesonBugException(f'unexpected type for key "{k}": "{type(v)}"')
new_dict[k] = v

# Finally, set default values.
for k, convertor in kwargs.items():
Expand Down
41 changes: 20 additions & 21 deletions mesonbuild/cargo/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,23 @@
from __future__ import annotations
import typing as T

from typing_extensions import Literal, TypedDict, Required
EDITION = T.Literal['2015', '2018', '2021']
CRATE_TYPE = T.Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro']
LINT_LEVEL = T.Literal['allow', 'deny', 'forbid', 'warn']

EDITION = Literal['2015', '2018', '2021']
CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro']
LINT_LEVEL = Literal['allow', 'deny', 'forbid', 'warn']


class FromWorkspace(TypedDict):
class FromWorkspace(T.TypedDict, total=False):

"""An entry or section that is copied from the workspace."""

workspace: bool


Package = TypedDict(
Package = T.TypedDict(
'Package',
{
'name': Required[str],
'version': Required[T.Union[FromWorkspace, str]],
'name': T.Required[str],
'version': T.Required[T.Union[FromWorkspace, str]],
'authors': T.Union[FromWorkspace, T.List[str]],
'edition': T.Union[FromWorkspace, EDITION],
'rust-version': T.Union[FromWorkspace, str],
Expand Down Expand Up @@ -55,15 +53,15 @@ class FromWorkspace(TypedDict):
)
"""A description of the Package Dictionary."""

class Badge(TypedDict):
class Badge(T.TypedDict, total=False):

"""An entry in the badge section."""

status: Literal['actively-developed', 'passively-developed', 'as-is', 'experimental', 'deprecated', 'none']
status: T.Literal['actively-developed', 'passively-developed', 'as-is', 'experimental', 'deprecated', 'none']
repository: str


Dependency = TypedDict(
Dependency = T.TypedDict(
'Dependency',
{
'version': str,
Expand All @@ -86,7 +84,7 @@ class Badge(TypedDict):
"""A Dependency entry, either a string or a Dependency Dict."""


_BaseBuildTarget = TypedDict(
_BaseBuildTarget = T.TypedDict(
'_BaseBuildTarget',
{
'path': str,
Expand All @@ -107,25 +105,25 @@ class Badge(TypedDict):

class BuildTarget(_BaseBuildTarget, total=False):

name: Required[str]
name: T.Required[str]


class LibTarget(_BaseBuildTarget, total=False):

name: str


class Target(TypedDict):
class Target(T.TypedDict, total=False):

"""Target entry in the Manifest File."""

dependencies: T.Dict[str, T.Union[FromWorkspace, DependencyV]]


Lint = TypedDict(
Lint = T.TypedDict(
'Lint',
{
'level': Required[LINT_LEVEL],
'level': T.Required[LINT_LEVEL],
'priority': int,
'check-cfg': T.List[str],
},
Expand All @@ -142,7 +140,7 @@ class Target(TypedDict):
"""A Lint entry, either a string or a Lint Dict."""


class Workspace(TypedDict):
class Workspace(T.TypedDict, total=False):

"""The representation of a workspace.

Expand All @@ -153,13 +151,14 @@ class Workspace(TypedDict):
the :attribute:`exclude` is always optional
"""

resolver: str
members: T.List[str]
exclude: T.List[str]
package: Package
dependencies: T.Dict[str, DependencyV]


Manifest = TypedDict(
Manifest = T.TypedDict(
'Manifest',
{
'package': Package,
Expand All @@ -185,7 +184,7 @@ class Workspace(TypedDict):
"""The Cargo Manifest format."""


class CargoLockPackage(TypedDict, total=False):
class CargoLockPackage(T.TypedDict, total=False):

"""A description of a package in the Cargo.lock file format."""

Expand All @@ -195,7 +194,7 @@ class CargoLockPackage(TypedDict, total=False):
checksum: str


class CargoLock(TypedDict, total=False):
class CargoLock(T.TypedDict, total=False):

"""A description of the Cargo.lock file format."""

Expand Down
Loading
Loading