You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

224 line
7.6 KiB

  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2017 Vector Creations Ltd
  3. # Copyright 2018 New Vector Ltd
  4. # Copyright 2020 The Matrix.org Foundation C.I.C.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import logging
  18. from typing import List, Set
  19. from pkg_resources import (
  20. DistributionNotFound,
  21. Requirement,
  22. VersionConflict,
  23. get_provider,
  24. )
  25. logger = logging.getLogger(__name__)
  26. # REQUIREMENTS is a simple list of requirement specifiers[1], and must be
  27. # installed. It is passed to setup() as install_requires in setup.py.
  28. #
  29. # CONDITIONAL_REQUIREMENTS is the optional dependencies, represented as a dict
  30. # of lists. The dict key is the optional dependency name and can be passed to
  31. # pip when installing. The list is a series of requirement specifiers[1] to be
  32. # installed when that optional dependency requirement is specified. It is passed
  33. # to setup() as extras_require in setup.py
  34. #
  35. # [1] https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers.
  36. REQUIREMENTS = [
  37. "jsonschema>=2.5.1",
  38. "frozendict>=1",
  39. "unpaddedbase64>=1.1.0",
  40. "canonicaljson>=1.4.0",
  41. # we use the type definitions added in signedjson 1.1.
  42. "signedjson>=1.1.0",
  43. "pynacl>=1.2.1",
  44. "idna>=2.5",
  45. # validating SSL certs for IP addresses requires service_identity 18.1.
  46. "service_identity>=18.1.0",
  47. # Twisted 18.9 introduces some logger improvements that the structured
  48. # logger utilises
  49. "Twisted>=18.9.0",
  50. "treq>=15.1",
  51. # Twisted has required pyopenssl 16.0 since about Twisted 16.6.
  52. "pyopenssl>=16.0.0",
  53. "pyyaml>=3.11",
  54. "pyasn1>=0.1.9",
  55. "pyasn1-modules>=0.0.7",
  56. "bcrypt>=3.1.0",
  57. "pillow>=4.3.0",
  58. "sortedcontainers>=1.4.4",
  59. "pymacaroons>=0.13.0",
  60. "msgpack>=0.5.2",
  61. "phonenumbers>=8.2.0",
  62. "prometheus_client>=0.0.18,<0.9.0",
  63. # we use attr.validators.deep_iterable, which arrived in 19.1.0 (Note:
  64. # Fedora 31 only has 19.1, so if we want to upgrade we should wait until 33
  65. # is out in November.)
  66. "attrs>=19.1.0",
  67. "netaddr>=0.7.18",
  68. "Jinja2>=2.9",
  69. "bleach>=1.4.3",
  70. "typing-extensions>=3.7.4",
  71. ]
  72. CONDITIONAL_REQUIREMENTS = {
  73. "matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
  74. # we use execute_batch, which arrived in psycopg 2.7.
  75. "postgres": ["psycopg2>=2.7"],
  76. # ACME support is required to provision TLS certificates from authorities
  77. # that use the protocol, such as Let's Encrypt.
  78. "acme": [
  79. "txacme>=0.9.2",
  80. # txacme depends on eliot. Eliot 1.8.0 is incompatible with
  81. # python 3.5.2, as per https://github.com/itamarst/eliot/issues/418
  82. 'eliot<1.8.0;python_version<"3.5.3"',
  83. ],
  84. "saml2": ["pysaml2>=4.5.0"],
  85. "oidc": ["authlib>=0.14.0"],
  86. "systemd": ["systemd-python>=231"],
  87. "url_preview": ["lxml>=3.5.0"],
  88. # Dependencies which are exclusively required by unit test code. This is
  89. # NOT a list of all modules that are necessary to run the unit tests.
  90. # Tests assume that all optional dependencies are installed.
  91. #
  92. # parameterized_class decorator was introduced in parameterized 0.7.0
  93. "test": ["mock>=2.0", "parameterized>=0.7.0"],
  94. "sentry": ["sentry-sdk>=0.7.2"],
  95. "opentracing": ["jaeger-client>=4.0.0", "opentracing>=2.2.0"],
  96. "jwt": ["pyjwt>=1.6.4"],
  97. # hiredis is not a *strict* dependency, but it makes things much faster.
  98. # (if it is not installed, we fall back to slow code.)
  99. "redis": ["txredisapi>=1.4.7", "hiredis"],
  100. # We pin black so that our tests don't start failing on new releases.
  101. "lint": ["isort==5.0.3", "black==19.10b0", "flake8-comprehensions", "flake8"],
  102. }
  103. ALL_OPTIONAL_REQUIREMENTS = set() # type: Set[str]
  104. for name, optional_deps in CONDITIONAL_REQUIREMENTS.items():
  105. # Exclude systemd as it's a system-based requirement.
  106. # Exclude lint as it's a dev-based requirement.
  107. if name not in ["systemd", "lint"]:
  108. ALL_OPTIONAL_REQUIREMENTS = set(optional_deps) | ALL_OPTIONAL_REQUIREMENTS
  109. def list_requirements():
  110. return list(set(REQUIREMENTS) | ALL_OPTIONAL_REQUIREMENTS)
  111. class DependencyException(Exception):
  112. @property
  113. def message(self):
  114. return "\n".join(
  115. [
  116. "Missing Requirements: %s" % (", ".join(self.dependencies),),
  117. "To install run:",
  118. " pip install --upgrade --force %s" % (" ".join(self.dependencies),),
  119. "",
  120. ]
  121. )
  122. @property
  123. def dependencies(self):
  124. for i in self.args[0]:
  125. yield "'" + i + "'"
  126. def check_requirements(for_feature=None):
  127. deps_needed = []
  128. errors = []
  129. if for_feature:
  130. reqs = CONDITIONAL_REQUIREMENTS[for_feature]
  131. else:
  132. reqs = REQUIREMENTS
  133. for dependency in reqs:
  134. try:
  135. _check_requirement(dependency)
  136. except VersionConflict as e:
  137. deps_needed.append(dependency)
  138. errors.append(
  139. "Needed %s, got %s==%s"
  140. % (
  141. dependency,
  142. e.dist.project_name, # type: ignore[attr-defined] # noqa
  143. e.dist.version, # type: ignore[attr-defined] # noqa
  144. )
  145. )
  146. except DistributionNotFound:
  147. deps_needed.append(dependency)
  148. if for_feature:
  149. errors.append(
  150. "Needed %s for the '%s' feature but it was not installed"
  151. % (dependency, for_feature)
  152. )
  153. else:
  154. errors.append("Needed %s but it was not installed" % (dependency,))
  155. if not for_feature:
  156. # Check the optional dependencies are up to date. We allow them to not be
  157. # installed.
  158. OPTS = sum(CONDITIONAL_REQUIREMENTS.values(), []) # type: List[str]
  159. for dependency in OPTS:
  160. try:
  161. _check_requirement(dependency)
  162. except VersionConflict as e:
  163. deps_needed.append(dependency)
  164. errors.append(
  165. "Needed optional %s, got %s==%s"
  166. % (
  167. dependency,
  168. e.dist.project_name, # type: ignore[attr-defined] # noqa
  169. e.dist.version, # type: ignore[attr-defined] # noqa
  170. )
  171. )
  172. except DistributionNotFound:
  173. # If it's not found, we don't care
  174. pass
  175. if deps_needed:
  176. for err in errors:
  177. logging.error(err)
  178. raise DependencyException(deps_needed)
  179. def _check_requirement(dependency_string):
  180. """Parses a dependency string, and checks if the specified requirement is installed
  181. Raises:
  182. VersionConflict if the requirement is installed, but with the the wrong version
  183. DistributionNotFound if nothing is found to provide the requirement
  184. """
  185. req = Requirement.parse(dependency_string)
  186. # first check if the markers specify that this requirement needs installing
  187. if req.marker is not None and not req.marker.evaluate():
  188. # not required for this environment
  189. return
  190. get_provider(req)
  191. if __name__ == "__main__":
  192. import sys
  193. sys.stdout.writelines(req + "\n" for req in list_requirements())