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