|
- # Copyright 2015, 2016 OpenMarket Ltd
- # Copyright 2017 Vector Creations Ltd
- # Copyright 2018 New Vector Ltd
- # Copyright 2020 The Matrix.org Foundation C.I.C.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
-
- import logging
- from typing import List, Set
-
- from pkg_resources import (
- DistributionNotFound,
- Requirement,
- VersionConflict,
- get_provider,
- )
-
- logger = logging.getLogger(__name__)
-
-
- # REQUIREMENTS is a simple list of requirement specifiers[1], and must be
- # installed. It is passed to setup() as install_requires in setup.py.
- #
- # CONDITIONAL_REQUIREMENTS is the optional dependencies, represented as a dict
- # of lists. The dict key is the optional dependency name and can be passed to
- # pip when installing. The list is a series of requirement specifiers[1] to be
- # installed when that optional dependency requirement is specified. It is passed
- # to setup() as extras_require in setup.py
- #
- # Note that these both represent runtime dependencies (and the versions
- # installed are checked at runtime).
- #
- # [1] https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers.
-
- REQUIREMENTS = [
- "jsonschema>=2.5.1",
- "frozendict>=1",
- "unpaddedbase64>=1.1.0",
- "canonicaljson>=1.4.0",
- # we use the type definitions added in signedjson 1.1.
- "signedjson>=1.1.0",
- "pynacl>=1.2.1",
- "idna>=2.5",
- # validating SSL certs for IP addresses requires service_identity 18.1.
- "service_identity>=18.1.0",
- # Twisted 18.9 introduces some logger improvements that the structured
- # logger utilises
- "Twisted>=18.9.0",
- "treq>=15.1",
- # Twisted has required pyopenssl 16.0 since about Twisted 16.6.
- "pyopenssl>=16.0.0",
- "pyyaml>=3.11",
- "pyasn1>=0.1.9",
- "pyasn1-modules>=0.0.7",
- "bcrypt>=3.1.0",
- "pillow>=4.3.0",
- "sortedcontainers>=1.4.4",
- "pymacaroons>=0.13.0",
- "msgpack>=0.5.2",
- "phonenumbers>=8.2.0",
- "prometheus_client>=0.0.18,<0.9.0",
- # we use attr.validators.deep_iterable, which arrived in 19.1.0 (Note:
- # Fedora 31 only has 19.1, so if we want to upgrade we should wait until 33
- # is out in November.)
- "attrs>=19.1.0",
- "netaddr>=0.7.18",
- "Jinja2>=2.9",
- "bleach>=1.4.3",
- "typing-extensions>=3.7.4",
- ]
-
- CONDITIONAL_REQUIREMENTS = {
- "matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
- # we use execute_batch, which arrived in psycopg 2.7.
- "postgres": ["psycopg2>=2.7"],
- # ACME support is required to provision TLS certificates from authorities
- # that use the protocol, such as Let's Encrypt.
- "acme": [
- "txacme>=0.9.2",
- # txacme depends on eliot. Eliot 1.8.0 is incompatible with
- # python 3.5.2, as per https://github.com/itamarst/eliot/issues/418
- 'eliot<1.8.0;python_version<"3.5.3"',
- ],
- "saml2": ["pysaml2>=4.5.0"],
- "oidc": ["authlib>=0.14.0"],
- "systemd": ["systemd-python>=231"],
- "url_preview": ["lxml>=3.5.0"],
- "sentry": ["sentry-sdk>=0.7.2"],
- "opentracing": ["jaeger-client>=4.0.0", "opentracing>=2.2.0"],
- "jwt": ["pyjwt>=1.6.4"],
- # hiredis is not a *strict* dependency, but it makes things much faster.
- # (if it is not installed, we fall back to slow code.)
- "redis": ["txredisapi>=1.4.7", "hiredis"],
- }
-
- ALL_OPTIONAL_REQUIREMENTS = set() # type: Set[str]
-
- for name, optional_deps in CONDITIONAL_REQUIREMENTS.items():
- # Exclude systemd as it's a system-based requirement.
- # Exclude lint as it's a dev-based requirement.
- if name not in ["systemd"]:
- ALL_OPTIONAL_REQUIREMENTS = set(optional_deps) | ALL_OPTIONAL_REQUIREMENTS
-
-
- def list_requirements():
- return list(set(REQUIREMENTS) | ALL_OPTIONAL_REQUIREMENTS)
-
-
- class DependencyException(Exception):
- @property
- def message(self):
- return "\n".join(
- [
- "Missing Requirements: %s" % (", ".join(self.dependencies),),
- "To install run:",
- " pip install --upgrade --force %s" % (" ".join(self.dependencies),),
- "",
- ]
- )
-
- @property
- def dependencies(self):
- for i in self.args[0]:
- yield "'" + i + "'"
-
-
- def check_requirements(for_feature=None):
- deps_needed = []
- errors = []
-
- if for_feature:
- reqs = CONDITIONAL_REQUIREMENTS[for_feature]
- else:
- reqs = REQUIREMENTS
-
- for dependency in reqs:
- try:
- _check_requirement(dependency)
- except VersionConflict as e:
- deps_needed.append(dependency)
- errors.append(
- "Needed %s, got %s==%s"
- % (
- dependency,
- e.dist.project_name, # type: ignore[attr-defined] # noqa
- e.dist.version, # type: ignore[attr-defined] # noqa
- )
- )
- except DistributionNotFound:
- deps_needed.append(dependency)
- if for_feature:
- errors.append(
- "Needed %s for the '%s' feature but it was not installed"
- % (dependency, for_feature)
- )
- else:
- errors.append("Needed %s but it was not installed" % (dependency,))
-
- if not for_feature:
- # Check the optional dependencies are up to date. We allow them to not be
- # installed.
- OPTS = sum(CONDITIONAL_REQUIREMENTS.values(), []) # type: List[str]
-
- for dependency in OPTS:
- try:
- _check_requirement(dependency)
- except VersionConflict as e:
- deps_needed.append(dependency)
- errors.append(
- "Needed optional %s, got %s==%s"
- % (
- dependency,
- e.dist.project_name, # type: ignore[attr-defined] # noqa
- e.dist.version, # type: ignore[attr-defined] # noqa
- )
- )
- except DistributionNotFound:
- # If it's not found, we don't care
- pass
-
- if deps_needed:
- for err in errors:
- logging.error(err)
-
- raise DependencyException(deps_needed)
-
-
- def _check_requirement(dependency_string):
- """Parses a dependency string, and checks if the specified requirement is installed
-
- Raises:
- VersionConflict if the requirement is installed, but with the the wrong version
- DistributionNotFound if nothing is found to provide the requirement
- """
- req = Requirement.parse(dependency_string)
-
- # first check if the markers specify that this requirement needs installing
- if req.marker is not None and not req.marker.evaluate():
- # not required for this environment
- return
-
- get_provider(req)
-
-
- if __name__ == "__main__":
- import sys
-
- sys.stdout.writelines(req + "\n" for req in list_requirements())
|