1# Copyright 2015, VIXL authors 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are met: 6# 7# * Redistributions of source code must retain the above copyright notice, 8# this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above copyright notice, 10# this list of conditions and the following disclaimer in the documentation 11# and/or other materials provided with the distribution. 12# * Neither the name of ARM Limited nor the names of its contributors may be 13# used to endorse or promote products derived from this software without 14# specific prior written permission. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND 17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 27from distutils.version import LooseVersion 28import config 29import fnmatch 30import glob 31import operator 32import os 33import re 34import shlex 35import subprocess 36import sys 37 38 39def ListCCFilesWithoutExt(path): 40 return map(lambda x : os.path.splitext(os.path.basename(x))[0], 41 glob.glob(os.path.join(path, '*.cc'))) 42 43 44def abort(message): 45 print('ABORTING: ' + message) 46 sys.exit(1) 47 48 49# Emulate python3 subprocess.getstatusoutput. 50def getstatusoutput(command): 51 try: 52 args = shlex.split(command) 53 output = subprocess.check_output(args, stderr=subprocess.STDOUT) 54 return 0, output.rstrip('\n') 55 except subprocess.CalledProcessError as e: 56 return e.returncode, e.output.rstrip('\n') 57 58 59def IsCommandAvailable(command): 60 retcode, unused_output = getstatusoutput('which %s' % command) 61 return retcode == 0 62 63 64def ensure_dir(path_name): 65 if not os.path.exists(path_name): 66 os.makedirs(path_name) 67 68 69# Check that the specified program is available. 70def require_program(program_name): 71 rc, out = getstatusoutput('which %s' % program_name) 72 if rc != 0: 73 print('ERROR: The required program %s was not found.' % program_name) 74 sys.exit(rc) 75 76def relrealpath(path, start=os.getcwd()): 77 return os.path.relpath(os.path.realpath(path), start) 78 79# Query the compiler about its preprocessor directives and return all of them as 80# a dictionnary. 81def GetCompilerDirectives(env): 82 args = [env['compiler']] 83 # Pass the CXXFLAGS varables to the compile, in case we've used "-m32" to 84 # compile for i386. 85 if env['CXXFLAGS']: 86 args.append(str(env['CXXFLAGS'])) 87 args += ['-E', '-dM', '-'] 88 89 # Instruct the compiler to dump all its preprocessor macros. 90 dump = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 91 out, _ = dump.communicate() 92 return { 93 # Extract the macro name as key and value as element. 94 match.group(1): match.group(2) 95 for match in [ 96 # Capture macro name. 97 re.search('^#define (\S+?) (.+)$', macro) 98 for macro in out.split('\n') 99 ] 100 # Filter out non-matches. 101 if match 102 } 103 104# Query the target architecture of the compiler. The 'target' architecture of 105# the compiler used to build VIXL is considered to be the 'host' architecture of 106# VIXL itself. 107def GetHostArch(env): 108 directives = GetCompilerDirectives(env) 109 if "__x86_64__" in directives: 110 return "x86_64" 111 elif "__i386__" in directives: 112 return "i386" 113 elif "__arm__" in directives: 114 return "aarch32" 115 elif "__aarch64__" in directives: 116 return "aarch64" 117 else: 118 raise Exception("Unsupported archtecture") 119 120# Class representing the compiler toolchain and version. 121class CompilerInformation(object): 122 def __init__(self, env): 123 directives = GetCompilerDirectives(env) 124 if '__llvm__' in directives: 125 major = int(directives['__clang_major__']) 126 minor = int(directives['__clang_minor__']) 127 self.compiler = 'clang' 128 self.version = '{}.{}'.format(major, minor) 129 elif '__GNUC__' in directives: 130 major = int(directives['__GNUC__']) 131 minor = int(directives['__GNUC_MINOR__']) 132 self.compiler = 'gcc' 133 self.version = '{}.{}'.format(major, minor) 134 else: 135 # Allow other compilers to be used for building VIXL. However, one would 136 # need to teach this class how to extract version information in order to 137 # make use of it. 138 self.compiler = None 139 self.version = None 140 141 def GetDescription(self): 142 return "{}-{}".format(self.compiler, self.version) 143 144 def __str__(self): 145 return self.GetDescription() 146 147 # Compare string descriptions with our object. The semantics are: 148 # 149 # - Equality 150 # 151 # If the description does not have a version number, then we compare the 152 # compiler names. For instance, "clang-3.6" is still equal to "clang" but of 153 # course is not to "gcc". 154 # 155 # - Ordering 156 # 157 # Asking whether a compiler is lower than another depends on the version 158 # number. What we are really asking here when using these operator is 159 # "Is my compiler in the allowed range?". So with this in mind, comparing 160 # two different compilers will always return false. If the compilers are the 161 # same, then the version numbers are compared. Of course, we cannot use 162 # ordering operators if no version number is provided. 163 164 def __eq__(self, description): 165 if description == self.GetDescription(): 166 return True 167 else: 168 # The user may not have provided a version, let's see if it matches the 169 # compiler. 170 return self.compiler == description 171 172 def __ne__(self, description): 173 return not self.__eq__(description) 174 175 def __lt__(self, description): 176 return self.CompareVersion(operator.lt, description) 177 178 def __le__(self, description): 179 return self.CompareVersion(operator.le, description) 180 181 def __ge__(self, description): 182 return self.CompareVersion(operator.ge, description) 183 184 def __gt__(self, description): 185 return self.CompareVersion(operator.gt, description) 186 187 # Comparing the provided `description` string, in the form of 188 # "{compiler}-{major}.{minor}". The comparison is done using the provided 189 # `operator` argument. 190 def CompareVersion(self, operator, description): 191 match = re.search('^(\S+)-(.*?)$', description) 192 if not match: 193 raise Exception("A version number is required when comparing compilers") 194 compiler, version = match.group(1), match.group(2) 195 # The result is false if the compilers are different, otherwise compare the 196 # version numbers. 197 return self.compiler == compiler and \ 198 operator(LooseVersion(self.version), LooseVersion(version)) 199 200class ReturnCode: 201 def __init__(self, exit_on_error, printer_fn): 202 self.rc = 0 203 self.exit_on_error = exit_on_error 204 self.printer_fn = printer_fn 205 206 def Combine(self, rc): 207 self.rc |= rc 208 if self.exit_on_error and rc != 0: 209 self.PrintStatus() 210 sys.exit(rc) 211 212 @property 213 def Value(self): 214 return self.rc 215 216 def PrintStatus(self): 217 self.printer_fn('\n$ ' + ' '.join(sys.argv)) 218 if self.rc == 0: 219 self.printer_fn('SUCCESS') 220 else: 221 self.printer_fn('FAILURE') 222 223# Return a list of files whose name matches at least one `include` pattern, and 224# no `exclude` patterns, and whose directory (relative to the repository base) 225# matches at least one `include_dirs` and no `exclude_dirs` patterns. 226# 227# For directory matches, leading and trailing slashes are added first (so that 228# "*/foo/*" matches all of 'foo/bar', 'bar/foo' and 'bar/foo/bar'). 229def get_source_files( 230 include = ['*.h', '*.cc'], 231 include_dirs = ['/src/*', '/test/*', '/examples/*', '/benchmarks/*'], 232 exclude = [], 233 exclude_dirs = ['.*', '*/traces/*']): 234 def NameMatchesAnyFilter(name, filters): 235 for f in filters: 236 if fnmatch.fnmatch(name, f): 237 return True 238 return False 239 240 files_found = [] 241 for root, dirs, files in os.walk(config.dir_root): 242 git_path = os.path.join('/', os.path.relpath(root, config.dir_root), '') 243 if NameMatchesAnyFilter(git_path, include_dirs) and \ 244 not NameMatchesAnyFilter(git_path, exclude_dirs): 245 files_found += [ 246 os.path.join(root, name) 247 for name in files 248 if NameMatchesAnyFilter(name, include) and \ 249 not NameMatchesAnyFilter(name, exclude) 250 ] 251 return files_found 252