# Copyright 2008, Google Inc.
# 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 Google Inc. 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 AND 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 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'))


SetOption('warn', ['no-missing-sconscript'] + GetOption('warn'))


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


env = Environment(
    BUILD_TYPE = ARGUMENTS.get('BUILD_TYPE', 'Hammer'),
    TARGET_ROOT = '#/$BUILD_TYPE',
    OBJ_ROOT = '$TARGET_ROOT',

    LIBS_DIR              = '#/$BUILD_TYPE/Libs',

    BASE_DIR              = '#/$BUILD_TYPE/base',
    BREAKPAD_DIR          = '#/$BUILD_TYPE/breakpad',
    CHROME_DIR            = '#/$BUILD_TYPE/chrome',
    GEARS_DIR             = '#/$BUILD_TYPE/gears',
    GOOGLE_UPDATE_DIR     = '#/$BUILD_TYPE/google_update',

    # Work around a limitation (bug?) in SCons in that, when we build
    # from a different directory, it forces the build targets defined
    # the SConscript.googleurl file to be relative to that file (i.e.,
    # here in the build/ directory with us), not relative to the
    # the SConstruct directory from which SCons was launched.  When
    # we roll forward to a version of SCons that fixes this, we'll
    # need to revert to the $BUILD_TYPE definition of GOOGLEURL_DIR.
    #GOOGLEURL_DIR         = '#/$BUILD_TYPE/googleurl',
    GOOGLEURL_DIR         = '#/../build/googleurl',

    NET_DIR               = '#/$BUILD_TYPE/net',
    RLZ_DIR               = '#/$BUILD_TYPE/rlz',
    SANDBOX_DIR           = '#/$BUILD_TYPE/sandbox',
    SKIA_DIR              = '#/$BUILD_TYPE/skia',
    TESTING_DIR           = '#/$BUILD_TYPE/testing',
    THIRD_PARTY_DIR       = '#/$BUILD_TYPE/third_party',
    V8_DIR                = '#/$BUILD_TYPE/v8',
    WEBKIT_DIR            = '#/$BUILD_TYPE/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',
    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,

    LIBPATH               = ['$LIBS_DIR'],
)

def AddPdbToTarget(args):
    """Add the windows pdb file to the build target.

    Arguments:
      args is a tuple passed to ChromeProgram or ChromeTestProgram
    Returns:
      A tuple to pass on to Environment.Program."""
    # Only add .pdb to the target if the target was only a string. We can't
    # blindly add foo.pdb because chrome.exe and chrome.dll use chrome_exe.pdb
    # and chrome_dll.pdb.
    if not isinstance(args[0], str):
        return args

    mutable_args = list(args)
    mutable_args[0] = [args[0], args[0] + '.pdb']
    return tuple(mutable_args)

def ChromeProgram(env, *args, **kw):
    if env['PLATFORM'] == 'win32':
        # TODO(tc): We should handle kw['target'] too.
        args = AddPdbToTarget(args)
    return env.Program(*args, **kw)
env.AddMethod(ChromeProgram, "ChromeProgram")

def ChromeTestProgram(env, *args, **kw):
    if env['PLATFORM'] == 'win32':
        # TODO(tc): We should handle kw['target'] too.
        args = AddPdbToTarget(args)
    return env.Program(*args, **kw)
env.AddMethod(ChromeTestProgram, "ChromeTestProgram")

def ChromeStaticLibrary(env, *args, **kw):
    result = env.StaticLibrary(*args, **kw)
    if env['PLATFORM'] == 'win32':
      # TODO(sgk):
      # We'd like to do this with env.Install() like we do on other systems,
      # but this causes problems on Windows when the Python copy of the file
      # in one thread prevents a linker spawned by another thread from
      # opening the copied .lib, despite the fact that the copy has
      # successfully concluded before the spawn occurs.  Work around the
      # underlying problem (whatever it is) by calling the external Windows
      # xcopy utility.
      env.Command('$LIBS_DIR/${RESULT.name}', '$RESULT',
                  "xcopy /q /y $SOURCE ${TARGET.dir}",
                  RESULT=result[0])
    else:
      env.Install('$LIBS_DIR', result)
    return result
