blob: 2e46ed1a8e5e3411c8db1ce2f7a13d5a2654d9b1 [file] [log] [blame]
#!/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 convert_verible_version(version_string):
'''Convert Verible version string to semantic versioning format.'''
# Drop the hash suffix and convert into version string that
# is compatible with StrictVersion in check_version below.
# Example: v0.0-808-g1e17daa -> 0.0.808
m = re.fullmatch(r'v([0-9]+)\.([0-9]+)-([0-9]+)-g[0-9a-f]+$', version_string)
if m is None:
log.error("{} has invalid version string format.".format(version_string))
return None
return '.'.join(m.group(1, 2, 3))
def get_verible_version():
'''Run Verible to check its version'''
try:
version_str = subprocess.run('verible-verilog-lint --version', shell=True,
check=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)
return version_str.stdout.split('\n')[0].strip()
except subprocess.CalledProcessError as e:
log.error("Unable to call Verible 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, version_converter=None):
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 a version string converter is defined, call it. This is required
# for some version strings that are not compatible with StrictVersion.
if version_converter is not None:
required_version = version_converter(required_version)
actual_version = version_converter(actual_version)
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,
'verible',
get_verible_version,
convert_verible_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())