# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import sh import subprocess import sys from pkg_resources import parse_version from waflib import Logs REQUIREMENTS_OSX = 'requirements-osx.txt' REQUIREMENTS_BREW = 'requirements-brew.txt' VERSION_REGEX = r"^(?P.*)(?P==|<=|>=|<|>)(?P.*)" VERSION_PATTERN = re.compile(VERSION_REGEX) def tool_check(): if sys.platform.startswith('linux'): Logs.pprint('RED', 'Linux requirements-linux.txt checking not implemented. Sorry.') return Logs.pprint('CYAN', 'Checking %s' % REQUIREMENTS_OSX) with open(REQUIREMENTS_OSX) as file: osx_req_list = text_to_req_list(file.read()) pip_installed_text = sh.pip('freeze') pip_installed_dict = installed_list_to_dict(text_to_req_list(pip_installed_text)) for req in osx_req_list: check_requirement(req, pip_installed_dict) Logs.pprint('CYAN', 'Checking %s' % REQUIREMENTS_BREW) with open(REQUIREMENTS_BREW) as file: brew_req_text = file.read() brew_req_list = text_to_req_list(brew_req_text) brew_installed_text = subprocess.check_output(['brew', 'list']) brew_installed_dict = installed_list_to_dict(text_to_req_list(brew_installed_text)) for req in brew_req_list: check_requirement(req, brew_installed_dict) def installed_list_to_dict(list): d = {} for l in list: d[l[0]] = l[2] return d def text_to_req_list(req_list_text): req_list = [] for raw_line in req_list_text.splitlines(): # Recursively handle -r includes if raw_line.startswith('-r '): with open(raw_line[3:]) as file: req_list.extend(text_to_req_list(file.read())) continue # Remove whitespace, comments, & --extra-index-url line = raw_line.replace(" ", "") if len(line) < 2 or line.startswith('#') or line.startswith('--'): continue match = VERSION_PATTERN.match(line) if not match: # Assume no version info req_list.append((line, None, None)) continue if match.group('package').endswith(','): # Muliple requirements match2 = VERSION_PATTERN.match(match.group('package').strip(',')) if not match2: Logs.pprint('RED', 'Don\'t understand line \'%s\'' % raw_line) continue req_list.append((match2.group('package'), match2.group('comparator'), match2.group('version'))) req_list.append((match2.group('package'), match.group('comparator'), match.group('version'))) else: req_list.append((match.group('package'), match.group('comparator'), match.group('version'))) return req_list def check_requirement(req, installed): if req[0] not in installed: Logs.pprint('RED', 'Package \'%s\' not installed' % req[0]) return if not req[1]: # No version/comparison return version = parse_version(installed[req[0]]) success = True if req[1] == '==': success = version == parse_version(req[2]) elif req[1] == '<=': success = version <= parse_version(req[2]) elif req[1] == '>=': success = version >= parse_version(req[2]) elif req[1] == '<': success = version < parse_version(req[2]) elif req[1] == '>': success = version > parse_version(req[2]) else: Logs.pprint('RED', 'Don\'t understand comparison \'%s\'' % req[1]) if not success: Logs.pprint('RED', 'Package \'%s\' installed = %s, needed %s %s ' % (req[0], version, req[1], req[2])) # vim:filetype=python