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 universal_newlines=True) 92 out, _ = dump.communicate() 93 return { 94 # Extract the macro name as key and value as element. 95 match.group(1): match.group(2) 96 for match in [ 97 # Capture macro name. 98 re.search('^#define (\S+?) (.+)$', macro) 99 for macro in out.split('\n') 100 ] 101 # Filter out non-matches. 102 if match 103 } 104 105# Query the target architecture of the compiler. The 'target' architecture of 106# the compiler used to build VIXL is considered to be the 'host' architecture of 107# VIXL itself. 108def GetHostArch(env): 109 directives = GetCompilerDirectives(env) 110 if "__x86_64__" in directives: 111 return "x86_64" 112 elif "__i386__" in directives: 113 return "i386" 114 elif "__arm__" in directives: 115 return "aarch32" 116 elif "__aarch64__" in directives: 117 return "aarch64" 118 else: 119 raise Exception("Unsupported archtecture") 120 121# Class representing the compiler toolchain and version. 122class CompilerInformation(object): 123 def __init__(self, env): 124 directives = GetCompilerDirectives(env) 125 if '__llvm__' in directives: 126 major = int(directives['__clang_major__']) 127 minor = int(directives['__clang_minor__']) 128 self.compiler = 'clang' 129 self.version = '{}.{}'.format(major, minor) 130 elif '__GNUC__' in directives: 131 major = int(directives['__GNUC__']) 132 minor = int(directives['__GNUC_MINOR__']) 133 self.compiler = 'gcc' 134 self.version = '{}.{}'.format(major, minor) 135 else: 136 # Allow other compilers to be used for building VIXL. However, one would 137 # need to teach this class how to extract version information in order to 138 # make use of it. 139 self.compiler = None 140 self.version = None 141 142 def GetDescription(self): 143 return "{}-{}".format(self.compiler, self.version) 144 145 def __str__(self): 146 return self.GetDescription() 147 148 # Compare string descriptions with our object. The semantics are: 149 # 150 # - Equality 151 # 152 # If the description does not have a version number, then we compare the 153 # compiler names. For instance, "clang-3.6" is still equal to "clang" but of 154 # course is not to "gcc". 155 # 156 # - Ordering 157 # 158 # Asking whether a compiler is lower than another depends on the version 159 # number. What we are really asking here when using these operator is 160 # "Is my compiler in the allowed range?". So with this in mind, comparing 161 # two different compilers will always return false. If the compilers are the 162 # same, then the version numbers are compared. Of course, we cannot use 163 # ordering operators if no version number is provided. 164 165 def __eq__(self, description): 166 if description == self.GetDescription(): 167 return True 168 else: 169 # The user may not have provided a version, let's see if it matches the 170 # compiler. 171 return self.compiler == description 172 173 def __ne__(self, description): 174 return not self.__eq__(description) 175 176 def __lt__(self, description): 177 return self.CompareVersion(operator.lt, description) 178 179 def __le__(self, description): 180 return self.CompareVersion(operator.le, description) 181 182 def __ge__(self, description): 183 return self.CompareVersion(operator.ge, description) 184 185 def __gt__(self, description): 186 return self.CompareVersion(operator.gt, description) 187 188 # Comparing the provided `description` string, in the form of 189 # "{compiler}-{major}.{minor}". The comparison is done using the provided 190 # `operator` argument. 191 def CompareVersion(self, operator, description): 192 match = re.search('^(\S+)-(.*?)$', description) 193 if not match: 194 raise Exception("A version number is required when comparing compilers") 195 compiler, version = match.group(1), match.group(2) 196 # The result is false if the compilers are different, otherwise compare the 197 # version numbers. 198 return self.compiler == compiler and \ 199 operator(LooseVersion(self.version), LooseVersion(version)) 200 201class ReturnCode: 202 def __init__(self, exit_on_error, printer_fn): 203 self.rc = 0 204 self.exit_on_error = exit_on_error 205 self.printer_fn = printer_fn 206 207 def Combine(self, rc): 208 self.rc |= rc 209 if self.exit_on_error and rc != 0: 210 self.PrintStatus() 211 sys.exit(rc) 212 213 @property 214 def Value(self): 215 return self.rc 216 217 def PrintStatus(self): 218 self.printer_fn('\n$ ' + ' '.join(sys.argv)) 219 if self.rc == 0: 220 self.printer_fn('SUCCESS') 221 else: 222 self.printer_fn('FAILURE') 223 224# Return a list of files whose name matches at least one `include` pattern, and 225# no `exclude` patterns, and whose directory (relative to the repository base) 226# matches at least one `include_dirs` and no `exclude_dirs` patterns. 227# 228# For directory matches, leading and trailing slashes are added first (so that 229# "*/foo/*" matches all of 'foo/bar', 'bar/foo' and 'bar/foo/bar'). 230def get_source_files( 231 include = ['*.h', '*.cc'], 232 include_dirs = ['/src/*', '/test/*', '/examples/*', '/benchmarks/*'], 233 exclude = [], 234 exclude_dirs = ['.*', '*/traces/*']): 235 def NameMatchesAnyFilter(name, filters): 236 for f in filters: 237 if fnmatch.fnmatch(name, f): 238 return True 239 return False 240 241 files_found = [] 242 for root, dirs, files in os.walk(config.dir_root): 243 git_path = os.path.join('/', os.path.relpath(root, config.dir_root), '') 244 if NameMatchesAnyFilter(git_path, include_dirs) and \ 245 not NameMatchesAnyFilter(git_path, exclude_dirs): 246 files_found += [ 247 os.path.join(root, name) 248 for name in files 249 if NameMatchesAnyFilter(name, include) and \ 250 not NameMatchesAnyFilter(name, exclude) 251 ] 252 return files_found 253