• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
27import glob
28import itertools
29import os
30from os.path import join
31import platform
32import subprocess
33import sys
34from collections import OrderedDict
35
36root_dir = os.path.dirname(File('SConstruct').rfile().abspath)
37sys.path.insert(0, join(root_dir, 'tools'))
38import config
39import util
40
41from SCons.Errors import UserError
42
43
44Help('''
45Build system for the VIXL project.
46See README.md for documentation and details about the build system.
47''')
48
49
50# We track top-level targets to automatically generate help and alias them.
51class VIXLTargets:
52  def __init__(self):
53    self.targets = []
54    self.help_messages = []
55  def Add(self, target, help_message):
56    self.targets.append(target)
57    self.help_messages.append(help_message)
58  def Help(self):
59    res = ""
60    for i in range(len(self.targets)):
61      res += '\t{0:<{1}}{2:<{3}}\n'.format(
62        'scons ' + self.targets[i],
63        len('scons ') + max(map(len, self.targets)),
64        ' : ' + self.help_messages[i],
65        len(' : ') + max(map(len, self.help_messages)))
66    return res
67
68top_level_targets = VIXLTargets()
69
70
71
72# Build options ----------------------------------------------------------------
73
74# Store all the options in a dictionary.
75# The SConstruct will check the build variables and construct the build
76# environment as appropriate.
77options = {
78    'all' : { # Unconditionally processed.
79      'CCFLAGS' : ['-Wall',
80                   '-Werror',
81                   '-fdiagnostics-show-option',
82                   '-Wextra',
83                   '-Wredundant-decls',
84                   '-pedantic',
85                   '-Wwrite-strings',
86                   '-Wunused'],
87      'CPPPATH' : [config.dir_src_vixl]
88      },
89#   'build_option:value' : {
90#     'environment_key' : 'values to append'
91#     },
92    'mode:debug' : {
93      'CCFLAGS' : ['-DVIXL_DEBUG', '-O0']
94      },
95    'mode:release' : {
96      'CCFLAGS' : ['-O3'],
97      },
98    'simulator:aarch64' : {
99      'CCFLAGS' : ['-DVIXL_INCLUDE_SIMULATOR_AARCH64'],
100      },
101    'symbols:on' : {
102      'CCFLAGS' : ['-g'],
103      'LINKFLAGS' : ['-g']
104      },
105    'negative_testing:on' : {
106      'CCFLAGS' : ['-DVIXL_NEGATIVE_TESTING']
107      },
108    'code_buffer_allocator:mmap' : {
109      'CCFLAGS' : ['-DVIXL_CODE_BUFFER_MMAP']
110      },
111    'code_buffer_allocator:malloc' : {
112      'CCFLAGS' : ['-DVIXL_CODE_BUFFER_MALLOC']
113      }
114    }
115
116
117# A `DefaultVariable` has a default value that depends on elements not known
118# when variables are first evaluated.
119# Each `DefaultVariable` has a handler that will compute the default value for
120# the given environment.
121def modifiable_flags_handler(env):
122  env['modifiable_flags'] = \
123      'on' if 'mode' in env and env['mode'] == 'debug' else 'off'
124
125
126def symbols_handler(env):
127  env['symbols'] = 'on' if 'mode' in env and env['mode'] == 'debug' else 'off'
128
129def Is32BitHost(env):
130  return env['host_arch'] in ['aarch32', 'i386']
131
132def IsAArch64Host(env):
133  return env['host_arch'] == 'aarch64'
134
135def CanTargetA32(env):
136  return 'a32' in env['target']
137
138def CanTargetT32(env):
139  return 't32' in env['target']
140
141def CanTargetAArch32(env):
142  return CanTargetA32(env) or CanTargetT32(env)
143
144def CanTargetA64(env):
145  return 'a64' in env['target']
146
147def CanTargetAArch64(env):
148  return CanTargetA64(env)
149
150
151# By default, include the simulator only if AArch64 is targeted and we are not
152# building VIXL natively for AArch64.
153def simulator_handler(env):
154  if not IsAArch64Host(env) and CanTargetAArch64(env):
155    env['simulator'] = 'aarch64'
156  else:
157    env['simulator'] = 'none'
158
159
160# 'mmap' is required for use with 'mprotect', which is needed for the tests
161# (when running natively), so we use it by default where we can.
162def code_buffer_allocator_handler(env):
163  directives = util.GetCompilerDirectives(env)
164  if '__linux__' in directives:
165    env['code_buffer_allocator'] = 'mmap'
166  else:
167    env['code_buffer_allocator'] = 'malloc'
168
169# A validator checks the consistency of provided options against the environment.
170def default_validator(env):
171  pass
172
173
174def simulator_validator(env):
175  if env['simulator'] == 'aarch64' and not CanTargetAArch64(env):
176    raise UserError('Building an AArch64 simulator implies that VIXL targets '
177                    'AArch64. Set `target` to include `aarch64` or `a64`.')
178
179
180# Default variables may depend on each other, therefore we need this dictionnary
181# to be ordered.
182vars_default_handlers = OrderedDict({
183    # variable_name    : [ 'default val', 'handler', 'validator']
184    'symbols'          : [ 'mode==debug', symbols_handler, default_validator ],
185    'modifiable_flags' : [ 'mode==debug', modifiable_flags_handler, default_validator],
186    'simulator'        : [ 'on if the target architectures include AArch64 but '
187                           'the host is not AArch64, else off',
188                           simulator_handler, simulator_validator ],
189    'code_buffer_allocator' : [ 'mmap with __linux__, malloc otherwise',
190                                code_buffer_allocator_handler, default_validator ]
191    })
192
193
194def DefaultVariable(name, help, allowed_values):
195  help = '%s (%s)' % (help, '|'.join(allowed_values))
196  default_value = vars_default_handlers[name][0]
197  def validator(name, value, env):
198    if value != default_value and value not in allowed_values:
199        raise UserError('Invalid value for option {name}: {value}.  '
200                        'Valid values are: {allowed_values}'.format(
201                            name, value, allowed_values))
202  return (name, help, default_value, validator)
203
204
205def AliasedListVariable(name, help, default_value, allowed_values, aliasing):
206  help = '%s (all|auto|comma-separated list) (any combination from [%s])' % \
207         (help, ', '.join(allowed_values))
208
209  def validator(name, value, env):
210    # Here list has been converted to space separated strings.
211    if value == '': return  # auto
212    for v in value.split():
213      if v not in allowed_values:
214        raise UserError('Invalid value for %s: %s' % (name, value))
215
216  def converter(value):
217    if value == 'auto': return []
218    if value == 'all':
219      translated = [aliasing[v] for v in allowed_values]
220      return list(set(itertools.chain.from_iterable(translated)))
221    # The validator is run later hence the get.
222    translated = [aliasing.get(v, v) for v in value.split(',')]
223    return list(set(itertools.chain.from_iterable(translated)))
224
225  return (name, help, default_value, validator, converter)
226
227
228vars = Variables()
229# Define command line build options.
230vars.AddVariables(
231    AliasedListVariable('target', 'Target ISA/Architecture', 'auto',
232                        ['aarch32', 'a32', 't32', 'aarch64', 'a64'],
233                        {'aarch32' : ['a32', 't32'],
234                         'a32' : ['a32'], 't32' : ['t32'],
235                         'aarch64' : ['a64'], 'a64' : ['a64']}),
236    EnumVariable('mode', 'Build mode',
237                 'release', allowed_values=config.build_options_modes),
238    EnumVariable('negative_testing',
239                  'Enable negative testing (needs exceptions)',
240                 'off', allowed_values=['on', 'off']),
241    DefaultVariable('symbols', 'Include debugging symbols in the binaries',
242                    ['on', 'off']),
243    DefaultVariable('simulator', 'Simulators to include', ['aarch64', 'none']),
244    DefaultVariable('code_buffer_allocator',
245                    'Configure the allocation mechanism in the CodeBuffer',
246                    ['malloc', 'mmap']),
247    ('std', 'C++ standard. The standards tested are: %s.' % \
248                                         ', '.join(config.tested_cpp_standards))
249    )
250
251# We use 'variant directories' to avoid recompiling multiple times when build
252# options are changed, different build paths are used depending on the options
253# set. These are the options that should be reflected in the build directory
254# path.
255options_influencing_build_path = [
256  'target', 'mode', 'symbols', 'CXX', 'std', 'simulator', 'negative_testing',
257  'code_buffer_allocator'
258]
259
260
261
262# Build helpers ----------------------------------------------------------------
263
264def RetrieveEnvironmentVariables(env):
265  for key in ['CC', 'CXX', 'AR', 'RANLIB', 'LD']:
266    if os.getenv(key): env[key] = os.getenv(key)
267  if os.getenv('LD_LIBRARY_PATH'): env['LIBPATH'] = os.getenv('LD_LIBRARY_PATH')
268  if os.getenv('CCFLAGS'):
269    env.Append(CCFLAGS = os.getenv('CCFLAGS').split())
270  if os.getenv('CXXFLAGS'):
271    env.Append(CXXFLAGS = os.getenv('CXXFLAGS').split())
272  if os.getenv('LINKFLAGS'):
273    env.Append(LINKFLAGS = os.getenv('LINKFLAGS').split())
274  # This allows colors to be displayed when using with clang.
275  env['ENV']['TERM'] = os.getenv('TERM')
276
277
278# The architecture targeted by default will depend on the compiler being
279# used. 'host_arch' is extracted from the compiler while 'target' can be
280# set by the user.
281# By default, we target both AArch32 and AArch64 unless the compiler targets a
282# 32-bit architecture. At the moment, we cannot build VIXL's AArch64 support on
283# a 32-bit platform.
284# TODO: Port VIXL to build on a 32-bit platform.
285def target_handler(env):
286  # Auto detect
287  if Is32BitHost(env):
288    # We use list(set(...)) to keep the same order as if it was specify as
289    # an option.
290    env['target'] = list(set(['a32', 't32']))
291  else:
292    env['target'] = list(set(['a64', 'a32', 't32']))
293
294
295def target_validator(env):
296  # TODO: Port VIXL64 to work on a 32-bit platform.
297  if Is32BitHost(env) and CanTargetAArch64(env):
298    raise UserError('Building VIXL for AArch64 in 32-bit is not supported. Set '
299                    '`target` to `aarch32`')
300
301
302# The target option is handled differently from the rest.
303def ProcessTargetOption(env):
304  if env['target'] == []: target_handler(env)
305
306  if 'a32' in env['target']: env['CCFLAGS'] += ['-DVIXL_INCLUDE_TARGET_A32']
307  if 't32' in env['target']: env['CCFLAGS'] += ['-DVIXL_INCLUDE_TARGET_T32']
308  if 'a64' in env['target']: env['CCFLAGS'] += ['-DVIXL_INCLUDE_TARGET_A64']
309
310  target_validator(env)
311
312
313def ProcessBuildOptions(env):
314  # 'all' is unconditionally processed.
315  if 'all' in options:
316    for var in options['all']:
317      if var in env and env[var]:
318        env[var] += options['all'][var]
319      else:
320        env[var] = options['all'][var]
321
322  # The target option *must* be processed before the options defined in
323  # vars_default_handlers.
324  ProcessTargetOption(env)
325
326  # Other build options must match 'option:value'
327  env_dict = env.Dictionary()
328
329  # First apply the default variables handlers in order.
330  for key, value in vars_default_handlers.items():
331    default = value[0]
332    handler = value[1]
333    if env_dict.get(key) == default:
334      handler(env_dict)
335
336  # Second, run the series of validators, to check for errors.
337  for _, value in vars_default_handlers.items():
338    validator = value[2]
339    validator(env)
340
341  for key in env_dict.keys():
342    # Then update the environment according to the value of the variable.
343    key_val_couple = key + ':%s' % env_dict[key]
344    if key_val_couple in options:
345      for var in options[key_val_couple]:
346        env[var] += options[key_val_couple][var]
347
348
349def ConfigureEnvironmentForCompiler(env):
350  if CanTargetA32(env) and CanTargetT32(env):
351    # When building for only one aarch32 isa, fixing the no-return is not worth
352    # the effort.
353    env.Append(CPPFLAGS = ['-Wmissing-noreturn'])
354
355  compiler = util.CompilerInformation(env)
356  if compiler == 'clang':
357    # These warnings only work for Clang.
358    # -Wimplicit-fallthrough only works when compiling the code base as C++11 or
359    # newer. The compiler does not complain if the option is passed when
360    # compiling earlier C++ standards.
361    env.Append(CPPFLAGS = ['-Wimplicit-fallthrough', '-Wshorten-64-to-32'])
362
363    # The '-Wunreachable-code' flag breaks builds for clang 3.4.
364    if compiler != 'clang-3.4':
365      env.Append(CPPFLAGS = ['-Wunreachable-code'])
366
367  # GCC 4.8 has a bug which produces a warning saying that an anonymous Operand
368  # object might be used uninitialized:
369  #   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57045
370  # The bug does not seem to appear in GCC 4.7, or in debug builds with GCC 4.8.
371  if env['mode'] == 'release':
372    if compiler == 'gcc-4.8':
373      env.Append(CPPFLAGS = ['-Wno-maybe-uninitialized'])
374
375  # When compiling with c++98 (the default), allow long long constants.
376  if 'std' not in env or env['std'] == 'c++98':
377    env.Append(CPPFLAGS = ['-Wno-long-long'])
378  # When compiling with c++11, suggest missing override keywords on methods.
379  if 'std' in env and env['std'] in ['c++11', 'c++14']:
380    if compiler >= 'gcc-5':
381      env.Append(CPPFLAGS = ['-Wsuggest-override'])
382    elif compiler >= 'clang-3.6':
383      env.Append(CPPFLAGS = ['-Winconsistent-missing-override'])
384
385
386def ConfigureEnvironment(env):
387  RetrieveEnvironmentVariables(env)
388  env['host_arch'] = util.GetHostArch(env)
389  ProcessBuildOptions(env)
390  if 'std' in env:
391    env.Append(CPPFLAGS = ['-std=' + env['std']])
392    std_path = env['std']
393  ConfigureEnvironmentForCompiler(env)
394
395
396def TargetBuildDir(env):
397  # Build-time option values are embedded in the build path to avoid requiring a
398  # full build when an option changes.
399  build_dir = config.dir_build
400  for option in options_influencing_build_path:
401    option_value = ''.join(env[option]) if option in env else ''
402    build_dir = join(build_dir, option + '_'+ option_value)
403  return build_dir
404
405
406def PrepareVariantDir(location, build_dir):
407  location_build_dir = join(build_dir, location)
408  VariantDir(location_build_dir, location)
409  return location_build_dir
410
411
412def VIXLLibraryTarget(env):
413  build_dir = TargetBuildDir(env)
414  # Create a link to the latest build directory.
415  # Use `-r` to avoid failure when `latest` exists and is a directory.
416  subprocess.check_call(["rm", "-rf", config.dir_build_latest])
417  util.ensure_dir(build_dir)
418  subprocess.check_call(["ln", "-s", build_dir, config.dir_build_latest])
419  # Source files are in `src` and in `src/aarch64/`.
420  variant_dir_vixl = PrepareVariantDir(join('src'), build_dir)
421  sources = [Glob(join(variant_dir_vixl, '*.cc'))]
422  if CanTargetAArch32(env):
423    variant_dir_aarch32 = PrepareVariantDir(join('src', 'aarch32'), build_dir)
424    sources.append(Glob(join(variant_dir_aarch32, '*.cc')))
425  if CanTargetAArch64(env):
426    variant_dir_aarch64 = PrepareVariantDir(join('src', 'aarch64'), build_dir)
427    sources.append(Glob(join(variant_dir_aarch64, '*.cc')))
428  return env.Library(join(build_dir, 'vixl'), sources)
429
430
431
432# Build ------------------------------------------------------------------------
433
434# The VIXL library, built by default.
435env = Environment(variables = vars,
436                  BUILDERS = {
437                      'Markdown': Builder(action = 'markdown $SOURCE > $TARGET',
438                                          suffix = '.html')
439                  })
440# Abort the build if any command line option is unknown or invalid.
441unknown_build_options = vars.UnknownVariables()
442if unknown_build_options:
443  print 'Unknown build options:',  unknown_build_options.keys()
444  Exit(1)
445
446ConfigureEnvironment(env)
447Help(vars.GenerateHelpText(env))
448libvixl = VIXLLibraryTarget(env)
449Default(libvixl)
450env.Alias('libvixl', libvixl)
451top_level_targets.Add('', 'Build the VIXL library.')
452
453
454# Common test code.
455test_build_dir = PrepareVariantDir('test', TargetBuildDir(env))
456test_objects = [env.Object(Glob(join(test_build_dir, '*.cc')))]
457
458# AArch32 support
459if CanTargetAArch32(env):
460  # The examples.
461  aarch32_example_names = util.ListCCFilesWithoutExt(config.dir_aarch32_examples)
462  aarch32_examples_build_dir = PrepareVariantDir('examples/aarch32', TargetBuildDir(env))
463  aarch32_example_targets = []
464  for example in aarch32_example_names:
465    prog = env.Program(join(aarch32_examples_build_dir, example),
466                       join(aarch32_examples_build_dir, example + '.cc'),
467                       LIBS=[libvixl])
468    aarch32_example_targets.append(prog)
469  env.Alias('aarch32_examples', aarch32_example_targets)
470  top_level_targets.Add('aarch32_examples', 'Build the examples for AArch32.')
471
472  # The benchmarks
473  aarch32_benchmark_names = util.ListCCFilesWithoutExt(config.dir_aarch32_benchmarks)
474  aarch32_benchmarks_build_dir = PrepareVariantDir('benchmarks/aarch32', TargetBuildDir(env))
475  aarch32_benchmark_targets = []
476  for bench in aarch32_benchmark_names:
477    prog = env.Program(join(aarch32_benchmarks_build_dir, bench),
478                       join(aarch32_benchmarks_build_dir, bench + '.cc'),
479                       LIBS=[libvixl])
480    aarch32_benchmark_targets.append(prog)
481  env.Alias('aarch32_benchmarks', aarch32_benchmark_targets)
482  top_level_targets.Add('aarch32_benchmarks', 'Build the benchmarks for AArch32.')
483
484  # The tests.
485  test_aarch32_build_dir = PrepareVariantDir(join('test', 'aarch32'), TargetBuildDir(env))
486  test_objects.append(env.Object(
487      Glob(join(test_aarch32_build_dir, '*.cc')),
488      CPPPATH = env['CPPPATH'] + [config.dir_tests]))
489
490# AArch64 support
491if CanTargetAArch64(env):
492  # The benchmarks.
493  aarch64_benchmark_names = util.ListCCFilesWithoutExt(config.dir_aarch64_benchmarks)
494  aarch64_benchmarks_build_dir = PrepareVariantDir('benchmarks/aarch64', TargetBuildDir(env))
495  aarch64_benchmark_targets = []
496  for bench in aarch64_benchmark_names:
497    prog = env.Program(join(aarch64_benchmarks_build_dir, bench),
498                       join(aarch64_benchmarks_build_dir, bench + '.cc'),
499                       LIBS=[libvixl])
500    aarch64_benchmark_targets.append(prog)
501  env.Alias('aarch64_benchmarks', aarch64_benchmark_targets)
502  top_level_targets.Add('aarch64_benchmarks', 'Build the benchmarks for AArch64.')
503
504  # The examples.
505  aarch64_example_names = util.ListCCFilesWithoutExt(config.dir_aarch64_examples)
506  aarch64_examples_build_dir = PrepareVariantDir('examples/aarch64', TargetBuildDir(env))
507  aarch64_example_targets = []
508  for example in aarch64_example_names:
509    prog = env.Program(join(aarch64_examples_build_dir, example),
510                       join(aarch64_examples_build_dir, example + '.cc'),
511                       LIBS=[libvixl])
512    aarch64_example_targets.append(prog)
513  env.Alias('aarch64_examples', aarch64_example_targets)
514  top_level_targets.Add('aarch64_examples', 'Build the examples for AArch64.')
515
516  # The tests.
517  test_aarch64_build_dir = PrepareVariantDir(join('test', 'aarch64'), TargetBuildDir(env))
518  test_objects.append(env.Object(
519      Glob(join(test_aarch64_build_dir, '*.cc')),
520      CPPPATH = env['CPPPATH'] + [config.dir_tests]))
521
522  # The test requires building the example files with specific options, so we
523  # create a separate variant dir for the example objects built this way.
524  test_aarch64_examples_vdir = join(TargetBuildDir(env), 'test', 'aarch64', 'test_examples')
525  VariantDir(test_aarch64_examples_vdir, '.')
526  test_aarch64_examples_obj = env.Object(
527      [Glob(join(test_aarch64_examples_vdir, join('test', 'aarch64', 'examples/aarch64', '*.cc'))),
528       Glob(join(test_aarch64_examples_vdir, join('examples/aarch64', '*.cc')))],
529      CCFLAGS = env['CCFLAGS'] + ['-DTEST_EXAMPLES'],
530      CPPPATH = env['CPPPATH'] + [config.dir_aarch64_examples] + [config.dir_tests])
531  test_objects.append(test_aarch64_examples_obj)
532
533test = env.Program(join(test_build_dir, 'test-runner'), test_objects,
534                   LIBS=[libvixl])
535env.Alias('tests', test)
536top_level_targets.Add('tests', 'Build the tests.')
537
538
539env.Alias('all', top_level_targets.targets)
540top_level_targets.Add('all', 'Build all the targets above.')
541
542Help('\n\nAvailable top level targets:\n' + top_level_targets.Help())
543
544extra_targets = VIXLTargets()
545
546# Build documentation
547doc = [
548    env.Markdown('README.md'),
549    env.Markdown('doc/changelog.md'),
550    env.Markdown('doc/aarch32/getting-started-aarch32.md'),
551    env.Markdown('doc/aarch32/design/code-generation-aarch32.md'),
552    env.Markdown('doc/aarch32/design/literal-pool-aarch32.md'),
553    env.Markdown('doc/aarch64/supported-instructions-aarch64.md'),
554    env.Markdown('doc/aarch64/getting-started-aarch64.md'),
555    env.Markdown('doc/aarch64/topics/ycm.md'),
556    env.Markdown('doc/aarch64/topics/extending-the-disassembler.md'),
557    env.Markdown('doc/aarch64/topics/index.md'),
558]
559env.Alias('doc', doc)
560extra_targets.Add('doc', 'Convert documentation to HTML (requires the '
561                         '`markdown` program).')
562
563Help('\nAvailable extra targets:\n' + extra_targets.Help())
564