Home | History | Annotate | Download | only in vixl
      1 # Copyright 2015, ARM Limited
      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 
     27 import glob
     28 import os
     29 from os.path import join
     30 import platform
     31 import subprocess
     32 import sys
     33 
     34 root_dir = os.path.dirname(File('SConstruct').rfile().abspath)
     35 sys.path.insert(0, join(root_dir, 'tools'))
     36 import config
     37 import util
     38 
     39 
     40 Help('''
     41 Build system for the VIXL project.
     42 See README.md for documentation and details about the build system.
     43 ''')
     44 
     45 
     46 # We track top-level targets to automatically generate help and alias them.
     47 class TopLevelTargets:
     48   def __init__(self):
     49     self.targets = []
     50     self.help_messages = []
     51   def Add(self, target, help_message):
     52     self.targets.append(target)
     53     self.help_messages.append(help_message)
     54   def Help(self):
     55     res = ""
     56     for i in range(len(self.targets)):
     57       res += '\t{0:<{1}}{2:<{3}}\n'.format(
     58         'scons ' + self.targets[i],
     59         len('scons ') + max(map(len, self.targets)),
     60         ' : ' + self.help_messages[i],
     61         len(' : ') + max(map(len, self.help_messages)))
     62     return res
     63 
     64 top_level_targets = TopLevelTargets()
     65 
     66 
     67 
     68 # Build options ----------------------------------------------------------------
     69 
     70 # Store all the options in a dictionary.
     71 # The SConstruct will check the build variables and construct the build
     72 # environment as appropriate.
     73 options = {
     74     'all' : { # Unconditionally processed.
     75       'CCFLAGS' : ['-Wall',
     76                    '-Werror',
     77                    '-fdiagnostics-show-option',
     78                    '-Wextra',
     79                    '-Wredundant-decls',
     80                    '-pedantic',
     81                    '-Wmissing-noreturn',
     82                    '-Wwrite-strings'],
     83       'CPPPATH' : [config.dir_src_vixl]
     84       },
     85 #   'build_option:value' : {
     86 #     'environment_key' : 'values to append'
     87 #     },
     88     'mode:debug' : {
     89       'CCFLAGS' : ['-DVIXL_DEBUG', '-O0']
     90       },
     91     'mode:release' : {
     92       'CCFLAGS' : ['-O3']
     93       },
     94     'simulator:on' : {
     95       'CCFLAGS' : ['-DVIXL_INCLUDE_SIMULATOR'],
     96       },
     97     'symbols:on' : {
     98       'CCFLAGS' : ['-g'],
     99       'LINKFLAGS' : ['-g']
    100       },
    101     }
    102 
    103 
    104 # A `DefaultVariable` has a default value that depends on elements not known
    105 # when variables are first evaluated.
    106 # Each `DefaultVariable` has a handler that will compute the default value for
    107 # the given environment.
    108 def modifiable_flags_handler(env):
    109   env['modifiable_flags'] = \
    110       'on' if 'mode' in env and env['mode'] == 'debug' else 'off'
    111 
    112 
    113 def symbols_handler(env):
    114   env['symbols'] = 'on' if 'mode' in env and env['mode'] == 'debug' else 'off'
    115 
    116 
    117 vars_default_handlers = {
    118     # variable_name    : [ 'default val', 'handler'                ]
    119     'symbols'          : [ 'mode==debug', symbols_handler          ],
    120     'modifiable_flags' : [ 'mode==debug', modifiable_flags_handler ]
    121     }
    122 
    123 
    124 def DefaultVariable(name, help, allowed):
    125   default_value = vars_default_handlers[name][0]
    126   allowed.append(default_value)
    127   return EnumVariable(name, help, default_value, allowed)
    128 
    129 
    130 vars = Variables()
    131 # Define command line build options.
    132 sim_default = 'off' if platform.machine() == 'aarch64' else 'on'
    133 vars.AddVariables(
    134     EnumVariable('mode', 'Build mode',
    135                  'release', allowed_values=config.build_options_modes),
    136     DefaultVariable('symbols', 'Include debugging symbols in the binaries',
    137                     ['on', 'off']),
    138     EnumVariable('simulator', 'Build for the simulator',
    139                  sim_default, allowed_values=['on', 'off']),
    140     ('std', 'C++ standard. The standards tested are: %s.' % \
    141                                          ', '.join(config.tested_cpp_standards))
    142     )
    143 
    144 # Abort the build if any command line option is unknown or invalid.
    145 unknown_build_options = vars.UnknownVariables()
    146 if unknown_build_options:
    147   print 'Unknown build options:',  unknown_build_options.keys()
    148   Exit(1)
    149 
    150 # We use 'variant directories' to avoid recompiling multiple times when build
    151 # options are changed, different build paths are used depending on the options
    152 # set. These are the options that should be reflected in the build directory
    153 # path.
    154 options_influencing_build_path = ['mode', 'symbols', 'CXX', 'std', 'simulator']
    155 
    156 
    157 
    158 # Build helpers ----------------------------------------------------------------
    159 
    160 def RetrieveEnvironmentVariables(env):
    161   for key in ['CC', 'CXX', 'CCFLAGS', 'CXXFLAGS', 'AR', 'RANLIB', 'LD']:
    162     if os.getenv(key): env[key] = os.getenv(key)
    163   if os.getenv('LD_LIBRARY_PATH'): env['LIBPATH'] = os.getenv('LD_LIBRARY_PATH')
    164   if os.getenv('CXXFLAGS'):
    165     env.Append(CXXFLAGS = os.getenv('CXXFLAGS').split())
    166   if os.getenv('LINKFLAGS'):
    167     env.Append(LINKFLAGS = os.getenv('LINKFLAGS').split())
    168   # This allows colors to be displayed when using with clang.
    169   env['ENV']['TERM'] = os.getenv('TERM')
    170 
    171 
    172 def ProcessBuildOptions(env):
    173   # 'all' is unconditionally processed.
    174   if 'all' in options:
    175     for var in options['all']:
    176       if var in env and env[var]:
    177         env[var] += options['all'][var]
    178       else:
    179         env[var] = options['all'][var]
    180   # Other build options must match 'option:value'
    181   env_dict = env.Dictionary()
    182   for key in env_dict.keys():
    183     # First apply the default variables handlers.
    184     if key in vars_default_handlers and \
    185         env_dict[key] == vars_default_handlers[key][0]:
    186       vars_default_handlers[key][1](env_dict)
    187     # Then update the environment according to the value of the variable.
    188     key_val_couple = key + ':%s' % env_dict[key]
    189     if key_val_couple in options:
    190       for var in options[key_val_couple]:
    191         env[var] += options[key_val_couple][var]
    192 
    193 
    194 def ConfigureEnvironmentForCompiler(env):
    195   def is_compiler(compiler):
    196     return env['CXX'].find(compiler) == 0
    197   if is_compiler('clang++'):
    198     # These warnings only work for Clang.
    199     # -Wimplicit-fallthrough only works when compiling the code base as C++11 or
    200     # newer. The compiler does not complain if the option is passed when
    201     # compiling earlier C++ standards.
    202     env.Append(CPPFLAGS = ['-Wimplicit-fallthrough', '-Wshorten-64-to-32'])
    203 
    204     # The '-Wunreachable-code' flag breaks builds for clang 3.4.
    205     process = subprocess.Popen(env['CXX'] + ' --version | grep "clang.*3\.4"',
    206                                shell = True,
    207                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    208     stdout, stderr = process.communicate()
    209     using_clang3_4 = stdout != ''
    210     if not using_clang3_4:
    211       env.Append(CPPFLAGS = ['-Wunreachable-code'])
    212 
    213   # GCC 4.8 has a bug which produces a warning saying that an anonymous Operand
    214   # object might be used uninitialized:
    215   #   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57045
    216   # The bug does not seem to appear in GCC 4.7, or in debug builds with GCC 4.8.
    217   if env['mode'] == 'release':
    218     process = subprocess.Popen(env['CXX'] + ' --version | grep "g++.*4\.8"',
    219                                shell = True,
    220                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    221     stdout, stderr = process.communicate()
    222     using_gcc48 = stdout != ''
    223     if using_gcc48:
    224       env.Append(CPPFLAGS = ['-Wno-maybe-uninitialized'])
    225 
    226 
    227 def ConfigureEnvironment(env):
    228   RetrieveEnvironmentVariables(env)
    229   ProcessBuildOptions(env)
    230   if 'std' in env:
    231     env.Append(CPPFLAGS = ['-std=' + env['std']])
    232     std_path = env['std']
    233   ConfigureEnvironmentForCompiler(env)
    234 
    235 
    236 def TargetBuildDir(env):
    237   # Build-time option values are embedded in the build path to avoid requiring a
    238   # full build when an option changes.
    239   build_dir = config.dir_build
    240   for option in options_influencing_build_path:
    241     option_value = env[option] if option in env else ''
    242     build_dir = join(build_dir, option + '_'+ option_value)
    243   return build_dir
    244 
    245 
    246 def PrepareVariantDir(location, build_dir):
    247   location_build_dir = join(build_dir, location)
    248   VariantDir(location_build_dir, location)
    249   return location_build_dir
    250 
    251 
    252 def VIXLLibraryTarget(env):
    253   build_dir = TargetBuildDir(env)
    254   # Create a link to the latest build directory.
    255   subprocess.check_call(["rm", "-f", config.dir_build_latest])
    256   util.ensure_dir(build_dir)
    257   subprocess.check_call(["ln", "-s", build_dir, config.dir_build_latest])
    258   # Source files are in `src/vixl` and in `src/vixl/a64/`.
    259   variant_dir_vixl = PrepareVariantDir(join('src', 'vixl'), build_dir)
    260   variant_dir_a64 = PrepareVariantDir(join('src', 'vixl', 'a64'), build_dir)
    261   sources = [Glob(join(variant_dir_vixl, '*.cc')),
    262              Glob(join(variant_dir_a64, '*.cc'))]
    263   return env.Library(join(build_dir, 'vixl'), sources)
    264 
    265 
    266 
    267 # Build ------------------------------------------------------------------------
    268 
    269 # The VIXL library, built by default.
    270 env = Environment(variables = vars)
    271 ConfigureEnvironment(env)
    272 Help(vars.GenerateHelpText(env))
    273 libvixl = VIXLLibraryTarget(env)
    274 Default(libvixl)
    275 env.Alias('libvixl', libvixl)
    276 top_level_targets.Add('', 'Build the VIXL library.')
    277 
    278 
    279 # The benchmarks.
    280 benchmark_names = util.ListCCFilesWithoutExt(config.dir_benchmarks)
    281 benchmarks_build_dir = PrepareVariantDir('benchmarks', TargetBuildDir(env))
    282 benchmark_targets = []
    283 for bench in benchmark_names:
    284   prog = env.Program(join(benchmarks_build_dir, bench),
    285                      join(benchmarks_build_dir, bench + '.cc'),
    286                      LIBS=[libvixl])
    287   benchmark_targets.append(prog)
    288 env.Alias('benchmarks', benchmark_targets)
    289 top_level_targets.Add('benchmarks', 'Build the benchmarks.')
    290 
    291 
    292 # The examples.
    293 example_names = util.ListCCFilesWithoutExt(config.dir_examples)
    294 examples_build_dir = PrepareVariantDir('examples', TargetBuildDir(env))
    295 example_targets = []
    296 for example in example_names:
    297   prog = env.Program(join(examples_build_dir, example),
    298                      join(examples_build_dir, example + '.cc'),
    299                      LIBS=[libvixl])
    300   example_targets.append(prog)
    301 env.Alias('examples', example_targets)
    302 top_level_targets.Add('examples', 'Build the examples.')
    303 
    304 
    305 # The tests.
    306 test_build_dir = PrepareVariantDir('test', TargetBuildDir(env))
    307 # The test requires building the example files with specific options, so we
    308 # create a separate variant dir for the example objects built this way.
    309 test_examples_vdir = join(TargetBuildDir(env), 'test', 'test_examples')
    310 VariantDir(test_examples_vdir, '.')
    311 test_examples_obj = env.Object(
    312     [Glob(join(test_examples_vdir, join('test', 'examples', '*.cc'))),
    313              Glob(join(test_examples_vdir, join('examples', '*.cc')))],
    314     CCFLAGS = env['CCFLAGS'] + ['-DTEST_EXAMPLES'],
    315     CPPPATH = env['CPPPATH'] + [config.dir_examples])
    316 test = env.Program(join(test_build_dir, 'test-runner'),
    317                    [Glob(join(test_build_dir, '*.cc')), test_examples_obj],
    318                    CPPPATH = env['CPPPATH'] + [config.dir_examples],
    319                    LIBS=[libvixl])
    320 env.Alias('tests', test)
    321 top_level_targets.Add('tests', 'Build the tests.')
    322 
    323 
    324 env.Alias('all', top_level_targets.targets)
    325 top_level_targets.Add('all', 'Build all the targets above.')
    326 
    327 Help('\n\nAvailable top level targets:\n' + top_level_targets.Help())
    328