# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import shutil
import sys


p = ARGUMENTS.get('PROGRESS')
if p == 'spinner':
  Progress(['/\r', '|\r', '\\\r', '-\r'], interval=5, file=open('con', 'w'))
elif p == 'name':
  Progress('$TARGET\r', overwrite=True, file=open('con', 'w'))


default_warnings = ['no-missing-sconscript', 'no-no-parallel-support']
default_warnings = ['no-no-parallel-support']
SetOption('warn', default_warnings + GetOption('warn'))


load = ARGUMENTS.get('LOAD')
if load:
    load = load.split(',')
else:
    load = []


root_env = Environment(
    tools = ['component_setup'],
    CHROME_SRC_DIR = '$MAIN_DIR/..',
    DESTINATION_ROOT = '$MAIN_DIR/Hammer',
    TARGET_ROOT = '$DESTINATION_ROOT',

    LIBS_DIR              = '$COMPONENT_LIBRARY_DIR',

    # Disable running of tests thru scons for now.
    COMPONENT_TEST_CMDLINE = '',

    BASE_DIR              = '$OBJ_ROOT/base',
    BREAKPAD_DIR          = '$OBJ_ROOT/breakpad',
    CHROME_DIR            = '$OBJ_ROOT/chrome',
    GEARS_DIR             = '$OBJ_ROOT/gears',
    GOOGLE_UPDATE_DIR     = '$OBJ_ROOT/google_update',
    GOOGLEURL_DIR         = '$OBJ_ROOT/googleurl',
    NET_DIR               = '$OBJ_ROOT/net',
    RLZ_DIR               = '$OBJ_ROOT/rlz',
    SANDBOX_DIR           = '$OBJ_ROOT/sandbox',
    SDCH_DIR              = '$OBJ_ROOT/sdch',
    SKIA_DIR              = '$OBJ_ROOT/skia',
    TESTING_DIR           = '$OBJ_ROOT/testing',
    THIRD_PARTY_DIR       = '$OBJ_ROOT/third_party',
    V8_DIR                = '$OBJ_ROOT/v8',
    WEBKIT_DIR            = '$OBJ_ROOT/webkit',

    GTEST_DIR             = '$TESTING_DIR/gtest',

    BSDIFF_DIR            = '$THIRD_PARTY_DIR/bsdiff',
    BSPATCH_DIR           = '$THIRD_PARTY_DIR/bspatch',
    BZIP2_DIR             = '$THIRD_PARTY_DIR/bzip2',
    ICU38_DIR             = '$THIRD_PARTY_DIR/icu38',
    LIBEVENT_DIR          = '$THIRD_PARTY_DIR/libevent',
    LIBJPEG_DIR           = '$THIRD_PARTY_DIR/libjpeg',
    LIBPNG_DIR            = '$THIRD_PARTY_DIR/libpng',
    LIBXML_DIR            = '$THIRD_PARTY_DIR/libxml',
    LIBXSLT_DIR           = '$THIRD_PARTY_DIR/libxslt',
    LZMA_SDK_DIR          = '$THIRD_PARTY_DIR/lzma_sdk',
    MODP_B64_DIR          = '$THIRD_PARTY_DIR/modp_b64',
    NPAPI_DIR             = '$THIRD_PARTY_DIR/npapi',
    ZLIB_DIR              = '$THIRD_PARTY_DIR/zlib',

    THIRD_PARTY_WEBKIT_DIR = '$THIRD_PARTY_DIR/WebKit',

    PYTHON=sys.executable,

    PERL                  = 'perl',
    PERL_INCLUDE_FLAG     = '-I ',
    PERL_INCLUDE_SUFFIX   = '',
    _PERL_INCLUDE_FLAGS   = ('${_concat(PERL_INCLUDE_FLAG, '
                             'PERL_INCLUDE_PATH, '
                             'PERL_INCLUDE_SUFFIX,'
                             '__env__, RDirs, TARGET, SOURCE)}'),
)

root_env.Append(LIBPATH = ['$V8_DIR'])



def ChromeProgram(env, *args, **kw):
  return env.ComponentProgram(*args, **kw)
