|  | #!/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()) |