# Copyright 2013, ARM Limited
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#   * Neither the name of ARM Limited nor the names of its contributors may be
#     used to endorse or promote products derived from this software without
#     specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import os.path
import sys

# Global configuration.
PROJ_SRC_DIR   = 'src'
PROJ_SRC_FILES = '''
src/utils-vixl.cc
src/a64/assembler-a64.cc
src/a64/macro-assembler-a64.cc
src/a64/instructions-a64.cc
src/a64/decoder-a64.cc
src/a64/debugger-a64.cc
src/a64/disasm-a64.cc
src/a64/cpu-a64.cc
src/a64/simulator-a64.cc
src/a64/instrument-a64.cc
'''.split()
PROJ_EXAMPLES_DIR = 'examples'
PROJ_EXAMPLES_SRC_FILES = '''
examples/debugger.cc
examples/add3-double.cc
examples/add4-double.cc
examples/factorial-rec.cc
examples/factorial.cc
examples/sum-array.cc
examples/abs.cc
examples/swap4.cc
examples/swap-int32.cc
examples/check-bounds.cc
examples/getting-started.cc
'''.split()
# List target specific files.
# Target names are used as dictionary entries.
TARGET_SRC_DIR = {
  'cctest': 'test',
  'bench_dataop': 'benchmarks',
  'bench_branch': 'benchmarks',
  'examples': 'examples'
}
TARGET_SRC_FILES = {
  'cctest': '''
    test/cctest.cc
    test/test-utils-a64.cc
    test/test-assembler-a64.cc
    test/test-simulator-a64.cc
    test/test-disasm-a64.cc
    test/test-fuzz-a64.cc
    test/examples/test-examples.cc
    '''.split() + PROJ_EXAMPLES_SRC_FILES,
  'bench_dataop': '''
    benchmarks/bench-dataop.cc
    '''.split(),
  'bench_branch': '''
    benchmarks/bench-branch.cc
    '''.split()
}
RELEASE_OBJ_DIR  = 'obj/release'
DEBUG_OBJ_DIR    = 'obj/debug'
COVERAGE_OBJ_DIR = 'obj/coverage'


# Helper functions.
def abort(message):
  print('ABORTING: ' + message)
  sys.exit(1)


def list_target(obj_dir, src_files):
  return map(lambda x: os.path.join(obj_dir, x), src_files)


def create_variant(obj_dir, targets_dir):
  VariantDir(os.path.join(obj_dir, PROJ_SRC_DIR), PROJ_SRC_DIR)
  for directory in targets_dir.itervalues():
    VariantDir(os.path.join(obj_dir, directory), directory)


# Build arguments.
args = Variables()
args.Add(EnumVariable('mode', 'Build mode', 'release',
                      allowed_values = ['release', 'debug', 'coverage']))
args.Add(EnumVariable('target', 'Target to build', 'cctest',
                      allowed_values = ['cctest',
                                        'bench_dataop',
                                        'bench_branch',
                                        'examples']))
args.Add(EnumVariable('simulator', 'build for the simulator', 'on',
                      allowed_values = ['on', 'off']))

# Configure the environment.
create_variant(RELEASE_OBJ_DIR, TARGET_SRC_DIR)
create_variant(DEBUG_OBJ_DIR, TARGET_SRC_DIR)
create_variant(COVERAGE_OBJ_DIR, TARGET_SRC_DIR)
env = Environment(variables=args)

# Commandline help.
Help(args.GenerateHelpText(env))

# Abort if any invalid argument was passed.
# This check must happened after an environment is created.
unknown_arg = args.UnknownVariables()
if unknown_arg:
  abort('Unknown variable(s): ' + str(unknown_arg.keys()))

# Setup tools.
# This is necessary for cross-compilation.
env['CXX'] = os.environ.get('CXX', env.get('CXX'))
env['AR'] = os.environ.get('AR', env.get('AR'))
env['RANLIB'] = os.environ.get('RANLIB', env.get('RANLIB'))
env['CC'] = os.environ.get('CC', env.get('CC'))
env['LD'] = os.environ.get('LD', env.get('LD'))

if os.environ.get('CPPFLAGS'):
  env.Append(CPPFLAGS = os.environ.get('CPPFLAGS').split())
if os.environ.get('LINKFLAGS'):
  env.Append(LINKFLAGS = os.environ.get('LINKFLAGS').split())

# Always look in 'src' for include files.
env.Append(CPPPATH = [PROJ_SRC_DIR])
env.Append(CPPFLAGS = ['-Wall',
                       '-Werror',
                       '-fdiagnostics-show-option',
                       '-Wextra',
                       '-pedantic',
                       # Explicitly enable the write-strings warning. VIXL uses
                       # const correctly when handling string constants.
                       '-Wwrite-strings'])

target_program = env['target']
build_suffix = ''

if env['simulator'] == 'on':
  env.Append(CPPFLAGS = ['-DUSE_SIMULATOR'])
  build_suffix += '_sim'

if env['mode'] == 'debug':
  env.Append(CPPFLAGS = ['-g', '-DDEBUG'])
  # Append the debug mode suffix to the executable name.
  build_suffix += '_g'
  build_dir = DEBUG_OBJ_DIR
elif env['mode'] == 'coverage':
  env.Append(CPPFLAGS = ['-g', '-DDEBUG', '-fprofile-arcs', '-ftest-coverage'])
  env.Append(LINKFLAGS = ['-fprofile-arcs'])
  # Append the coverage mode suffix to the executable name.
  build_suffix += '_gcov'
  build_dir = COVERAGE_OBJ_DIR
else:
  # Release mode.
  env.Append(CPPFLAGS = ['-O3'])
  build_dir = RELEASE_OBJ_DIR
  # GCC 4.8 has a bug which produces a warning saying that an anonymous Operand
  # object might be used uninitialized:
  #   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57045
  # The bug does not seem to appear in GCC 4.7, or in debug builds with GCC 4.8.
  env.Append(CPPFLAGS = ['-Wno-maybe-uninitialized'])


if target_program == 'cctest':
  env.Append(CPPPATH = [PROJ_EXAMPLES_DIR])
  env.Append(CPPFLAGS = ['-DTEST_EXAMPLES'])

# Build the library.
proj_library = env.Library('vixl' + build_suffix, list_target(build_dir, PROJ_SRC_FILES))

if target_program == 'examples':
  # Build the examples.
  env.Append(CPPPATH = [PROJ_EXAMPLES_DIR])
  for example in PROJ_EXAMPLES_SRC_FILES:
    example_name = "example-" + os.path.splitext(os.path.basename(example))[0]
    env.Program(example_name, list_target(build_dir, [example]) + proj_library)
else:
  # Build the target program.
  program_target_files = list_target(build_dir, TARGET_SRC_FILES[env['target']])
  env.Program(target_program + build_suffix, program_target_files + proj_library)