root_env.AddMethod(ChromeProgram)

def ChromeTestProgram(env, *args, **kw):
  return env.ComponentTestProgram(*args, **kw)
root_env.AddMethod(ChromeTestProgram)

def ChromeStaticLibrary(env, *args, **kw):
  kw['COMPONENT_STATIC'] = True
  return env.ComponentLibrary(*args, **kw)
root_env.AddMethod(ChromeStaticLibrary)

def ChromeSharedLibrary(env, *args, **kw):
  kw['COMPONENT_STATIC'] = False
  return [env.ComponentLibrary(*args, **kw)[0]]
root_env.AddMethod(ChromeSharedLibrary, "ChromeSharedLibrary")

def ChromeObject(env, *args, **kw):
  return env.ComponentObject(*args, **kw)
root_env.AddMethod(ChromeObject)


# TODO(bradnelson): pull this functionality into hammer.
# Auto select the number of processors
if root_env['PLATFORM'] in ['win32', 'cygwin']:
  cpus = int(os.environ.get('NUMBER_OF_PROCESSORS', 1))
elif root_env['PLATFORM'] in ['linux', 'linux2', 'posix']:
  # TODO(evanm): this is Linux-specific, not posix.
  # Parse /proc/cpuinfo for processor count.
  cpus = len([l for l in open('/proc/cpuinfo') if l.startswith('processor\t')])
SetOption('num_jobs', cpus + 1)


if ARGUMENTS.get('VERBOSE') in (None, '0'):
  root_env['CCCOMSTR'] = 'Compiling $TARGET ...'
  root_env['CXXCOMSTR'] = 'Compiling $TARGET ...'
  root_env['SHCCCOMSTR'] = 'Compiling $TARGET ...'
  root_env['SHCXXCOMSTR'] = 'Compiling $TARGET ...'
  root_env['ARCOMSTR'] = 'Archiving $TARGET ...'
  root_env['LINKCOMSTR'] = 'Linking $TARGET ...'
  root_env['BINDINGSCOMSTR'] = 'Building bindings in $TARGET ...'

# Use timestamps change, followed by MD5 for speed
root_env.Decider('MD5-timestamp')


# The list of all leaf (fully described) environments.
environment_list = []


# --------------------------------------------------------------------------
# Decide which things to load.
# Don't put anything platform depended here, this is just to gate things
# in or out for speed.

included = [c for c in load if not c.startswith('-')]
excluded = [c[1:] for c in load if c.startswith('-')]
if not included:
  included = ['all']

components = ['all']

def LoadComponent(c):
  components.append(c)
  return (not GetOption('help') and
          c in included or
          ('all' in included and not c in excluded))

sconscripts = []

if LoadComponent('base'):
  sconscripts.append('$BASE_DIR/base.scons')

if LoadComponent('breakpad'):
  sconscripts.append('$BREAKPAD_DIR/SConscript')

if LoadComponent('chrome'):
  sconscripts.append('$CHROME_DIR/SConscript')

if LoadComponent('gears'):
  sconscripts.append('$GEARS_DIR/SConscript')

if LoadComponent('google_update'):
  sconscripts.append('$GOOGLE_UPDATE_DIR/SConscript')

if LoadComponent('googleurl'):
  # googleurl comes from a different repository so we provide the SConscript
  # file.
  sconscripts.append('$GOOGLEURL_DIR/googleurl.scons')

if LoadComponent('net'):
  sconscripts.append('$NET_DIR/net.scons')

if LoadComponent('rlz'):
  sconscripts.append('$RLZ_DIR/SConscript')

if LoadComponent('sandbox'):
  sconscripts.append('$SANDBOX_DIR/sandbox.scons')

if LoadComponent('sdch'):
  sconscripts.append('$SDCH_DIR/SConscript')

if LoadComponent('skia'):
  sconscripts.append('$SKIA_DIR/SConscript')

if LoadComponent('testing'):
  sconscripts.append('$TESTING_DIR/SConscript.gtest')

