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 27 import glob 28 import itertools 29 import os 30 from os.path import join 31 import platform 32 import subprocess 33 import sys 34 from collections import OrderedDict 35 36 root_dir = os.path.dirname(File('SConstruct').rfile().abspath) 37 sys.path.insert(0, join(root_dir, 'tools')) 38 import config 39 import util 40 41 from SCons.Errors import UserError 42 43 44 Help(''' 45 Build system for the VIXL project. 46 See 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. 51 class 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 68 top_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. 77 options = { 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. 121 def modifiable_flags_handler(env): 122 env['modifiable_flags'] = \ 123 'on' if 'mode' in env and env['mode'] == 'debug' else 'off' 124 125 126 def symbols_handler(env): 127 env['symbols'] = 'on' if 'mode' in env and env['mode'] == 'debug' else 'off' 128 129 def Is32BitHost(env): 130 return env['host_arch'] in ['aarch32', 'i386'] 131 132 def IsAArch64Host(env): 133 return env['host_arch'] == 'aarch64' 134 135 def CanTargetA32(env): 136 return 'a32' in env['target'] 137 138 def CanTargetT32(env): 139 return 't32' in env['target'] 140 141 def CanTargetAArch32(env): 142 return CanTargetA32(env) or CanTargetT32(env) 143 144 def CanTargetA64(env): 145 return 'a64' in env['target'] 146 147 def 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. 153 def 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. 162 def 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. 170 def default_validator(env): 171 pass 172 173 174 def 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. 182 vars_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 194 def 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 205 def 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 228 vars = Variables() 229 # Define command line build options. 230 vars.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. 255 options_influencing_build_path = [ 256 'target', 'mode', 'symbols', 'CXX', 'std', 'simulator', 'negative_testing', 257 'code_buffer_allocator' 258 ] 259 260 261 262 # Build helpers ---------------------------------------------------------------- 263 264 def 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. 285 def 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 295 def 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. 303 def 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 313 def 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 349 def 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 386 def 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 396 def 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 406 def 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 412 def 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. 435 env = 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. 441 unknown_build_options = vars.UnknownVariables() 442 if unknown_build_options: 443 print 'Unknown build options:', unknown_build_options.keys() 444 Exit(1) 445 446 ConfigureEnvironment(env) 447 Help(vars.GenerateHelpText(env)) 448 libvixl = VIXLLibraryTarget(env) 449 Default(libvixl) 450 env.Alias('libvixl', libvixl) 451 top_level_targets.Add('', 'Build the VIXL library.') 452 453 454 # Common test code. 455 test_build_dir = PrepareVariantDir('test', TargetBuildDir(env)) 456 test_objects = [env.Object(Glob(join(test_build_dir, '*.cc')))] 457 458 # AArch32 support 459 if 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 491 if 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 533 test = env.Program(join(test_build_dir, 'test-runner'), test_objects, 534 LIBS=[libvixl]) 535 env.Alias('tests', test) 536 top_level_targets.Add('tests', 'Build the tests.') 537 538 539 env.Alias('all', top_level_targets.targets) 540 top_level_targets.Add('all', 'Build all the targets above.') 541 542 Help('\n\nAvailable top level targets:\n' + top_level_targets.Help()) 543 544 extra_targets = VIXLTargets() 545 546 # Build documentation 547 doc = [ 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 ] 559 env.Alias('doc', doc) 560 extra_targets.Add('doc', 'Convert documentation to HTML (requires the ' 561 '`markdown` program).') 562 563 Help('\nAvailable extra targets:\n' + extra_targets.Help()) 564