2016-01-06 21:26:29 -07:00
|
|
|
# Copyright 2015, 2016 OpenMarket Ltd
|
2017-03-13 11:27:51 -06:00
|
|
|
# Copyright 2017 Vector Creations Ltd
|
2018-04-26 05:34:40 -06:00
|
|
|
# Copyright 2018 New Vector Ltd
|
2020-01-30 04:25:59 -07:00
|
|
|
# Copyright 2020 The Matrix.org Foundation C.I.C.
|
2015-04-24 03:35:29 -06:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2015-01-08 10:07:28 -07:00
|
|
|
import logging
|
2019-10-02 06:29:01 -06:00
|
|
|
from typing import List, Set
|
2018-12-21 07:37:26 -07:00
|
|
|
|
2019-05-21 08:58:01 -06:00
|
|
|
from pkg_resources import (
|
|
|
|
DistributionNotFound,
|
|
|
|
Requirement,
|
|
|
|
VersionConflict,
|
|
|
|
get_provider,
|
|
|
|
)
|
2015-01-08 10:07:28 -07:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2018-12-21 07:37:26 -07:00
|
|
|
|
|
|
|
# REQUIREMENTS is a simple list of requirement specifiers[1], and must be
|
|
|
|
# installed. It is passed to setup() as install_requires in setup.py.
|
2018-04-26 05:34:40 -06:00
|
|
|
#
|
2018-12-21 07:37:26 -07:00
|
|
|
# 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
|
2018-04-26 05:34:40 -06:00
|
|
|
#
|
2020-09-23 04:45:37 -06:00
|
|
|
# Note that these both represent runtime dependencies (and the versions
|
|
|
|
# installed are checked at runtime).
|
|
|
|
#
|
2018-04-26 05:34:40 -06:00
|
|
|
# [1] https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers.
|
|
|
|
|
2018-12-21 07:37:26 -07:00
|
|
|
REQUIREMENTS = [
|
|
|
|
"jsonschema>=2.5.1",
|
|
|
|
"frozendict>=1",
|
|
|
|
"unpaddedbase64>=1.1.0",
|
2020-09-07 05:21:38 -06:00
|
|
|
"canonicaljson>=1.4.0",
|
2020-01-30 04:25:59 -07:00
|
|
|
# we use the type definitions added in signedjson 1.1.
|
|
|
|
"signedjson>=1.1.0",
|
2018-12-21 07:37:26 -07:00
|
|
|
"pynacl>=1.2.1",
|
2019-06-12 05:31:59 -06:00
|
|
|
"idna>=2.5",
|
2019-06-10 08:58:35 -06:00
|
|
|
# validating SSL certs for IP addresses requires service_identity 18.1.
|
|
|
|
"service_identity>=18.1.0",
|
2019-08-28 05:18:53 -06:00
|
|
|
# Twisted 18.9 introduces some logger improvements that the structured
|
|
|
|
# logger utilises
|
|
|
|
"Twisted>=18.9.0",
|
2018-12-21 07:37:26 -07:00
|
|
|
"treq>=15.1",
|
2018-09-06 05:53:15 -06:00
|
|
|
# Twisted has required pyopenssl 16.0 since about Twisted 16.6.
|
2018-12-21 07:37:26 -07:00
|
|
|
"pyopenssl>=16.0.0",
|
|
|
|
"pyyaml>=3.11",
|
|
|
|
"pyasn1>=0.1.9",
|
|
|
|
"pyasn1-modules>=0.0.7",
|
|
|
|
"bcrypt>=3.1.0",
|
2019-05-16 12:04:26 -06:00
|
|
|
"pillow>=4.3.0",
|
2018-12-21 07:37:26 -07:00
|
|
|
"sortedcontainers>=1.4.4",
|
2019-01-16 01:44:22 -07:00
|
|
|
"pymacaroons>=0.13.0",
|
2019-06-12 05:31:59 -06:00
|
|
|
"msgpack>=0.5.2",
|
2018-12-21 07:37:26 -07:00
|
|
|
"phonenumbers>=8.2.0",
|
2020-09-30 09:42:05 -06:00
|
|
|
# we use GaugeHistogramMetric, which was added in prom-client 0.4.0.
|
|
|
|
# prom-client has a history of breaking backwards compatibility between
|
|
|
|
# minor versions (https://github.com/prometheus/client_python/issues/317),
|
|
|
|
# so we also pin the minor version.
|
|
|
|
"prometheus_client>=0.4.0,<0.9.0",
|
2020-08-28 10:12:45 -06:00
|
|
|
# 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.)
|
2020-06-16 05:44:07 -06:00
|
|
|
"attrs>=19.1.0",
|
2018-12-21 07:37:26 -07:00
|
|
|
"netaddr>=0.7.18",
|
2019-06-21 09:52:09 -06:00
|
|
|
"Jinja2>=2.9",
|
|
|
|
"bleach>=1.4.3",
|
2019-10-02 06:29:01 -06:00
|
|
|
"typing-extensions>=3.7.4",
|
2018-12-21 07:37:26 -07:00
|
|
|
]
|
2018-06-05 08:13:57 -06:00
|
|
|
|
2015-03-17 05:45:37 -06:00
|
|
|
CONDITIONAL_REQUIREMENTS = {
|
2018-12-21 07:37:26 -07:00
|
|
|
"matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
|
2019-04-09 04:29:50 -06:00
|
|
|
# we use execute_batch, which arrived in psycopg 2.7.
|
|
|
|
"postgres": ["psycopg2>=2.7"],
|
2019-01-23 01:39:06 -07:00
|
|
|
# ACME support is required to provision TLS certificates from authorities
|
|
|
|
# that use the protocol, such as Let's Encrypt.
|
2019-05-21 08:58:01 -06:00
|
|
|
"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"',
|
|
|
|
],
|
2018-12-21 07:37:26 -07:00
|
|
|
"saml2": ["pysaml2>=4.5.0"],
|
2020-05-08 06:30:40 -06:00
|
|
|
"oidc": ["authlib>=0.14.0"],
|
2019-04-16 04:41:17 -06:00
|
|
|
"systemd": ["systemd-python>=231"],
|
2018-12-21 07:37:26 -07:00
|
|
|
"url_preview": ["lxml>=3.5.0"],
|
2019-02-12 06:55:58 -07:00
|
|
|
"sentry": ["sentry-sdk>=0.7.2"],
|
2019-07-11 03:36:03 -06:00
|
|
|
"opentracing": ["jaeger-client>=4.0.0", "opentracing>=2.2.0"],
|
2019-06-27 05:02:41 -06:00
|
|
|
"jwt": ["pyjwt>=1.6.4"],
|
2020-05-06 10:36:46 -06:00
|
|
|
# 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"],
|
2015-03-17 05:45:37 -06:00
|
|
|
}
|
|
|
|
|
2019-09-12 10:29:55 -06:00
|
|
|
ALL_OPTIONAL_REQUIREMENTS = set() # type: Set[str]
|
2019-04-16 04:41:17 -06:00
|
|
|
|
|
|
|
for name, optional_deps in CONDITIONAL_REQUIREMENTS.items():
|
|
|
|
# Exclude systemd as it's a system-based requirement.
|
2020-09-17 14:45:22 -06:00
|
|
|
# Exclude lint as it's a dev-based requirement.
|
2020-09-23 04:45:37 -06:00
|
|
|
if name not in ["systemd"]:
|
2019-04-16 04:41:17 -06:00
|
|
|
ALL_OPTIONAL_REQUIREMENTS = set(optional_deps) | ALL_OPTIONAL_REQUIREMENTS
|
2015-03-17 05:45:37 -06:00
|
|
|
|
2015-01-08 10:07:28 -07:00
|
|
|
|
2019-04-16 04:41:17 -06:00
|
|
|
def list_requirements():
|
|
|
|
return list(set(REQUIREMENTS) | ALL_OPTIONAL_REQUIREMENTS)
|
2015-02-10 09:30:48 -07:00
|
|
|
|
2015-02-02 10:22:40 -07:00
|
|
|
|
2018-12-21 07:37:26 -07:00
|
|
|
class DependencyException(Exception):
|
2019-01-07 03:14:31 -07:00
|
|
|
@property
|
|
|
|
def message(self):
|
2019-06-20 03:32:02 -06:00
|
|
|
return "\n".join(
|
|
|
|
[
|
|
|
|
"Missing Requirements: %s" % (", ".join(self.dependencies),),
|
|
|
|
"To install run:",
|
|
|
|
" pip install --upgrade --force %s" % (" ".join(self.dependencies),),
|
|
|
|
"",
|
|
|
|
]
|
|
|
|
)
|
2019-01-07 03:14:31 -07:00
|
|
|
|
2018-12-21 07:37:26 -07:00
|
|
|
@property
|
|
|
|
def dependencies(self):
|
|
|
|
for i in self.args[0]:
|
2019-05-21 08:58:01 -06:00
|
|
|
yield "'" + i + "'"
|
2016-11-15 04:22:29 -07:00
|
|
|
|
2015-02-02 10:22:40 -07:00
|
|
|
|
2019-05-21 08:58:01 -06:00
|
|
|
def check_requirements(for_feature=None):
|
2018-12-21 07:37:26 -07:00
|
|
|
deps_needed = []
|
|
|
|
errors = []
|
2015-01-08 10:07:28 -07:00
|
|
|
|
2019-01-07 03:14:31 -07:00
|
|
|
if for_feature:
|
|
|
|
reqs = CONDITIONAL_REQUIREMENTS[for_feature]
|
|
|
|
else:
|
|
|
|
reqs = REQUIREMENTS
|
|
|
|
|
|
|
|
for dependency in reqs:
|
2018-12-21 07:37:26 -07:00
|
|
|
try:
|
2019-05-21 08:58:01 -06:00
|
|
|
_check_requirement(dependency)
|
2018-12-21 07:37:26 -07:00
|
|
|
except VersionConflict as e:
|
|
|
|
deps_needed.append(dependency)
|
|
|
|
errors.append(
|
|
|
|
"Needed %s, got %s==%s"
|
2019-10-02 06:29:01 -06:00
|
|
|
% (
|
|
|
|
dependency,
|
|
|
|
e.dist.project_name, # type: ignore[attr-defined] # noqa
|
|
|
|
e.dist.version, # type: ignore[attr-defined] # noqa
|
|
|
|
)
|
2015-01-08 10:07:28 -07:00
|
|
|
)
|
2018-12-21 07:37:26 -07:00
|
|
|
except DistributionNotFound:
|
|
|
|
deps_needed.append(dependency)
|
2019-09-11 07:00:37 -06:00
|
|
|
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,))
|
2015-01-08 10:07:28 -07:00
|
|
|
|
2019-01-07 03:14:31 -07:00
|
|
|
if not for_feature:
|
|
|
|
# Check the optional dependencies are up to date. We allow them to not be
|
|
|
|
# installed.
|
2019-10-02 06:29:01 -06:00
|
|
|
OPTS = sum(CONDITIONAL_REQUIREMENTS.values(), []) # type: List[str]
|
2019-01-07 03:14:31 -07:00
|
|
|
|
|
|
|
for dependency in OPTS:
|
|
|
|
try:
|
2019-05-21 08:58:01 -06:00
|
|
|
_check_requirement(dependency)
|
2019-01-23 10:41:59 -07:00
|
|
|
except VersionConflict as e:
|
2019-01-07 03:14:31 -07:00
|
|
|
deps_needed.append(dependency)
|
2019-01-23 10:41:59 -07:00
|
|
|
errors.append(
|
|
|
|
"Needed optional %s, got %s==%s"
|
2019-10-02 06:29:01 -06:00
|
|
|
% (
|
|
|
|
dependency,
|
|
|
|
e.dist.project_name, # type: ignore[attr-defined] # noqa
|
|
|
|
e.dist.version, # type: ignore[attr-defined] # noqa
|
|
|
|
)
|
2019-01-23 10:41:59 -07:00
|
|
|
)
|
2019-01-07 03:14:31 -07:00
|
|
|
except DistributionNotFound:
|
|
|
|
# If it's not found, we don't care
|
|
|
|
pass
|
2015-02-10 09:30:48 -07:00
|
|
|
|
2018-12-21 07:37:26 -07:00
|
|
|
if deps_needed:
|
2019-09-12 10:29:55 -06:00
|
|
|
for err in errors:
|
|
|
|
logging.error(err)
|
2018-12-21 07:37:26 -07:00
|
|
|
|
|
|
|
raise DependencyException(deps_needed)
|
2015-02-02 10:22:40 -07:00
|
|
|
|
2016-11-15 04:22:29 -07:00
|
|
|
|
2019-05-21 08:58:01 -06:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2015-02-02 10:22:40 -07:00
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
2018-12-21 07:37:26 -07:00
|
|
|
|
2015-02-02 10:22:40 -07:00
|
|
|
sys.stdout.writelines(req + "\n" for req in list_requirements())
|