if LoadComponent('third_party'):
  sconscripts.extend([
      '$BSDIFF_DIR/SConscript',
      '$BZIP2_DIR/bzip2.scons',
      '$ICU38_DIR/icu38.scons',
      '$LIBPNG_DIR/libpng.scons',
      '$LZMA_SDK_DIR/SConscript',
      '$MODP_B64_DIR/modp_b64.scons',
      '$ZLIB_DIR/zlib.scons',
      '$LIBJPEG_DIR/SConscript',
      '$LIBXML_DIR/SConscript',
      '$LIBXSLT_DIR/SConscript',
      '$BSPATCH_DIR/SConscript',
  ])

if LoadComponent('v8') and root_env.Dir('$CHROME_SRC_DIR/v8').exists():
  sconscripts.append('build/SConscript.v8')

if LoadComponent('webkit'):
  sconscripts.append('$WEBKIT_DIR/SConscript')


# Add the final list into the root environment to be build in BuildComponents.
root_env.Append(BUILD_SCONSCRIPTS = sconscripts)



# --------------------------------------------------------------------------
# Windows specific

windows_env = root_env.Clone()
environment_list.append(windows_env)
windows_env.Tool('target_platform_windows')
windows_env.Tool('target_debug')
windows_env.Tool('midl')
windows_env.Replace(
    BUILD_TYPE = 'debug-windows',
    BUILD_TYPE_DESCRIPTION = 'Windows debug build',
    BUILD_GROUPS = ['default'],
)

# TODO(bradnelson): this is needed for now because target_platform_windows
# has OS_WINDOWS defined in a weird way.
windows_env.FilterOut(CPPDEFINES = ['OS_WINDOWS=OS_WINDOWS'])

windows_env['PDB'] = '${TARGET.base}.pdb'


# TODO(bradnelson): this should not need to be gated on host platform.
if root_env['PLATFORM'] in ['win32', 'cygwin']:
  msvs_env = Environment(tools=['msvc', 'mslink', 'msvs'])['ENV']
  msvs_drive = msvs_env['PATH'][0]
else:
  msvs_env = {'PATH': '', 'INCLUDE': '', 'LIB': ''}
  msvs_drive = 'C'

# Use the absolute path for MSVC because it might not be on the same drive
# as our source checkout.
visual_studio_path = msvs_drive + ':/Program Files/Microsoft Visual Studio 8'

windows_env.Replace(
    CSCRIPT = 'c:\\Windows\\System32\\cscript',

    PLATFORMSDK_VISTA_REL = '../third_party/platformsdk_vista_6_0',
    PLATFORMSDK_VISTA = '$CHROME_SRC_DIR/third_party/platformsdk_vista_6_0',
    VISUAL_STUDIO = visual_studio_path,

    CYGWIN_DIR = windows_env.Dir('$CHROME_SRC_DIR/third_party/cygwin'),
    CYGWIN_BIN_DIR = '$CYGWIN_DIR/bin',

    PERL = '$CYGWIN_BIN_DIR/perl.exe',

    MSVS_ENV = msvs_env,

    YACC = '$CYGWIN_BIN_DIR/bison.exe',
)

