Home | History | Annotate | Download | only in site_scons
      1 # Copyright 2010 Google Inc.
      2 # All Rights Reserved.
      3 #
      4 # Author: Tim Haloun (thaloun (at] google.com)
      5 #         Daniel Petersson (dape (at] google.com)
      6 #
      7 import os
      8 
      9 # Keep a global dictionary of library target params for lookups in
     10 # ExtendComponent().
     11 _all_lib_targets = {}
     12 
     13 def _GenericLibrary(env, static, **kwargs):
     14   """Extends ComponentLibrary to support multiplatform builds
     15      of dynamic or static libraries.
     16 
     17   Args:
     18     env: The environment object.
     19     kwargs: The keyword arguments.
     20 
     21   Returns:
     22     See swtoolkit ComponentLibrary
     23   """
     24   params = CombineDicts(kwargs, {'COMPONENT_STATIC': static})
     25   return ExtendComponent(env, 'ComponentLibrary', **params)
     26 
     27 
     28 def Library(env, **kwargs):
     29   """Extends ComponentLibrary to support multiplatform builds of static
     30      libraries.
     31 
     32   Args:
     33     env: The current environment.
     34     kwargs: The keyword arguments.
     35 
     36   Returns:
     37     See swtoolkit ComponentLibrary
     38   """
     39   return _GenericLibrary(env, True, **kwargs)
     40 
     41 
     42 def DynamicLibrary(env, **kwargs):
     43   """Extends ComponentLibrary to support multiplatform builds
     44      of dynmic libraries.
     45 
     46   Args:
     47     env: The environment object.
     48     kwargs: The keyword arguments.
     49 
     50   Returns:
     51     See swtoolkit ComponentLibrary
     52   """
     53   return _GenericLibrary(env, False, **kwargs)
     54 
     55 
     56 def Object(env, **kwargs):
     57   return ExtendComponent(env, 'ComponentObject', **kwargs)
     58 
     59 
     60 def Unittest(env, **kwargs):
     61   """Extends ComponentTestProgram to support unittest built
     62      for multiple platforms.
     63 
     64   Args:
     65     env: The current environment.
     66     kwargs: The keyword arguments.
     67 
     68   Returns:
     69     See swtoolkit ComponentProgram.
     70   """
     71   kwargs['name'] = kwargs['name'] + '_unittest'
     72 
     73   common_test_params = {
     74     'posix_cppdefines': ['GUNIT_NO_GOOGLE3', 'GTEST_HAS_RTTI=0'],
     75     'libs': ['unittest_main', 'gunit']
     76   }
     77   if not kwargs.has_key('explicit_libs'):
     78     common_test_params['win_libs'] = [
     79       'advapi32',
     80       'crypt32',
     81       'iphlpapi',
     82       'secur32',
     83       'shell32',
     84       'shlwapi',
     85       'user32',
     86       'wininet',
     87       'ws2_32'
     88     ]
     89     common_test_params['lin_libs'] = [
     90       'crypto',
     91       'pthread',
     92       'ssl',
     93     ]
     94 
     95   params = CombineDicts(kwargs, common_test_params)
     96   return ExtendComponent(env, 'ComponentTestProgram', **params)
     97 
     98 
     99 def App(env, **kwargs):
    100   """Extends ComponentProgram to support executables with platform specific
    101      options.
    102 
    103   Args:
    104     env: The current environment.
    105     kwargs: The keyword arguments.
    106 
    107   Returns:
    108     See swtoolkit ComponentProgram.
    109   """
    110   if not kwargs.has_key('explicit_libs'):
    111     common_app_params = {
    112       'win_libs': [
    113         'advapi32',
    114         'crypt32',
    115         'iphlpapi',
    116         'secur32',
    117         'shell32',
    118         'shlwapi',
    119         'user32',
    120         'wininet',
    121         'ws2_32'
    122       ]}
    123     params = CombineDicts(kwargs, common_app_params)
    124   else:
    125     params = kwargs
    126   return ExtendComponent(env, 'ComponentProgram', **params)
    127 
    128 def WiX(env, **kwargs):
    129   """ Extends the WiX builder
    130   Args:
    131     env: The current environment.
    132     kwargs: The keyword arguments.
    133 
    134   Returns:
    135     The node produced by the environment's wix builder
    136   """
    137   return ExtendComponent(env, 'WiX', **kwargs)
    138 
    139 def Repository(env, at, path):
    140   """Maps a directory external to $MAIN_DIR to the given path so that sources
    141      compiled from it end up in the correct place under $OBJ_DIR.  NOT required
    142      when only referring to header files.
    143 
    144   Args:
    145     env: The current environment object.
    146     at: The 'mount point' within the current directory.
    147     path: Path to the actual directory.
    148   """
    149   env.Dir(at).addRepository(env.Dir(path))
    150 
    151 
    152 def Components(*paths):
    153   """Completes the directory paths with the correct file
    154      names such that the directory/directory.scons name
    155      convention can be used.
    156 
    157   Args:
    158     paths: The paths to complete. If it refers to an existing
    159            file then it is ignored.
    160 
    161   Returns:
    162     The completed lif scons files that are needed to build talk.
    163   """
    164   files = []
    165   for path in paths:
    166     if os.path.isfile(path):
    167       files.append(path)
    168     else:
    169       files.append(ExpandSconsPath(path))
    170   return files
    171 
    172 
    173 def ExpandSconsPath(path):
    174   """Expands a directory path into the path to the
    175      scons file that our build uses.
    176      Ex: magiflute/plugin/common => magicflute/plugin/common/common.scons
    177 
    178   Args:
    179     path: The directory path to expand.
    180 
    181   Returns:
    182     The expanded path.
    183   """
    184   return '%s/%s.scons' % (path, os.path.basename(path))
    185 
    186 
    187 def AddMediaLibs(env, **kwargs):
    188   lmi_libdir = '$GOOGLE3/../googleclient/third_party/lmi/files/lib/'
    189   if env.Bit('windows'):
    190     if env.get('COVERAGE_ENABLED'):
    191       lmi_libdir += 'win32/c_only'
    192     else:
    193       lmi_libdir += 'win32/Release'
    194   elif env.Bit('mac'):
    195     lmi_libdir += 'macos'
    196   elif env.Bit('linux'):
    197       lmi_libdir += 'linux/x86'
    198 
    199 
    200   AddToDict(kwargs, 'libdirs', [
    201     '$MAIN_DIR/third_party/gips/Libraries/',
    202     lmi_libdir,
    203   ])
    204 
    205   gips_lib = ''
    206   if env.Bit('windows'):
    207     if env.Bit('debug'):
    208       gips_lib = 'gipsvoiceenginelib_mtd'
    209     else:
    210       gips_lib = 'gipsvoiceenginelib_mt'
    211   elif env.Bit('mac'):
    212     gips_lib = 'VoiceEngine_mac_universal_gcc'
    213   elif env.Bit('linux'):
    214       gips_lib = 'VoiceEngine_Linux_gcc'
    215 
    216 
    217   AddToDict(kwargs, 'libs', [
    218     gips_lib,
    219     'LmiAudioCommon',
    220     'LmiClient',
    221     'LmiCmcp',
    222     'LmiDeviceManager',
    223     'LmiH263ClientPlugIn',
    224     'LmiH263CodecCommon',
    225     'LmiH263Decoder',
    226     'LmiH263Encoder',
    227     'LmiH264ClientPlugIn',
    228     'LmiH264CodecCommon',
    229     'LmiH264Common',
    230     'LmiH264Decoder',
    231     'LmiH264Encoder',
    232     'LmiIce',
    233     'LmiMediaPayload',
    234     'LmiOs',
    235     'LmiPacketCache',
    236     'LmiProtocolStack',
    237     'LmiRateShaper',
    238     'LmiRtp',
    239     'LmiSecurity',
    240     'LmiSignaling',
    241     'LmiStun',
    242     'LmiTransport',
    243     'LmiUi',
    244     'LmiUtils',
    245     'LmiVideoCommon',
    246     'LmiXml',
    247   ])
    248 
    249   if env.Bit('windows'):
    250     AddToDict(kwargs, 'libs', [
    251       'dsound',
    252       'd3d9',
    253       'gdi32',
    254       'strmiids',
    255     ])
    256 
    257   if env.Bit('mac'):
    258     AddToDict(kwargs, 'FRAMEWORKS', [
    259       'AudioToolbox',
    260       'AudioUnit',
    261       'Cocoa',
    262       'CoreAudio',
    263       'CoreFoundation',
    264       'IOKit',
    265       'QTKit',
    266       'QuickTime',
    267       'QuartzCore',
    268     ])
    269   return kwargs
    270 
    271 
    272 def ReadVersion(filename):
    273   """Executes the supplied file and pulls out a version definition from it. """
    274   defs = {}
    275   execfile(str(filename), defs)
    276   if not defs.has_key('version'):
    277     return '0.0.0.0'
    278   version = defs['version']
    279   parts = version.split(',')
    280   build = os.environ.get('GOOGLE_VERSION_BUILDNUMBER')
    281   if build:
    282     parts[-1] = str(build)
    283   return '.'.join(parts)
    284 
    285 
    286 #-------------------------------------------------------------------------------
    287 # Helper methods for translating talk.Foo() declarations in to manipulations of
    288 # environmuent construction variables, including parameter parsing and merging,
    289 #
    290 def GetEntry(dict, key):
    291   """Get the value from a dictionary by key. If the key
    292      isn't in the dictionary then None is returned. If it is in
    293      the dictionaruy the value is fetched and then is it removed
    294      from the dictionary.
    295 
    296   Args:
    297     key: The key to get the value for.
    298     kwargs: The keyword argument dictionary.
    299   Returns:
    300     The value or None if the key is missing.
    301   """
    302   value = None
    303   if dict.has_key(key):
    304     value = dict[key]
    305     dict.pop(key)
    306 
    307   return value
    308 
    309 
    310 def MergeAndFilterByPlatform(env, params):
    311   """Take a dictionary of arguments to lists of values, and, depending on
    312      which platform we are targetting, merge the lists of associated keys.
    313      Merge by combining value lists like so:
    314        {win_foo = [a,b], lin_foo = [c,d], foo = [e], mac_bar = [f], bar = [g] }
    315        becomes {foo = [a,b,e], bar = [g]} on windows, and
    316        {foo = [e], bar = [f,g]} on mac
    317 
    318   Args:
    319     env: The hammer environment which knows which platforms are active
    320     params: The keyword argument dictionary.
    321   Returns:
    322     A new dictionary with the filtered and combined entries of params
    323   """
    324   platforms = {
    325     'linux': 'lin_',
    326     'mac': 'mac_',
    327     'posix': 'posix_',
    328     'windows': 'win_',
    329   }
    330   active_prefixes = [
    331     platforms[x] for x in iter(platforms) if env.Bit(x)
    332   ]
    333   inactive_prefixes = [
    334     platforms[x] for x in iter(platforms) if not env.Bit(x)
    335   ]
    336 
    337   merged = {}
    338   for arg, values in params.iteritems():
    339     inactive_platform = False
    340 
    341     key = arg
    342 
    343     for prefix in active_prefixes:
    344       if arg.startswith(prefix):
    345         key = arg[len(prefix):]
    346 
    347     for prefix in inactive_prefixes:
    348       if arg.startswith(prefix):
    349         inactive_platform = True
    350 
    351     if inactive_platform:
    352       continue
    353 
    354     AddToDict(merged, key, values)
    355 
    356   return merged
    357 
    358 # Linux can build both 32 and 64 bit on 64 bit host, but 32 bit host can
    359 # only build 32 bit.  For 32 bit debian installer a 32 bit host is required.
    360 # ChromeOS (linux) ebuild don't support 64 bit and requires 32 bit build only
    361 # for now.
    362 def Allow64BitCompile(env):
    363   return (env.Bit('linux') and env.Bit('platform_arch_64bit')
    364           )
    365 
    366 def MergeSettingsFromLibraryDependencies(env, params):
    367   if params.has_key('libs'):
    368     for lib in params['libs']:
    369       if (_all_lib_targets.has_key(lib) and
    370           _all_lib_targets[lib].has_key('dependent_target_settings')):
    371         params = CombineDicts(
    372             params,
    373             MergeAndFilterByPlatform(
    374                 env,
    375                 _all_lib_targets[lib]['dependent_target_settings']))
    376   return params
    377 
    378 def ExtendComponent(env, component, **kwargs):
    379   """A wrapper around a scons builder function that preprocesses and post-
    380      processes its inputs and outputs.  For example, it merges and filters
    381      certain keyword arguments before appending them to the environments
    382      construction variables.  It can build signed targets and 64bit copies
    383      of targets as well.
    384 
    385   Args:
    386     env: The hammer environment with which to build the target
    387     component: The environment's builder function, e.g. ComponentProgram
    388     kwargs: keyword arguments that are either merged, translated, and passed on
    389             to the call to component, or which control execution.
    390             TODO(): Document the fields, such as cppdefines->CPPDEFINES,
    391             prepend_includedirs, include_talk_media_libs, etc.
    392   Returns:
    393     The output node returned by the call to component, or a subsequent signed
    394     dependant node.
    395   """
    396   env = env.Clone()
    397 
    398   # prune parameters intended for other platforms, then merge
    399   params = MergeAndFilterByPlatform(env, kwargs)
    400 
    401   # get the 'target' field
    402   name = GetEntry(params, 'name')
    403 
    404   # save pristine params of lib targets for future reference
    405   if 'ComponentLibrary' == component:
    406     _all_lib_targets[name] = dict(params)
    407 
    408   # add any dependent target settings from library dependencies
    409   params = MergeSettingsFromLibraryDependencies(env, params)
    410 
    411   # if this is a signed binary we need to make an unsigned version first
    412   signed = env.Bit('windows') and GetEntry(params, 'signed')
    413   if signed:
    414     name = 'unsigned_' + name
    415 
    416   # add default values
    417   if GetEntry(params, 'include_talk_media_libs'):
    418     params = AddMediaLibs(env, **params)
    419 
    420   # potentially exit now
    421   srcs = GetEntry(params, 'srcs')
    422   if not srcs or not hasattr(env, component):
    423     return None
    424 
    425   # apply any explicit dependencies
    426   dependencies = GetEntry(params, 'depends')
    427   if dependencies is not None:
    428     env.Depends(name, dependencies)
    429 
    430   # put the contents of params into the environment
    431   # some entries are renamed then appended, others renamed then prepended
    432   appends = {
    433     'cppdefines' : 'CPPDEFINES',
    434     'libdirs' : 'LIBPATH',
    435     'link_flags' : 'LINKFLAGS',
    436     'libs' : 'LIBS',
    437     'FRAMEWORKS' : 'FRAMEWORKS',
    438   }
    439   prepends = {}
    440   if env.Bit('windows'):
    441     # MSVC compile flags have precedence at the beginning ...
    442     prepends['ccflags'] = 'CCFLAGS'
    443   else:
    444     # ... while GCC compile flags have precedence at the end
    445     appends['ccflags'] = 'CCFLAGS'
    446   if GetEntry(params, 'prepend_includedirs'):
    447     prepends['includedirs'] = 'CPPPATH'
    448   else:
    449     appends['includedirs'] = 'CPPPATH'
    450 
    451   for field, var in appends.items():
    452     values = GetEntry(params, field)
    453     if values is not None:
    454       env.Append(**{var : values})
    455   for field, var in prepends.items():
    456     values = GetEntry(params, field)
    457     if values is not None:
    458       env.Prepend(**{var : values})
    459 
    460   # workaround for pulse stripping link flag for unknown reason
    461   if Allow64BitCompile(env):
    462     env['SHLINKCOM'] = ('$SHLINK -o $TARGET -m32 $SHLINKFLAGS $SOURCES '
    463                         '$_LIBDIRFLAGS $_LIBFLAGS')
    464     env['LINKCOM'] = ('$LINK -o $TARGET -m32 $LINKFLAGS $SOURCES '
    465                       '$_LIBDIRFLAGS $_LIBFLAGS')
    466 
    467   # any other parameters are replaced without renaming
    468   for field, value in params.items():
    469     env.Replace(**{field : value})
    470 
    471   # invoke the builder function
    472   builder = getattr(env, component)
    473 
    474   node = builder(name, srcs)
    475 
    476   # make a parallel 64bit version if requested
    477   if Allow64BitCompile(env) and GetEntry(params, 'also64bit'):
    478     env_64bit = env.Clone()
    479     env_64bit.FilterOut(CCFLAGS = ['-m32'], LINKFLAGS = ['-m32'])
    480     env_64bit.Prepend(CCFLAGS = ['-m64', '-fPIC'], LINKFLAGS = ['-m64'])
    481     name_64bit = name + '64'
    482     env_64bit.Replace(OBJSUFFIX = '64' + env_64bit['OBJSUFFIX'])
    483     env_64bit.Replace(SHOBJSUFFIX = '64' + env_64bit['SHOBJSUFFIX'])
    484     if ('ComponentProgram' == component or
    485         ('ComponentLibrary' == component and
    486          env_64bit['COMPONENT_STATIC'] == False)):
    487       # link 64 bit versions of libraries
    488       libs = []
    489       for lib in env_64bit['LIBS']:
    490         if (_all_lib_targets.has_key(lib) and
    491             _all_lib_targets[lib].has_key('also64bit')):
    492           libs.append(lib + '64')
    493         else:
    494           libs.append(lib)
    495       env_64bit.Replace(LIBS = libs)
    496 
    497     env_64bit['SHLINKCOM'] = ('$SHLINK -o $TARGET -m64 $SHLINKFLAGS $SOURCES '
    498                               '$_LIBDIRFLAGS $_LIBFLAGS')
    499     env_64bit['LINKCOM'] = ('$LINK -o $TARGET -m64 $LINKFLAGS $SOURCES '
    500                             '$_LIBDIRFLAGS $_LIBFLAGS')
    501     builder = getattr(env_64bit, component)
    502     nodes = [node, builder(name_64bit, srcs)]
    503     return nodes
    504 
    505   if signed:  # Note currently incompatible with 64Bit flag
    506     # Get the name of the built binary, then get the name of the final signed
    507     # version from it.  We need the output path since we don't know the file
    508     # extension beforehand.
    509     target = node[0].path.split('_', 1)[1]
    510     signed_node = env.SignedBinary(
    511       source = node,
    512       target = '$STAGING_DIR/' + target,
    513     )
    514     env.Alias('signed_binaries', signed_node)
    515     return signed_node
    516 
    517   return node
    518 
    519 
    520 def AddToDict(dictionary, key, values, append=True):
    521   """Merge the given key value(s) pair into a dictionary.  If it contains an
    522      entry with that key already, then combine by appending or prepending the
    523      values as directed.  Otherwise, assign a new keyvalue pair.
    524   """
    525   if values is None:
    526     return
    527 
    528   if not dictionary.has_key(key):
    529     dictionary[key] = values
    530     return
    531 
    532   cur = dictionary[key]
    533   # TODO: Make sure that there are no duplicates
    534   # in the list. I can't use python set for this since
    535   # the nodes that are returned by the SCONS builders
    536   # are not hashable.
    537   # dictionary[key] = list(set(cur).union(set(values)))
    538   if append:
    539     dictionary[key] = cur + values
    540   else:
    541     dictionary[key] = values + cur
    542 
    543 
    544 def CombineDicts(a, b):
    545   """Unions two dictionaries by combining values of keys shared between them.
    546   """
    547   c = {}
    548   for key in a:
    549     if b.has_key(key):
    550       c[key] = a[key] + b.pop(key)
    551     else:
    552       c[key] = a[key]
    553 
    554   for key in b:
    555     c[key] = b[key]
    556 
    557   return c
    558 
    559 
    560 def RenameKey(d, old, new, append=True):
    561   AddToDict(d, new, GetEntry(d, old), append)
    562