167 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
| # Copyright 2015, 2016 OpenMarket Ltd
 | |
| # Copyright 2017 Vector Creations Ltd
 | |
| # Copyright 2018 New Vector Ltd
 | |
| #
 | |
| # 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 pkg_resources import DistributionNotFound, VersionConflict, get_distribution
 | |
| 
 | |
| 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
 | |
| #
 | |
| # [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.1.3",
 | |
|     "signedjson>=1.0.0",
 | |
|     "pynacl>=1.2.1",
 | |
|     "service_identity>=16.0.0",
 | |
| 
 | |
|     # our logcontext handling relies on the ability to cancel inlineCallbacks
 | |
|     # (https://twistedmatrix.com/trac/ticket/4632) which landed in Twisted 18.7.
 | |
|     "Twisted>=18.7.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",
 | |
|     "daemonize>=2.3.1",
 | |
|     "bcrypt>=3.1.0",
 | |
|     "pillow>=3.1.2",
 | |
|     "sortedcontainers>=1.4.4",
 | |
|     "psutil>=2.0.0",
 | |
|     "pymacaroons>=0.13.0",
 | |
|     "msgpack>=0.5.0",
 | |
|     "phonenumbers>=8.2.0",
 | |
|     "six>=1.10",
 | |
|     # prometheus_client 0.4.0 changed the format of counter metrics
 | |
|     # (cf https://github.com/matrix-org/synapse/issues/4001)
 | |
|     "prometheus_client>=0.0.18,<0.4.0",
 | |
| 
 | |
|     # we use attr.s(slots), which arrived in 16.0.0
 | |
|     # Twisted 18.7.0 requires attrs>=17.4.0
 | |
|     "attrs>=17.4.0",
 | |
| 
 | |
|     "netaddr>=0.7.18",
 | |
| ]
 | |
| 
 | |
| CONDITIONAL_REQUIREMENTS = {
 | |
|     "email.enable_notifs": ["Jinja2>=2.9", "bleach>=1.4.2"],
 | |
|     "matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
 | |
|     "postgres": ["psycopg2>=2.6"],
 | |
| 
 | |
|     # ConsentResource uses select_autoescape, which arrived in jinja 2.9
 | |
|     "resources.consent": ["Jinja2>=2.9"],
 | |
| 
 | |
|     # ACME support is required to provision TLS certificates from authorities
 | |
|     # that use the protocol, such as Let's Encrypt.
 | |
|     "acme": ["txacme>=0.9.2"],
 | |
| 
 | |
|     "saml2": ["pysaml2>=4.5.0"],
 | |
|     "url_preview": ["lxml>=3.5.0"],
 | |
|     "test": ["mock>=2.0", "parameterized"],
 | |
| }
 | |
| 
 | |
| 
 | |
| def list_requirements():
 | |
|     deps = set(REQUIREMENTS)
 | |
|     for opt in CONDITIONAL_REQUIREMENTS.values():
 | |
|         deps = set(opt) | deps
 | |
| 
 | |
|     return list(deps)
 | |
| 
 | |
| 
 | |
| 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, _get_distribution=get_distribution):
 | |
|     deps_needed = []
 | |
|     errors = []
 | |
| 
 | |
|     if for_feature:
 | |
|         reqs = CONDITIONAL_REQUIREMENTS[for_feature]
 | |
|     else:
 | |
|         reqs = REQUIREMENTS
 | |
| 
 | |
|     for dependency in reqs:
 | |
|         try:
 | |
|             _get_distribution(dependency)
 | |
|         except VersionConflict as e:
 | |
|             deps_needed.append(dependency)
 | |
|             errors.append(
 | |
|                 "Needed %s, got %s==%s"
 | |
|                 % (dependency, e.dist.project_name, e.dist.version)
 | |
|             )
 | |
|         except DistributionNotFound:
 | |
|             deps_needed.append(dependency)
 | |
|             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(), [])
 | |
| 
 | |
|         for dependency in OPTS:
 | |
|             try:
 | |
|                 _get_distribution(dependency)
 | |
|             except VersionConflict as e:
 | |
|                 deps_needed.append(dependency)
 | |
|                 errors.append(
 | |
|                     "Needed optional %s, got %s==%s"
 | |
|                     % (dependency, e.dist.project_name, e.dist.version)
 | |
|                 )
 | |
|             except DistributionNotFound:
 | |
|                 # If it's not found, we don't care
 | |
|                 pass
 | |
| 
 | |
|     if deps_needed:
 | |
|         for e in errors:
 | |
|             logging.error(e)
 | |
| 
 | |
|         raise DependencyException(deps_needed)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     import sys
 | |
| 
 | |
|     sys.stdout.writelines(req + "\n" for req in list_requirements())
 |