windows_env.Append(
    ARFLAGS = [
        '/nologo',
    ],

    CCFLAGS = [
        '/nologo',

        '/Od',          # no optimization

        '/RTC1',
        '/MTd',         # static link to crt, and debug version
        '/Gy',
        '/GR-',

        '/W3',

        '/Z7',

        '/errorReport:prompt',

        '/wd4503',
        '/wd4819',
    ],

    CPPDEFINES = [
        '_CRT_SECURE_NO_DEPRECATE',
        '_CRT_NONSTDC_NO_WARNINGS',
        '_CRT_NONSTDC_NO_DEPRECATE',
        '_SCL_SECURE_NO_DEPRECATE',

        '_DEBUG',

        '_CRT_RAND_S',
        ('_WIN32_WINNT', '0x0600'),
        ('WINVER', '0x0600'),
        'WIN32',
        '_WINDOWS',
        ('_HAS_EXCEPTIONS', 0),
        'NOMINMAX',
        '_UNICODE',
        'UNICODE',

        'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
        'WIN32_LEAN_AND_MEAN',
    ],

    CPPPATH = [
        '$PLATFORMSDK_VISTA/files/Include',
        '$PLATFORMSDK_VISTA/files/VC/INCLUDE',
        '$VISUAL_STUDIO/VC/atlmfc/include',
    ],

    LIBS = [
        'advapi32.lib',
        'comdlg32.lib',
        'gdi32.lib',
        'kernel32.lib',
        'msimg32.lib',
        'odbc32.lib',
        'odbccp32.lib',
        'ole32.lib',
        'oleaut32.lib',
        'psapi.lib',
        'shell32.lib',
        'user32.lib',
        'usp10.lib',
        'uuid.lib',
        'version.lib',
        'wininet.lib',
        'winspool.lib',
        'ws2_32.lib',

        'DelayImp.lib',
    ],

    LINKFLAGS = [
        '/nologo',
        '/DEBUG',
    ],

    ICU_LIBS = ['icu'],
)

windows_env.Append(
    LIBPATH = [
        '$PLATFORMSDK_VISTA/files/Lib',
        '$PLATFORMSDK_VISTA/files/VC/LIB',
        '$VISUAL_STUDIO/VC/atlmfc/lib',
    ],
)

# TODO(sgk): remove once we upgrade to SCons 0.98.4
for var in ['INCLUDE', 'LIB', 'PATH']:
  msvs_env[var] = msvs_env[var].split('|', 1)[0]
  windows_env['ENV'][var] = windows_env['ENV'][var].split('|', 1)[0]

# Force scons to handle long include lines correctly.
pchcom_fixed = windows_env['PCHCOM']
pchcom_fixed = pchcom_fixed.replace('${TARGETS[0]}', '$TARGET')
pchcom_fixed = pchcom_fixed.replace('${TARGETS[1]}', '$TARGETS1')

windows_env.Replace(
    CCCOM = "${TEMPFILE('%s')}" % windows_env['CCCOM'],
    CXXCOM = "${TEMPFILE('%s')}" % windows_env['CXXCOM'],
    SHCCCOM = "${TEMPFILE('%s')}" % windows_env['SHCCCOM'],
    SHCXXCOM = "${TEMPFILE('%s')}" % windows_env['SHCXXCOM'],
    PCHCOM = "${TEMPFILE('%s')}" % pchcom_fixed,
    TARGETS1 = '${TARGETS[1]}',
)

windows_env['ENV']['PROGRAMFILES'] = os.environ.get('PROGRAMFILES', '')
windows_env['ENV']['SystemDrive'] = os.environ.get('SystemDrive', '')
windows_env['ENV']['USERPROFILE'] = os.environ.get('USERPROFILE', '')

windows_env.AppendENVPath('PATH', ';C:\\WINDOWS\\system32')


# --------------------------------------------------------------------------
# Linux specific

linux_env = root_env.Clone()
environment_list.append(linux_env)
linux_env.Tool('target_platform_linux')
linux_env.Tool('target_debug')
linux_env.Tool('yacc')
linux_env.Replace(
    BUILD_TYPE = 'debug-linux',
    BUILD_TYPE_DESCRIPTION = 'Linux debug build',
    BUILD_GROUPS = ['default'],
)


# TODO(bradnelson): this is needed for now because target_platform_linux has
# OS_LINUX defined in a weird way.
linux_env.FilterOut(CPPDEFINES = ['OS_LINUX=OS_LINUX'])

# Copy some environment variables from the outer environment to the
# SCons.Environment if they exist.
for envvar in ('CC', 'CXX'):
  if envvar in os.environ:
    linux_env[envvar] = os.environ[envvar]
# Copy these environment variables from the outer environment to the
# environment that the build commands run in.
# $HOME is needed by distcc so it can find its lock file.
for envvar in ('HOME', 'DISTCC_HOSTS', 'CCACHE_DIR'):
  if envvar in os.environ:
    linux_env['ENV'][envvar] = os.environ[envvar]

