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  src_files = glob.glob(os.path.join(path, '*.cc'))
41  return [os.path.splitext(os.path.basename(x))[0] for x in src_files]
42
43
44def abort(message):
45  print('ABORTING: ' + message)
46  sys.exit(1)
47
48
49def getstatusoutput(command):
50  return subprocess.getstatusoutput(command)
51
52
53def IsCommandAvailable(command):
54    retcode, unused_output = getstatusoutput('which %s' % command)
55    return retcode == 0
56
57
58def ensure_dir(path_name):
59  if not os.path.exists(path_name):
60    os.makedirs(path_name)
61
62
63# Check that the specified program is available.
64def require_program(program_name):
65  rc, out = getstatusoutput('which %s' % program_name)
66  if rc != 0:
67    print('ERROR: The required program %s was not found.' % program_name)
68    sys.exit(rc)
69
70def relrealpath(path, start=os.getcwd()):
71  return os.path.relpath(os.path.realpath(path), start)
72
73# Query the compiler about its preprocessor directives and return all of them as
74# a dictionary.
75def GetCompilerDirectives(env):
76  args = [env['compiler']]
77  # Pass the CXXFLAGS variables to the compile, in case we've used "-m32" to
78  # compile for i386.
79  if env['CXXFLAGS']:
80    args.append(str(env['CXXFLAGS']))
81  args += ['-E', '-dM', '-']
82
83  # Instruct the compiler to dump all its preprocessor macros.
84  dump = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
85                          universal_newlines=True)
86  out, _ = dump.communicate()
87  return {
88    # Extract the macro name as key and value as element.
89    match.group(1): match.group(2)
90    for match in [
91      # Capture macro name.
92      re.search('^#define (\S+?) (.+)$', macro)
93      for macro in out.split('\n')
94    ]
95    # Filter out non-matches.
96    if match
97  }
98
99# Query the target architecture of the compiler. The 'target' architecture of
100# the compiler used to build VIXL is considered to be the 'host' architecture of
101# VIXL itself.
102def GetHostArch(env):
103  directives = GetCompilerDirectives(env)
104  if "__x86_64__" in directives:
105    return "x86_64"
106  elif "__i386__" in directives:
107    return "i386"
108  elif "__arm__" in directives:
109    return "aarch32"
110  elif "__aarch64__" in directives:
111    return "aarch64"
112  else:
113    raise Exception("Unsupported architecture")
114
115# Class representing the compiler toolchain and version.
116class CompilerInformation(object):
117  def __init__(self, env):
118    directives = GetCompilerDirectives(env)
119    if '__llvm__' in directives:
120      major = int(directives['__clang_major__'])
121      minor = int(directives['__clang_minor__'])
122      self.compiler = 'clang'
123      self.version = '{}.{}'.format(major, minor)
124    elif '__GNUC__' in directives:
125      major = int(directives['__GNUC__'])
126      minor = int(directives['__GNUC_MINOR__'])
127      self.compiler = 'gcc'
128      self.version = '{}.{}'.format(major, minor)
129    else:
130      # Allow other compilers to be used for building VIXL. However, one would
131      # need to teach this class how to extract version information in order to
132      # make use of it.
133      self.compiler = None
134      self.version = None
135
136  def GetDescription(self):
137    return "{}-{}".format(self.compiler, self.version)
138
139  def __str__(self):
140    return self.GetDescription()
141
142  # Compare string descriptions with our object. The semantics are:
143  #
144  # - Equality
145  #
146  #   If the description does not have a version number, then we compare the
147  #   compiler names. For instance, "clang-3.6" is still equal to "clang" but of
148  #   course is not to "gcc".
149  #
150  # - Ordering
151  #
152  #   Asking whether a compiler is lower than another depends on the version
153  #   number. What we are really asking here when using these operator is
154  #   "Is my compiler in the allowed range?". So with this in mind, comparing
155  #   two different compilers will always return false. If the compilers are the
156  #   same, then the version numbers are compared. Of course, we cannot use
157  #   ordering operators if no version number is provided.
158
159  def __eq__(self, description):
160    if description == self.GetDescription():
161      return True
162    else:
163      # The user may not have provided a version, let's see if it matches the
164      # compiler.
165      return self.compiler == description
166
167  def __ne__(self, description):
168    return not self.__eq__(description)
169
170  def __lt__(self, description):
171    return self.CompareVersion(operator.lt, description)
172
173  def __le__(self, description):
174    return self.CompareVersion(operator.le, description)
175
176  def __ge__(self, description):
177    return self.CompareVersion(operator.ge, description)
178
179  def __gt__(self, description):
180    return self.CompareVersion(operator.gt, description)
181
182  # Comparing the provided `description` string, in the form of
183  # "{compiler}-{major}.{minor}". The comparison is done using the provided
184  # `operator` argument.
185  def CompareVersion(self, operator, description):
186    match = re.search('^(\S+)-(.*?)$', description)
187    if not match:
188      raise Exception("A version number is required when comparing compilers")
189    compiler, version = match.group(1), match.group(2)
190    # The result is false if the compilers are different, otherwise compare the
191    # version numbers.
192    return self.compiler == compiler and \
193           operator(LooseVersion(self.version), LooseVersion(version))
194
195class ReturnCode:
196  def __init__(self, exit_on_error, printer_fn):
197    self.rc = 0
198    self.exit_on_error = exit_on_error
199    self.printer_fn = printer_fn
200
201  def Combine(self, rc):
202    self.rc |= rc
203    if self.exit_on_error and rc != 0:
204      self.PrintStatus()
205      sys.exit(rc)
206
207  @property
208  def Value(self):
209    return self.rc
210
211  def PrintStatus(self):
212    self.printer_fn('\n$ ' + ' '.join(sys.argv))
213    if self.rc == 0:
214      self.printer_fn('SUCCESS')
215    else:
216      self.printer_fn('FAILURE')
217
218# Return a list of files whose name matches at least one `include` pattern, and
219# no `exclude` patterns, and whose directory (relative to the repository base)
220# matches at least one `include_dirs` and no `exclude_dirs` patterns.
221#
222# For directory matches, leading and trailing slashes are added first (so that
223# "*/foo/*" matches all of 'foo/bar', 'bar/foo' and 'bar/foo/bar').
224def get_source_files(
225    include = ['*.h', '*.cc'],
226    include_dirs = ['/src/*', '/test/*', '/examples/*', '/benchmarks/*'],
227    exclude = [],
228    exclude_dirs = ['.*', '*/traces/*']):
229  def NameMatchesAnyFilter(name, filters):
230    for f in filters:
231      if fnmatch.fnmatch(name, f):
232        return True
233    return False
234
235  files_found = []
236  for root, dirs, files in os.walk(config.dir_root):
237    git_path = os.path.join('/', os.path.relpath(root, config.dir_root), '')
238    if NameMatchesAnyFilter(git_path, include_dirs) and \
239        not NameMatchesAnyFilter(git_path, exclude_dirs):
240      files_found += [
241        os.path.join(root, name)
242        for name in files
243        if NameMatchesAnyFilter(name, include) and \
244            not NameMatchesAnyFilter(name, exclude)
245      ]
246  return files_found
247