1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# Copyright 2017, Data61 5# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 6# ABN 41 687 119 230. 7# 8# This software may be distributed and modified according to the terms of 9# the BSD 2-Clause license. Note that NO WARRANTY is provided. 10# See "LICENSE_BSD2.txt" for details. 11# 12# @TAG(DATA61_BSD) 13# 14 15''' 16Dependency checker for CAmkES. 17''' 18 19from __future__ import absolute_import, division, print_function, \ 20 unicode_literals 21 22# This script can only import parts of the Python standard library, or it 23# becomes useless as a dependency checker. 24import abc, argparse, importlib, os, re, shutil, subprocess, sys, tempfile 25 26class CheckDepException(Exception): 27 pass 28 29class Package(object): 30 31 # Note that the following line has no effect in Python 3, but we just live 32 # with this rather than using the `six` wrapper as that would introduce a 33 # dependency on `six` that the user may not have installed. 34 __metaclass__ = abc.ABCMeta 35 36 def __init__(self, name, description): 37 self.name = name 38 self.description = description 39 40 @abc.abstractmethod 41 def exists(self): 42 raise NotImplementedError 43 44class Binary(Package): 45 def exists(self): 46 with open(os.devnull, 'wt') as f: 47 return subprocess.call(['which', self.name], stdout=f, stderr=f) \ 48 == 0 49 50class Pylint(Binary): 51 def __init__(self, name, description, min_version): 52 super(Pylint, self).__init__(name, description) 53 self.min_version = min_version 54 55 def exists(self): 56 if not super(Pylint, self).exists(): 57 return False 58 with open(os.devnull, 'wt') as f: 59 output = subprocess.check_output(['pylint', '--version'], stderr=f) 60 m = re.search(r'^pylint\s+(\d+\.\d+)', output, flags=re.MULTILINE) 61 if m is None: 62 raise CheckDepException('cannot determine version') 63 version = float(m.group(1)) 64 if version < self.min_version: 65 raise CheckDepException('found version %0.1f but need at least ' 66 'version %0.1f' % (version, self.min_version)) 67 return True 68 69class PythonModule(Package): 70 def exists(self): 71 try: 72 importlib.import_module(self.name) 73 return True 74 except ImportError: 75 return False 76 77class PythonModuleWith(PythonModule): 78 def __init__(self, name, description, attr): 79 super(PythonModuleWith, self).__init__(name, description) 80 self.attr = attr 81 82 def exists(self): 83 if not super(PythonModuleWith, self).exists(): 84 return False 85 mod = importlib.import_module(self.name) 86 if not hasattr(mod, self.attr): 87 raise CheckDepException('module exists, but %s.%s not found ' 88 '(upgrade required?)' % (self.name, self.attr)) 89 return True 90 91class CLibrary(Package): 92 def exists(self): 93 with open(os.devnull, 'wt') as f: 94 return subprocess.call(['pkg-config', '--cflags', self.name], 95 stdout=f, stderr=f) == 0 96 97class Or(Package): 98 def __init__(self, *packages): 99 self.name = ' or '.join(p.name for p in packages) 100 self.description = '...' 101 self.packages = packages 102 103 def exists(self): 104 return any(p.exists() for p in self.packages) 105 106class CUnit(Package): 107 def exists(self): 108 # CUnit is misconfigured for use with `pkg-config`, so test for its 109 # existence manually. 110 tmp = tempfile.mkdtemp() 111 try: 112 source = os.path.join(tmp, 'main.c') 113 with open(source, 'wt') as f: 114 f.write(''' 115 #include <CUnit/Basic.h> 116 117 int main(void) { 118 CU_initialize_registry(); 119 CU_cleanup_registry(); 120 return 0; 121 }''') 122 with open(os.devnull, 'wt') as f: 123 subprocess.check_call(['gcc', 'main.c', '-lcunit'], cwd=tmp, 124 stdout=f, stderr=f) 125 return True 126 except subprocess.CalledProcessError: 127 return False 128 finally: 129 shutil.rmtree(tmp) 130 131def green(string): 132 return '\033[32;1m%s\033[0m' % string 133 134def red(string): 135 return '\033[31;1m%s\033[0m' % string 136 137def yellow(string): 138 return '\033[33m%s\033[0m' % string 139 140DEPENDENCIES = { 141 'CAmkES runner':(PythonModule('jinja2', 'Python templating module'), 142 PythonModule('plyplus', 'Python parsing module'), 143 PythonModule('ply', 'Python parsing module'), 144 PythonModule('elftools', 'Python ELF parsing module'), 145 PythonModule('orderedset', 'Python OrderedSet module (orderedset)'), 146 PythonModuleWith('six', 'Python 2/3 compatibility layer', 'assertCountEqual'), 147 PythonModule('sqlite3', 'Python SQLite module'), 148 PythonModule('pyfdt', 'Python flattened device tree parser')), 149 'seL4':(Binary('gcc', 'C compiler'), 150 PythonModule('tempita', 'Python templating module'), 151 Binary('xmllint', 'XML validator'), 152 Binary('bash', 'shell'), 153 Binary('make', 'GNU Make build tool'), 154 Binary('cpio', 'CPIO file system tool')), 155 'CapDL translator':(Binary('stack', 'Haskell version manager'),), 156 'CAmkES test suite':(Binary('expect', 'automation utility'), 157 Pylint('pylint', 'Python linter', 1.4), 158 Binary('qemu-system-arm', 'ARM emulator'), 159 Binary('qemu-system-i386', 'IA32 emulator'), 160 PythonModule('pycparser', 'Python C parsing module'), 161 Binary('gcc', 'C compiler'), 162 Binary('spin', 'model checker'), 163 Binary('sha256sum', 'file hashing utility')), 164} 165 166EXTRAS = frozenset(( 167 (Binary('sponge', 'input coalescer from moreutils'), 168 'installing this will give a marginal improvement in compilation times'), 169 (Binary('qemu-system-arm', 'ARM emulator'), 170 'this is required to simulate ARM systems'), 171 (Binary('qemu-system-i386', 'IA32 emulator'), 172 'this is required to simulate IA32 systems'), 173 (Binary('ccache', 'C compiler accelerator'), 174 'installing this will speed up your C compilation times'), 175 (Binary('clang-format', 'Clang code reformatter'), 176 'installing this will reflow generated C code to make it more readable'), 177 (CLibrary('ncurses', 'terminal menus library'), 178 'you will need to install this if you want to run menuconfig'), 179 (Or(Binary('arm-none-eabi-gcc', 'ARM C compiler'), 180 Binary('arm-linux-gnueabi-gcc', 'ARM C compiler'), 181 Binary('arm-linux-gnu-gcc', 'ARM C compiler')), 182 'you will need one of these if you want to target ARM systems'), 183 (Binary('pandoc', 'document format translator'), 184 'you will need this if you want to build the CAmkES documentation'), 185 (Binary('astyle', 'code reformater'), 186 'installing this will allow you to use the "style" Makefile targets to reformat C code'), 187 (Binary('c-parser', 'NICTA C-to-Simpl parser'), 188 'you will need this installed if you want to validate code for verification'), 189 (Or(Binary('arm-none-eabi-objdump', 'ARM disassembler'), 190 Binary('arm-linux-gnueabi-objdump', 'ARM disassembler'), 191 Binary('arm-linux-gnu-objdump', 'ARM disassembler')), 192 'installing one of these will speed up CapDL generation times for ARM builds'), 193 (Binary('objdump', 'disassembler'), 194 'installing this will speed up CapDL generation times for IA32 builds'), 195 (Binary('VBoxManage', 'VirtualBox administration tool'), 196 'you will need this installed if you want to build VMWare images'), 197 (Binary('syslinux', 'Linux bootloader tool'), 198 'you will need this installed if you want to build QEMU images for IA32'), 199 (Binary('mpartition', 'partitioning tool for MSDOS disks'), 200 'you will need this installed if you want to build QEMU images for IA32'), 201 (Binary('mformat', 'formatting tool for MSDOS disks'), 202 'you will need this installed if you want to build QEMU images for IA32'), 203 (Binary('mcopy', 'copying tool for MSDOS disks'), 204 'you will need this installed if you want to build QEMU images for IA32'), 205 (Binary('figleaf', 'code coverage tool for Python'), 206 'you will need this installed if you want to measure code coverage within CAmkES'), 207 (Binary('python-coverage', 'code coverage tool for Python'), 208 'you will need this installed if you want to measure code coverage within CAmkES'), 209 (Binary('clang', 'C compiler'), 210 'you will need this installed to efficiently use large DMA pools on ARM'), 211 212)) 213 214def main(argv): 215 parser = argparse.ArgumentParser(description='CAmkES dependency checker') 216 parser.add_argument('--component', '-c', action='append', 217 choices=list(DEPENDENCIES.keys()), help='component whose dependecies ' 218 'should be checked (default: all)') 219 options = parser.parse_args(argv[1:]) 220 221 ret = 0 222 223 for k, v in sorted(DEPENDENCIES.items()): 224 if options.component is not None and k not in options.component: 225 continue 226 ok = True 227 sys.stdout.write('Dependencies of %s\n' % k) 228 for p in v: 229 sys.stdout.write(' %s (%s)... ' % (p.name, p.description)) 230 try: 231 if p.exists(): 232 sys.stdout.write(green('Found\n')) 233 else: 234 raise CheckDepException('Not found') 235 except CheckDepException as e: 236 ok = False 237 ret = -1 238 sys.stdout.write(red('%s\n' % e)) 239 if not ok: 240 sys.stdout.write(red('You will not be able to build/run this component\n')) 241 sys.stdout.write('\n') 242 243 printed_header = False 244 for p, note in EXTRAS: 245 if not p.exists(): 246 if not printed_header: 247 sys.stdout.write('Suggestions:\n') 248 printed_header = True 249 sys.stdout.write(yellow(' %s (%s): %s\n' % (p.name, p.description, note))) 250 251 return ret 252 253if __name__ == '__main__': 254 sys.exit(main(sys.argv)) 255