excluded_warnings = [
  # TODO: Clean up uses of ext/hash_map and remove this.
  # (see unordered_map and base/hash_tables.h)
  '-Wno-deprecated',  # Needed for using ext/hash_map on GCC 4.3
  '-Wno-unknown-pragmas',  # In wtf's ref counting system
]
linux_env.Append(
    BUILD_SCONSCRIPTS = [
        '$LIBEVENT_DIR/libevent.scons',
    ],
    CCFLAGS = ['-m32', '-g', '-pthread'],
    CXXFLAGS = ['-Wall', '-Werror'] + excluded_warnings,
    LINKFLAGS = ['-m32', '-pthread'],
)

linux_env.Replace(
    # We have several cases where archives depend on each other in a cyclic
    # fashion. (V8Bindings, libport and WebCore being the most significant
    # example.) Since the GNU linker does only a single pass over the archives
    # we need some extra trickery to deal with these unavoidable cycles. That
    # trickery is --start-group and --end-group (aka -( and -) ). That causes ld
    # to loop over the group until no more undefined symbols are found. In an
    # ideal world we would only make groups from those libraries which we knew
    # to be in cycles. However, that's tough with SCons, so we bodge it by
    # making all the archives a group by redefining the linking command here.
    SHLINKCOM = ('$SHLINK -shared -o $TARGET $SHLINKFLAGS $SOURCES '
                 '$_LIBDIRFLAGS '
                 '-Wl,--start-group $_LIBFLAGS -Wl,--end-group'),
    LINKCOM = ('$LINK -o $TARGET $LINKFLAGS $SOURCES '
               '$_LIBDIRFLAGS '
               '-Wl,--start-group $_LIBFLAGS -Wl,--end-group'),
)

linux_env.Replace(
    PERL = '/usr/bin/perl',
    PERL_INCLUDE_FLAG = '-I ',
    PERL_INCLUDE_SUFFIX = '',
    _PERL_INCLUDE_FLAGS = ('${_concat(PERL_INCLUDE_FLAG, '
                           'PERL_INCLUDE_PATH, '
                           'PERL_INCLUDE_SUFFIX,'
                           '__env__, RDirs, TARGET, SOURCE)}'),
)

linux_env.FilterOut(
    BUILD_SCONSCRIPTS = [
        '$BSPATCH_DIR/SConscript',
        '$BSDIFF_DIR/SConscript',
        '$GEARS_DIR/SConscript',
        '$GOOGLE_UPDATE_DIR/SConscript',
        '$RLZ_DIR/SConscript',
        '$SANDBOX_DIR/sandbox.scons',
    ],
)

linux_env.Append(
    # We need rt for clock_gettime.
    LIBS = ['rt'],

    ICU_LIBS = ['icu'],
)
# Build with support for gcov when COVERAGE=1.
if ARGUMENTS.get('COVERAGE') == '1':
  linux_env.Append(CCFLAGS=['-fprofile-arcs', '-ftest-coverage'])
  linux_env.Append(LINKFLAGS=['-fprofile-arcs'])

# Build with system-provided NSS and GTK.
if root_env['PLATFORM'] in ['linux', 'linux2', 'posix']:
  linux_env.ParseConfig('pkg-config --cflags --libs nss')
  linux_env.ParseConfig('pkg-config --cflags --libs gtk+-2.0')


# --------------------------------------------------------------------------
# Mac specific

mac_env = root_env.Clone()
environment_list.append(mac_env)
mac_env.Tool('target_platform_mac')
mac_env.Tool('target_debug')
mac_env.Replace(
    BUILD_TYPE = 'debug-mac',
    BUILD_TYPE_DESCRIPTION = 'Mac debug build',
    BUILD_GROUPS = ['default'],
)

mac_env.Replace(
    # Reproduce XCode's behavior of using gcc even to link C++,
    # and distinguishing it the -x c++ option.
    CC = 'gcc-4.2',
    CXX = 'g++-4.2',
    LINK = '$CXX',
)

