2015-11-17 08:18:42 +00:00
|
|
|
#!/usr/bin/env python3
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Generate an updated requirements_all.txt."""
|
2015-11-17 08:18:42 +00:00
|
|
|
import importlib
|
|
|
|
import os
|
|
|
|
import pkgutil
|
|
|
|
import re
|
2015-12-18 07:51:34 +00:00
|
|
|
import sys
|
2015-11-17 08:18:42 +00:00
|
|
|
|
2016-07-02 18:22:51 +00:00
|
|
|
COMMENT_REQUIREMENTS = (
|
2015-11-17 08:28:22 +00:00
|
|
|
'RPi.GPIO',
|
2017-05-05 07:02:47 +00:00
|
|
|
'raspihats',
|
2016-05-12 05:56:05 +00:00
|
|
|
'rpi-rf',
|
2015-12-17 17:16:36 +00:00
|
|
|
'Adafruit_Python_DHT',
|
2017-01-12 16:31:30 +00:00
|
|
|
'Adafruit_BBIO',
|
2016-01-29 05:37:08 +00:00
|
|
|
'fritzconnection',
|
2016-04-19 15:18:46 +00:00
|
|
|
'pybluez',
|
2017-04-04 21:57:19 +00:00
|
|
|
'beacontools',
|
2016-04-20 04:00:56 +00:00
|
|
|
'bluepy',
|
2017-05-03 18:08:21 +00:00
|
|
|
'opencv-python',
|
2016-05-23 05:19:10 +00:00
|
|
|
'python-lirc',
|
2016-09-12 04:59:48 +00:00
|
|
|
'gattlib',
|
|
|
|
'pyuserinput',
|
2016-09-28 07:05:38 +00:00
|
|
|
'evdev',
|
2016-11-03 08:31:50 +00:00
|
|
|
'pycups',
|
2017-01-18 05:48:33 +00:00
|
|
|
'python-eq3bt',
|
2017-01-21 22:14:08 +00:00
|
|
|
'avion',
|
2017-05-04 14:03:50 +00:00
|
|
|
'decora',
|
2017-05-12 02:20:23 +00:00
|
|
|
'face_recognition',
|
|
|
|
'blinkt',
|
2017-05-13 03:06:28 +00:00
|
|
|
'smbus-cffi',
|
2017-06-21 15:24:39 +00:00
|
|
|
'envirophat',
|
2017-10-13 06:57:45 +00:00
|
|
|
'i2csense',
|
2017-10-20 06:20:33 +00:00
|
|
|
'credstash',
|
2017-11-22 09:37:20 +00:00
|
|
|
'pytradfri',
|
2018-01-23 07:51:52 +00:00
|
|
|
'bme680',
|
2016-07-02 18:22:51 +00:00
|
|
|
)
|
|
|
|
|
2017-05-07 05:37:31 +00:00
|
|
|
TEST_REQUIREMENTS = (
|
|
|
|
'aioautomatic',
|
2017-09-12 16:47:04 +00:00
|
|
|
'aiohttp_cors',
|
2017-05-07 05:37:31 +00:00
|
|
|
'apns2',
|
2017-12-10 16:44:28 +00:00
|
|
|
'caldav',
|
2017-10-26 16:49:17 +00:00
|
|
|
'coinmarketcap',
|
2017-10-18 14:21:46 +00:00
|
|
|
'defusedxml',
|
2017-09-12 16:47:04 +00:00
|
|
|
'dsmr_parser',
|
|
|
|
'ephem',
|
|
|
|
'evohomeclient',
|
2017-09-24 06:12:38 +00:00
|
|
|
'feedparser',
|
2017-09-12 16:47:04 +00:00
|
|
|
'gTTS-token',
|
|
|
|
'ha-ffmpeg',
|
2017-09-24 06:12:38 +00:00
|
|
|
'haversine',
|
2017-05-07 05:37:31 +00:00
|
|
|
'hbmqtt',
|
2017-09-12 16:47:04 +00:00
|
|
|
'holidays',
|
2017-10-25 02:36:27 +00:00
|
|
|
'home-assistant-frontend',
|
2017-09-12 16:47:04 +00:00
|
|
|
'influxdb',
|
|
|
|
'libpurecoollink',
|
|
|
|
'libsoundtouch',
|
2017-05-07 05:37:31 +00:00
|
|
|
'mficlient',
|
2017-10-25 15:33:17 +00:00
|
|
|
'numpy',
|
2017-10-09 03:49:51 +00:00
|
|
|
'paho-mqtt',
|
2017-09-12 16:47:04 +00:00
|
|
|
'pexpect',
|
|
|
|
'pilight',
|
2017-05-07 05:37:31 +00:00
|
|
|
'pmsensor',
|
2017-09-12 16:47:04 +00:00
|
|
|
'prometheus_client',
|
2018-01-24 20:06:35 +00:00
|
|
|
'pushbullet.py',
|
2017-12-08 09:40:45 +00:00
|
|
|
'py-canary',
|
2017-10-09 03:49:51 +00:00
|
|
|
'pydispatcher',
|
2017-05-07 05:37:31 +00:00
|
|
|
'PyJWT',
|
2017-09-12 16:47:04 +00:00
|
|
|
'pylitejet',
|
2017-12-22 09:26:34 +00:00
|
|
|
'pymonoprice',
|
2017-10-09 03:49:51 +00:00
|
|
|
'pynx584',
|
|
|
|
'python-forecastio',
|
2017-06-17 18:09:27 +00:00
|
|
|
'pyunifi',
|
2017-09-12 16:47:04 +00:00
|
|
|
'pywebpush',
|
|
|
|
'restrictedpython',
|
|
|
|
'rflink',
|
|
|
|
'ring_doorbell',
|
|
|
|
'rxv',
|
|
|
|
'sleepyq',
|
|
|
|
'SoCo',
|
|
|
|
'somecomfort',
|
|
|
|
'sqlalchemy',
|
|
|
|
'statsd',
|
|
|
|
'uvcclient',
|
2018-02-16 22:07:38 +00:00
|
|
|
'voluptuous-serialize',
|
2017-09-12 16:47:04 +00:00
|
|
|
'warrant',
|
|
|
|
'yahoo-finance',
|
2017-11-01 10:15:24 +00:00
|
|
|
'pythonwhois',
|
2017-11-05 13:10:14 +00:00
|
|
|
'wakeonlan',
|
|
|
|
'vultr'
|
2017-05-07 05:37:31 +00:00
|
|
|
)
|
|
|
|
|
2016-07-02 18:22:51 +00:00
|
|
|
IGNORE_PACKAGES = (
|
|
|
|
'homeassistant.components.recorder.models',
|
|
|
|
)
|
2015-11-17 08:28:22 +00:00
|
|
|
|
2017-01-21 23:31:10 +00:00
|
|
|
IGNORE_PIN = ('colorlog>2.1,<3', 'keyring>=9.3,<10.0', 'urllib3')
|
|
|
|
|
2017-03-12 19:08:49 +00:00
|
|
|
IGNORE_REQ = (
|
|
|
|
'colorama<=1', # Windows only requirement in check_config
|
|
|
|
)
|
|
|
|
|
2017-01-22 16:34:00 +00:00
|
|
|
URL_PIN = ('https://home-assistant.io/developers/code_review_platform/'
|
|
|
|
'#1-requirements')
|
|
|
|
|
2015-11-17 08:18:42 +00:00
|
|
|
|
2017-03-22 15:50:54 +00:00
|
|
|
CONSTRAINT_PATH = os.path.join(os.path.dirname(__file__),
|
|
|
|
'../homeassistant/package_constraints.txt')
|
2017-08-05 06:06:10 +00:00
|
|
|
CONSTRAINT_BASE = """
|
|
|
|
# Breaks Python 3.6 and is not needed for our supported Pythons
|
|
|
|
enum34==1000000000.0.0
|
|
|
|
"""
|
2017-03-22 15:50:54 +00:00
|
|
|
|
|
|
|
|
2015-11-17 08:18:42 +00:00
|
|
|
def explore_module(package, explore_children):
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Explore the modules."""
|
2015-11-17 08:18:42 +00:00
|
|
|
module = importlib.import_module(package)
|
|
|
|
|
|
|
|
found = []
|
|
|
|
|
|
|
|
if not hasattr(module, '__path__'):
|
|
|
|
return found
|
|
|
|
|
2016-08-23 04:42:05 +00:00
|
|
|
for _, name, _ in pkgutil.iter_modules(module.__path__, package + '.'):
|
2015-11-17 08:18:42 +00:00
|
|
|
found.append(name)
|
|
|
|
|
|
|
|
if explore_children:
|
|
|
|
found.extend(explore_module(name, False))
|
|
|
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
|
|
def core_requirements():
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Gather core requirements out of setup.py."""
|
2015-11-17 08:18:42 +00:00
|
|
|
with open('setup.py') as inp:
|
|
|
|
reqs_raw = re.search(
|
|
|
|
r'REQUIRES = \[(.*?)\]', inp.read(), re.S).group(1)
|
|
|
|
return re.findall(r"'(.*?)'", reqs_raw)
|
|
|
|
|
|
|
|
|
2015-11-17 08:28:22 +00:00
|
|
|
def comment_requirement(req):
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Some requirements don't install on all systems."""
|
2015-11-17 08:28:22 +00:00
|
|
|
return any(ign in req for ign in COMMENT_REQUIREMENTS)
|
|
|
|
|
|
|
|
|
2015-11-25 22:31:04 +00:00
|
|
|
def gather_modules():
|
2017-05-07 05:37:31 +00:00
|
|
|
"""Collect the information."""
|
2016-02-01 07:52:42 +00:00
|
|
|
reqs = {}
|
2015-11-17 08:18:42 +00:00
|
|
|
|
|
|
|
errors = []
|
2015-11-25 22:31:04 +00:00
|
|
|
|
2016-08-23 04:42:05 +00:00
|
|
|
for package in sorted(explore_module('homeassistant.components', True) +
|
|
|
|
explore_module('homeassistant.scripts', True)):
|
2015-11-17 08:18:42 +00:00
|
|
|
try:
|
|
|
|
module = importlib.import_module(package)
|
|
|
|
except ImportError:
|
2016-07-02 18:22:51 +00:00
|
|
|
if package not in IGNORE_PACKAGES:
|
|
|
|
errors.append(package)
|
2015-11-17 08:18:42 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
if not getattr(module, 'REQUIREMENTS', None):
|
|
|
|
continue
|
|
|
|
|
|
|
|
for req in module.REQUIREMENTS:
|
2017-03-12 19:08:49 +00:00
|
|
|
if req in IGNORE_REQ:
|
|
|
|
continue
|
2017-01-21 23:31:10 +00:00
|
|
|
if req.partition('==')[1] == '' and req not in IGNORE_PIN:
|
2017-01-22 16:34:00 +00:00
|
|
|
errors.append(
|
|
|
|
"{}[Please pin requirement {}, see {}]".format(
|
|
|
|
package, req, URL_PIN))
|
2015-11-17 08:18:42 +00:00
|
|
|
reqs.setdefault(req, []).append(package)
|
|
|
|
|
2016-02-01 07:52:42 +00:00
|
|
|
for key in reqs:
|
|
|
|
reqs[key] = sorted(reqs[key],
|
|
|
|
key=lambda name: (len(name.split('.')), name))
|
|
|
|
|
2015-11-17 08:18:42 +00:00
|
|
|
if errors:
|
2015-12-18 07:51:34 +00:00
|
|
|
print("******* ERROR")
|
|
|
|
print("Errors while importing: ", ', '.join(errors))
|
|
|
|
print("Make sure you import 3rd party libraries inside methods.")
|
2015-11-29 21:55:46 +00:00
|
|
|
return None
|
2015-11-17 08:18:42 +00:00
|
|
|
|
2017-05-07 05:37:31 +00:00
|
|
|
return reqs
|
|
|
|
|
|
|
|
|
|
|
|
def generate_requirements_list(reqs):
|
|
|
|
"""Generate a pip file based on requirements."""
|
|
|
|
output = []
|
2016-02-01 07:52:42 +00:00
|
|
|
for pkg, requirements in sorted(reqs.items(), key=lambda item: item[0]):
|
2015-11-17 08:18:42 +00:00
|
|
|
for req in sorted(requirements,
|
|
|
|
key=lambda name: (len(name.split('.')), name)):
|
2015-11-25 22:31:04 +00:00
|
|
|
output.append('\n# {}'.format(req))
|
2015-11-17 08:28:22 +00:00
|
|
|
|
|
|
|
if comment_requirement(pkg):
|
2015-11-25 22:31:04 +00:00
|
|
|
output.append('\n# {}\n'.format(pkg))
|
2015-11-17 08:28:22 +00:00
|
|
|
else:
|
2015-11-25 22:31:04 +00:00
|
|
|
output.append('\n{}\n'.format(pkg))
|
2017-05-07 05:37:31 +00:00
|
|
|
return ''.join(output)
|
|
|
|
|
|
|
|
|
|
|
|
def requirements_all_output(reqs):
|
|
|
|
"""Generate output for requirements_all."""
|
|
|
|
output = []
|
|
|
|
output.append('# Home Assistant core')
|
|
|
|
output.append('\n')
|
|
|
|
output.append('\n'.join(core_requirements()))
|
|
|
|
output.append('\n')
|
|
|
|
output.append(generate_requirements_list(reqs))
|
|
|
|
|
|
|
|
return ''.join(output)
|
|
|
|
|
|
|
|
|
|
|
|
def requirements_test_output(reqs):
|
|
|
|
"""Generate output for test_requirements."""
|
|
|
|
output = []
|
|
|
|
output.append('# Home Assistant test')
|
|
|
|
output.append('\n')
|
2017-10-09 03:49:51 +00:00
|
|
|
with open('requirements_test.txt') as test_file:
|
|
|
|
output.append(test_file.read())
|
2017-05-07 05:37:31 +00:00
|
|
|
output.append('\n')
|
|
|
|
filtered = {key: value for key, value in reqs.items()
|
2017-10-09 03:49:51 +00:00
|
|
|
if any(
|
|
|
|
re.search(r'(^|#){}($|[=><])'.format(ign),
|
|
|
|
key) is not None for ign in TEST_REQUIREMENTS)}
|
2017-05-07 05:37:31 +00:00
|
|
|
output.append(generate_requirements_list(filtered))
|
2015-11-25 22:31:04 +00:00
|
|
|
|
|
|
|
return ''.join(output)
|
|
|
|
|
|
|
|
|
2017-03-22 15:50:54 +00:00
|
|
|
def gather_constraints():
|
|
|
|
"""Construct output for constraint file."""
|
|
|
|
return '\n'.join(core_requirements() + [''])
|
|
|
|
|
|
|
|
|
|
|
|
def write_requirements_file(data):
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Write the modules to the requirements_all.txt."""
|
2017-03-12 19:08:49 +00:00
|
|
|
with open('requirements_all.txt', 'w+', newline="\n") as req_file:
|
2015-11-25 22:31:04 +00:00
|
|
|
req_file.write(data)
|
|
|
|
|
|
|
|
|
2017-05-07 05:37:31 +00:00
|
|
|
def write_test_requirements_file(data):
|
|
|
|
"""Write the modules to the requirements_all.txt."""
|
|
|
|
with open('requirements_test_all.txt', 'w+', newline="\n") as req_file:
|
|
|
|
req_file.write(data)
|
|
|
|
|
|
|
|
|
2017-03-22 15:50:54 +00:00
|
|
|
def write_constraints_file(data):
|
|
|
|
"""Write constraints to a file."""
|
|
|
|
with open(CONSTRAINT_PATH, 'w+', newline="\n") as req_file:
|
2017-08-05 06:06:10 +00:00
|
|
|
req_file.write(data + CONSTRAINT_BASE)
|
2017-03-22 15:50:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
def validate_requirements_file(data):
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Validate if requirements_all.txt is up to date."""
|
2015-12-18 07:51:34 +00:00
|
|
|
with open('requirements_all.txt', 'r') as req_file:
|
2017-08-05 06:06:10 +00:00
|
|
|
return data == req_file.read()
|
2015-12-18 07:51:34 +00:00
|
|
|
|
|
|
|
|
2017-05-07 05:37:31 +00:00
|
|
|
def validate_requirements_test_file(data):
|
|
|
|
"""Validate if requirements_all.txt is up to date."""
|
|
|
|
with open('requirements_test_all.txt', 'r') as req_file:
|
2017-08-05 06:06:10 +00:00
|
|
|
return data == req_file.read()
|
2017-05-07 05:37:31 +00:00
|
|
|
|
|
|
|
|
2017-03-22 15:50:54 +00:00
|
|
|
def validate_constraints_file(data):
|
|
|
|
"""Validate if constraints is up to date."""
|
|
|
|
with open(CONSTRAINT_PATH, 'r') as req_file:
|
2017-08-05 06:06:10 +00:00
|
|
|
return data + CONSTRAINT_BASE == req_file.read()
|
2017-03-22 15:50:54 +00:00
|
|
|
|
|
|
|
|
2015-11-25 22:31:04 +00:00
|
|
|
def main():
|
2016-03-09 10:15:04 +00:00
|
|
|
"""Main section of the script."""
|
2015-11-25 22:31:04 +00:00
|
|
|
if not os.path.isfile('requirements_all.txt'):
|
|
|
|
print('Run this from HA root dir')
|
|
|
|
return
|
2015-11-29 21:55:46 +00:00
|
|
|
|
2015-11-25 22:31:04 +00:00
|
|
|
data = gather_modules()
|
|
|
|
|
2015-11-29 21:55:46 +00:00
|
|
|
if data is None:
|
2015-12-18 07:51:34 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2017-03-22 15:50:54 +00:00
|
|
|
constraints = gather_constraints()
|
2015-11-29 21:55:46 +00:00
|
|
|
|
2017-05-07 05:37:31 +00:00
|
|
|
reqs_file = requirements_all_output(data)
|
|
|
|
reqs_test_file = requirements_test_output(data)
|
|
|
|
|
2017-03-22 15:50:54 +00:00
|
|
|
if sys.argv[-1] == 'validate':
|
2017-05-07 05:37:31 +00:00
|
|
|
errors = []
|
|
|
|
if not validate_requirements_file(reqs_file):
|
|
|
|
errors.append("requirements_all.txt is not up to date")
|
|
|
|
|
|
|
|
if not validate_requirements_test_file(reqs_test_file):
|
|
|
|
errors.append("requirements_test_all.txt is not up to date")
|
2017-03-22 15:50:54 +00:00
|
|
|
|
|
|
|
if not validate_constraints_file(constraints):
|
2017-05-07 05:37:31 +00:00
|
|
|
errors.append(
|
|
|
|
"home-assistant/package_constraints.txt is not up to date")
|
|
|
|
|
|
|
|
if errors:
|
2017-03-22 15:50:54 +00:00
|
|
|
print("******* ERROR")
|
2017-05-07 05:37:31 +00:00
|
|
|
print('\n'.join(errors))
|
2017-03-22 15:50:54 +00:00
|
|
|
print("Please run script/gen_requirements_all.py")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
2017-05-07 05:37:31 +00:00
|
|
|
write_requirements_file(reqs_file)
|
|
|
|
write_test_requirements_file(reqs_test_file)
|
2017-03-22 15:50:54 +00:00
|
|
|
write_constraints_file(constraints)
|
2015-11-17 08:18:42 +00:00
|
|
|
|
2016-11-19 05:47:59 +00:00
|
|
|
|
2015-11-17 08:18:42 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|