1+ # Copyright 2022 The Matrix.org Foundation C.I.C.
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+ #
15+
16+ """
17+ This module exposes a single function which checks synapse's dependencies are present
18+ and correctly versioned. It makes use of `importlib.metadata` to do so. The details
19+ are a bit murky: there's no easy way to get a map from "extras" to the packages they
20+ require. But this is probably just symptomatic of Python's package management.
21+ """
22+
123import logging
224from typing import Iterable , NamedTuple , Optional
325
1032except ImportError :
1133 import importlib_metadata as metadata # type: ignore[no-redef]
1234
35+ __all__ = ["check_requirements" ]
36+
1337
1438class DependencyException (Exception ):
1539 @property
@@ -29,7 +53,17 @@ def dependencies(self) -> Iterable[str]:
2953 yield '"' + i + '"'
3054
3155
32- EXTRAS = set (metadata .metadata (DISTRIBUTION_NAME ).get_all ("Provides-Extra" ))
56+ DEV_EXTRAS = {"lint" , "mypy" , "test" , "dev" }
57+ RUNTIME_EXTRAS = (
58+ set (metadata .metadata (DISTRIBUTION_NAME ).get_all ("Provides-Extra" )) - DEV_EXTRAS
59+ )
60+ VERSION = metadata .version (DISTRIBUTION_NAME )
61+
62+
63+ def _is_dev_dependency (req : Requirement ) -> bool :
64+ return req .marker is not None and any (
65+ req .marker .evaluate ({"extra" : e }) for e in DEV_EXTRAS
66+ )
3367
3468
3569class Dependency (NamedTuple ):
@@ -43,6 +77,9 @@ def _generic_dependencies() -> Iterable[Dependency]:
4377 assert requirements is not None
4478 for raw_requirement in requirements :
4579 req = Requirement (raw_requirement )
80+ if _is_dev_dependency (req ):
81+ continue
82+
4683 # https://packaging.pypa.io/en/latest/markers.html#usage notes that
4784 # > Evaluating an extra marker with no environment is an error
4885 # so we pass in a dummy empty extra value here.
@@ -56,6 +93,8 @@ def _dependencies_for_extra(extra: str) -> Iterable[Dependency]:
5693 assert requirements is not None
5794 for raw_requirement in requirements :
5895 req = Requirement (raw_requirement )
96+ if _is_dev_dependency (req ):
97+ continue
5998 # Exclude mandatory deps by only selecting deps needed with this extra.
6099 if (
61100 req .marker is not None
@@ -67,18 +106,26 @@ def _dependencies_for_extra(extra: str) -> Iterable[Dependency]:
67106
68107def _not_installed (requirement : Requirement , extra : Optional [str ] = None ) -> str :
69108 if extra :
70- return f"Need { requirement .name } for { extra } , but it is not installed"
109+ return (
110+ f"Synapse { VERSION } needs { requirement .name } for { extra } , "
111+ f"but it is not installed"
112+ )
71113 else :
72- return f"Need { requirement .name } , but it is not installed"
114+ return f"Synapse { VERSION } needs { requirement .name } , but it is not installed"
73115
74116
75117def _incorrect_version (
76118 requirement : Requirement , got : str , extra : Optional [str ] = None
77119) -> str :
78120 if extra :
79- return f"Need { requirement } for { extra } , but got { requirement .name } =={ got } "
121+ return (
122+ f"Synapse { VERSION } needs { requirement } for { extra } , "
123+ f"but got { requirement .name } =={ got } "
124+ )
80125 else :
81- return f"Need { requirement } , but got { requirement .name } =={ got } "
126+ return (
127+ f"Synapse { VERSION } needs { requirement } , but got { requirement .name } =={ got } "
128+ )
82129
83130
84131def check_requirements (extra : Optional [str ] = None ) -> None :
@@ -100,10 +147,10 @@ def check_requirements(extra: Optional[str] = None) -> None:
100147 # First work out which dependencies are required, and which are optional.
101148 if extra is None :
102149 dependencies = _generic_dependencies ()
103- elif extra in EXTRAS :
150+ elif extra in RUNTIME_EXTRAS :
104151 dependencies = _dependencies_for_extra (extra )
105152 else :
106- raise ValueError (f"Synapse does not provide the feature '{ extra } '" )
153+ raise ValueError (f"Synapse { VERSION } does not provide the feature '{ extra } '" )
107154
108155 deps_unfulfilled = []
109156 errors = []
0 commit comments