mac_env.FilterOut(
    BUILD_SCONSCRIPTS = [
        '$BSPATCH_DIR/SConscript',
        '$BSDIFF_DIR/SConscript',
        '$LIBJPEG_DIR/SConscript',
        '$LIBXML_DIR/SConscript',
        '$LIBXSLT_DIR/SConscript',
        '$BREAKPAD_DIR/SConscript',
        '$CHROME_DIR/SConscript',
        '$GEARS_DIR/SConscript',
        '$GOOGLE_UPDATE_DIR/SConscript',
        '$RLZ_DIR/SConscript',
        '$SANDBOX_DIR/sandbox.scons',
        'build/SConscript.v8',
        '$WEBKIT_DIR/SConscript',
    ],
)

mac_env.Append(
    BUILD_SCONSCRIPTS = [
        '$LIBEVENT_DIR/libevent.scons',
    ],
    CFLAGS = [
        '-std=c99',
    ],
    CXXFLAGS = [
        '-fvisibility-inlines-hidden',
        '${str(SOURCE).endswith(".mm") and "-fobjc-gc" or ""}',
    ],
    CCFLAGS = [
        '-fmessage-length=0',
        '-pipe',
        '-O0',
        '-mdynamic-no-pic',
        '-Werror',
        '-Wnewline-eof',
        '-fvisibility=hidden',
        '-gdwarf-2',
        '-Wall',
        '-Wendif-labels',
        '-fstack-protector',
        '-fstack-protector-all',
    ],
    CPPDEFINES = [
        'DEBUG',
    ],

    FRAMEWORKPATH = [
        mac_env.Dir('${TARGET_ROOT}'),
        '/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks',
    ],
    FRAMEWORKS = [
        'AppKit',
        'ApplicationServices',
        'Foundation',
    ],

    ICU_LIBS = ['icui18n', 'icuuc', 'icudata'],
)


# Add --clobber (for the buildbot).
# NOTE: seems to be crucial to do this before any builders are invoked.
AddOption('--clobber', action='store_true', dest='clobber', default=False,
          help='Delete build directory before building.')
if GetOption('clobber'):
  shutil.rmtree(root_env.Dir('$DESTINATION_ROOT').abspath, True)
  # sconsign file gets put here at the moment.
  shutil.rmtree(root_env.Dir('$MAIN_DIR/scons-out').abspath, True)



# -------------------------------------------------------------------------


# Overlay things from a layer below.
for env in environment_list:
  env.Dir('$OBJ_ROOT').addRepository(env.Dir('$CHROME_SRC_DIR'))
  env.Dir('$OBJ_ROOT/googleurl').addRepository(env.Dir('$CHROME_SRC_DIR/build'))

  # We pre-resolve some common targets.  We end up spending lots of time
  # resolving these over and over again.
  env.Replace(
      CHROME_SRC_DIR        = str(env.Dir('$CHROME_SRC_DIR')),
      DESTINATION_ROOT      = str(env.Dir('$DESTINATION_ROOT')),
      TARGET_ROOT           = str(env.Dir('$TARGET_ROOT')),
      OBJ_ROOT              = str(env.Dir('$OBJ_ROOT')),
      WEBKIT_DIR            = str(env.Dir('$WEBKIT_DIR')),
  )


help_fmt = """
Usage: hammer [SCONS_OPTIONS] [VARIABLES] [TARGET] ...

Supported build variables:
  LOAD=[module,...]         Comma-separated list of components to load in the
                              dependency graph ('-' prefix excludes):
%s
  PROGRESS=type             Display a progress indicator:
                              name:  print each evaluated target name
                              spinner:  print a spinner every 5 targets
  VERBOSE=1                 Display full command lines
"""

if GetOption('help'):
  import textwrap
  tw = textwrap.TextWrapper(
    width = 78,
    initial_indent = ' '*32,
    subsequent_indent = ' '*32,
  )
  components = tw.fill(', '.join(components))

  Help(help_fmt % components)


Import('build_component')
Default(Alias(build_component))

# -------------------------------------------------------------------------

# Invoke all the SConscripts in each of the environments that make sense on
# this host-platform.
BuildComponents(environment_list)

# -------------------------------------------------------------------------