Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | # Copyright lowRISC contributors. |
| 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 4 | # SPDX-License-Identifier: Apache-2.0 |
| 5 | |
| 6 | from distutils.version import StrictVersion |
| 7 | import logging as log |
| 8 | import os |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 9 | import re |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 10 | import subprocess |
| 11 | import sys |
| 12 | |
| 13 | # Display INFO log messages and up. |
| 14 | log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") |
| 15 | |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 16 | |
| 17 | def get_tool_requirements_path(): |
| 18 | '''Return the path to tool_requirements.py, at the top of the repo''' |
| 19 | # top_src_dir is the top of the repository |
| 20 | top_src_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), |
| 21 | '..')) |
| 22 | |
| 23 | return os.path.join(top_src_dir, 'tool_requirements.py') |
| 24 | |
| 25 | |
| 26 | def read_tool_requirements(path=None): |
| 27 | '''Read tool requirements from a Python file''' |
| 28 | if path is None: |
| 29 | path = get_tool_requirements_path() |
| 30 | |
| 31 | with open(path, 'r') as pyfile: |
| 32 | globs = {} |
| 33 | exec(pyfile.read(), globs) |
| 34 | |
| 35 | # We expect the exec call to have populated globs with a |
| 36 | # __TOOL_REQUIREMENTS__ dictionary. |
| 37 | reqs = globs.get('__TOOL_REQUIREMENTS__') |
| 38 | if reqs is None: |
| 39 | log.error('The Python file at {} did not define ' |
| 40 | '__TOOL_REQUIREMENTS__.' |
| 41 | .format(path)) |
| 42 | return None |
| 43 | |
| 44 | # reqs should be a dictionary (mapping tool name to minimum version) |
| 45 | if not isinstance(reqs, dict): |
| 46 | log.error('The Python file at {} defined ' |
| 47 | '__TOOL_REQUIREMENTS__, but it is not a dict.' |
| 48 | .format(path)) |
| 49 | return None |
| 50 | |
| 51 | return reqs |
| 52 | |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 53 | |
| 54 | def get_verilator_version(): |
| 55 | try: |
| 56 | # Note: "verilator" needs to be called through a shell and with all |
| 57 | # arguments in a string, as it doesn't have a shebang, but instead |
| 58 | # relies on perl magic to parse command line arguments. |
| 59 | version_str = subprocess.run('verilator --version', shell=True, |
| 60 | check=True, stdout=subprocess.PIPE, |
| 61 | stderr=subprocess.STDOUT, |
| 62 | universal_newlines=True) |
| 63 | return version_str.stdout.split(' ')[1].strip() |
| 64 | |
| 65 | except subprocess.CalledProcessError as e: |
| 66 | log.error("Unable to call Verilator to check version: " + str(e)) |
| 67 | log.error(e.stdout) |
| 68 | return None |
| 69 | |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 70 | |
| 71 | def pip3_get_version(tool): |
| 72 | '''Run pip3 to find the version of an installed module''' |
| 73 | cmd = ['pip3', 'show', tool] |
| 74 | try: |
| 75 | proc = subprocess.run(cmd, |
| 76 | check=True, |
| 77 | stderr=subprocess.STDOUT, |
| 78 | stdout=subprocess.PIPE, |
| 79 | universal_newlines=True) |
| 80 | except subprocess.CalledProcessError as err: |
| 81 | log.error('pip3 command failed: {}'.format(err)) |
| 82 | log.error("Failed to get version of {} with pip3: is it installed?" |
| 83 | .format(tool)) |
| 84 | log.error(err.stdout) |
| 85 | return None |
| 86 | |
| 87 | version_re = 'Version: (.*)' |
| 88 | for line in proc.stdout.splitlines(): |
| 89 | match = re.match(version_re, line) |
| 90 | if match: |
| 91 | return match.group(1) |
| 92 | |
| 93 | # If we get here, we never saw a version line. |
| 94 | log.error('No output line from running {} started with "Version: ".' |
| 95 | .format(cmd)) |
| 96 | return None |
| 97 | |
| 98 | |
| 99 | def check_version(requirements, tool_name, getter): |
| 100 | required_version = requirements.get(tool_name) |
| 101 | if required_version is None: |
| 102 | log.error('Requirements file does not specify version for {}.' |
| 103 | .format(tool_name)) |
| 104 | return False |
| 105 | |
| 106 | actual_version = getter() |
| 107 | if actual_version is None: |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 108 | return False |
| 109 | |
| 110 | if StrictVersion(actual_version) < StrictVersion(required_version): |
| 111 | log.error("%s is too old: found version %s, need at least %s", |
| 112 | tool_name, actual_version, required_version) |
| 113 | return False |
| 114 | else: |
| 115 | log.info("Found sufficiently recent version of %s (found %s, need %s)", |
| 116 | tool_name, actual_version, required_version) |
| 117 | return True |
| 118 | |
| 119 | |
| 120 | def main(): |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 121 | # Get tool requirements |
| 122 | tool_requirements = read_tool_requirements() |
| 123 | if tool_requirements is None: |
| 124 | return 1 |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 125 | |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 126 | all_good = True |
| 127 | all_good &= check_version(tool_requirements, |
| 128 | 'verilator', |
| 129 | get_verilator_version) |
| 130 | all_good &= check_version(tool_requirements, |
| 131 | 'edalize', |
| 132 | lambda: pip3_get_version('edalize')) |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 133 | |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 134 | if not all_good: |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 135 | log.error("Tool requirements not fulfilled. " |
| 136 | "Please update the tools and retry.") |
| 137 | return 1 |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 138 | |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 139 | return 0 |
| 140 | |
Philipp Wagner | 82ed76e | 2020-05-26 11:07:48 +0100 | [diff] [blame] | 141 | |
Pirmin Vogel | ed097cc | 2020-03-09 11:35:21 +0100 | [diff] [blame] | 142 | if __name__ == "__main__": |
| 143 | sys.exit(main()) |