env.AddMethod(ChromeStaticLibrary, "ChromeStaticLibrary")

def ChromeSharedLibrary(env, *args, **kw):
    return env.SharedLibrary(*args, **kw)
env.AddMethod(ChromeSharedLibrary, "ChromeSharedLibrary")


if env['PLATFORM'] == 'win32':

  processors = int(os.environ.get('NUMBER_OF_PROCESSORS', 1))
  SetOption('num_jobs', processors + 1)

  msvs_env = Environment(tools=['msvc', 'mslink', 'msvs'])['ENV']

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

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

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

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

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

    MSVS_ENV = msvs_env,

    YACC = '$CYGWIN_BIN_DIR/bison.exe',

    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',
    ],
  )

  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]
    env['ENV'][var] = env['ENV'][var].split('|', 1)[0]

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

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

  env['ENV']['PROGRAMFILES'] = os.environ['PROGRAMFILES']
  env['ENV']['SystemDrive'] = os.environ['SystemDrive']
  env['ENV']['USERPROFILE'] = os.environ['USERPROFILE']

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

elif env['PLATFORM'] == 'posix':

  # Copy some environment variables from the outer environment if they exist.
  for envvar in ['CC', 'CXX']:
    if envvar in os.environ:
      env[envvar] = os.environ[envvar]
  # Provide $HOME when compiling so distcc can find its lock file.
  env['ENV']['HOME'] = os.environ['HOME']

  # 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)

  # For now, linux only loads the components we know work on Linux, by default.
  load = ['base', 'testing', 'third_party', 'breakpad', 'skia']

  env.Replace(
      # TODO(linux): we should also compile with -Werror, but not yet.
      CCFLAGS = ['-m32', '-g'],
      LINKFLAGS = ['-m32'],
      # We need pthread for threading, and rt for clock_gettime.
      LIBS = ['pthread', 'rt'],
  )

else:

  print "Unsupported SCons $PLATFORM value %s" % repr(env['PLATFORM'])
  Exit(1)


if ARGUMENTS.get('VERBOSE') in (None, '0'):
  env['CCCOMSTR'] = 'Compiling $TARGET ...'
  env['CXXCOMSTR'] = 'Compiling $TARGET ...'
  env['ARCOMSTR'] = 'Archiving $TARGET ...'
  env['LINKCOMSTR'] = 'Linking $TARGET ...'


# Place the .sconsign.dblite in the build directory.
target_dir = env.Dir('$TARGET_ROOT')
if not os.path.exists(target_dir.abspath):
  Execute(Mkdir(target_dir))
SConsignFile(target_dir.File('.sconsign').abspath)


# 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(env.Dir('$TARGET_ROOT').abspath, True)

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


# Overlay things from a layer below.
env.Dir('$TARGET_ROOT').addRepository(Dir('..'))


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/SConscript')

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

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

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

if LoadComponent('googleurl'):
  env.SConscript('SConscript.googleurl',
                 duplicate=0,
                 variant_dir='$GOOGLEURL_DIR',
                 src_dir='../googleurl',
                 exports=['env'])

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

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

if LoadComponent('sandbox'):
  sconscripts.append('$SANDBOX_DIR/src/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',
      '$BSPATCH_DIR/SConscript',
      '$BZIP2_DIR/SConscript',
      '$ICU38_DIR/SConscript',
      '$LIBJPEG_DIR/SConscript',
      '$LIBPNG_DIR/SConscript',
      '$LIBXML_DIR/SConscript',
      '$LIBXSLT_DIR/SConscript',
      '$LZMA_SDK_DIR/SConscript',
      '$MODP_B64_DIR/SConscript',
      '$ZLIB_DIR/SConscript',
  ])

if LoadComponent('v8') and env.Dir('#/../v8').exists():
  env.SConscript('SConscript.v8',
                 exports=['env'])

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

env.SConscript(sconscripts, exports=['env'])


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

Supported build variables:

  BUILD_TYPE=type           Build type.  Also used as the subdirectory name
                              in which the build occurs.
  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(build_component)