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