diff --git a/tool_requirements.py b/tool_requirements.py index 17a2bb09..a818999f 100644 --- a/tool_requirements.py +++ b/tool_requirements.py @@ -6,4 +6,5 @@ # and inserted into the Sphinx-generated documentation. __TOOL_REQUIREMENTS__ = { 'verilator': '4.028', + 'edalize': '0.2.0' } diff --git a/util/check_tool_requirements.py b/util/check_tool_requirements.py index 5a59d4cd..935ac9d7 100755 --- a/util/check_tool_requirements.py +++ b/util/check_tool_requirements.py @@ -6,15 +6,50 @@ from distutils.version import StrictVersion import logging as log import os +import re import subprocess import sys # Display INFO log messages and up. log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") -# Populate __TOOL_REQUIREMENTS__ -topsrcdir = os.path.join(os.path.dirname(__file__), '..') -exec(open(os.path.join(topsrcdir, 'tool_requirements.py')).read()) + +def get_tool_requirements_path(): + '''Return the path to tool_requirements.py, at the top of the Ibex repo''' + # top_src_dir is the top of the repository + top_src_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), + '..')) + + return os.path.join(top_src_dir, 'tool_requirements.py') + + +def read_tool_requirements(path=None): + '''Read tool requirements from a Python file''' + if path is None: + path = get_tool_requirements_path() + + with open(path, 'r') as pyfile: + globs = {} + exec(pyfile.read(), globs) + + # We expect the exec call to have populated globs with a + # __TOOL_REQUIREMENTS__ dictionary. + reqs = globs.get('__TOOL_REQUIREMENTS__') + if reqs is None: + log.error('The Python file at {} did not define ' + '__TOOL_REQUIREMENTS__.' + .format(path)) + return None + + # reqs should be a dictionary (mapping tool name to minimum version) + if not isinstance(reqs, dict): + log.error('The Python file at {} defined ' + '__TOOL_REQUIREMENTS__, but it is not a dict.' + .format(path)) + return None + + return reqs + def get_verilator_version(): try: @@ -32,8 +67,44 @@ def get_verilator_version(): log.error(e.stdout) return None -def check_version(tool_name, required_version, actual_version): - if required_version is None or actual_version is None: + +def pip3_get_version(tool): + '''Run pip3 to find the version of an installed module''' + cmd = ['pip3', 'show', tool] + try: + proc = subprocess.run(cmd, + check=True, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + universal_newlines=True) + except subprocess.CalledProcessError as err: + log.error('pip3 command failed: {}'.format(err)) + log.error("Failed to get version of {} with pip3: is it installed?" + .format(tool)) + log.error(err.stdout) + return None + + version_re = 'Version: (.*)' + for line in proc.stdout.splitlines(): + match = re.match(version_re, line) + if match: + return match.group(1) + + # If we get here, we never saw a version line. + log.error('No output line from running {} started with "Version: ".' + .format(cmd)) + return None + + +def check_version(requirements, tool_name, getter): + required_version = requirements.get(tool_name) + if required_version is None: + log.error('Requirements file does not specify version for {}.' + .format(tool_name)) + return False + + actual_version = getter() + if actual_version is None: return False if StrictVersion(actual_version) < StrictVersion(required_version): @@ -47,17 +118,26 @@ def check_version(tool_name, required_version, actual_version): def main(): - any_failed = False + # Get tool requirements + tool_requirements = read_tool_requirements() + if tool_requirements is None: + return 1 - if not check_version('verilator', __TOOL_REQUIREMENTS__['verilator'], - get_verilator_version()): - any_failed = True + all_good = True + all_good &= check_version(tool_requirements, + 'verilator', + get_verilator_version) + all_good &= check_version(tool_requirements, + 'edalize', + lambda: pip3_get_version('edalize')) - if any_failed: + if not all_good: log.error("Tool requirements not fulfilled. " "Please update the tools and retry.") return 1 + return 0 + if __name__ == "__main__": sys.exit(main())