| #!/usr/bin/python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| 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") |
| |
| |
| def get_tool_requirements_path(): |
| '''Return the path to tool_requirements.py, at the top of the 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: |
| # Note: "verilator" needs to be called through a shell and with all |
| # arguments in a string, as it doesn't have a shebang, but instead |
| # relies on perl magic to parse command line arguments. |
| version_str = subprocess.run('verilator --version', shell=True, |
| check=True, stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| universal_newlines=True) |
| return version_str.stdout.split(' ')[1].strip() |
| |
| except subprocess.CalledProcessError as e: |
| log.error("Unable to call Verilator to check version: " + str(e)) |
| log.error(e.stdout) |
| return 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): |
| log.error("%s is too old: found version %s, need at least %s", |
| tool_name, actual_version, required_version) |
| return False |
| else: |
| log.info("Found sufficiently recent version of %s (found %s, need %s)", |
| tool_name, actual_version, required_version) |
| return True |
| |
| |
| def main(): |
| # Get tool requirements |
| tool_requirements = read_tool_requirements() |
| if tool_requirements is None: |
| return 1 |
| |
| 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 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()) |