1# Copyright 2010 Google Inc. 2# All Rights Reserved. 3# 4# Author: Tim Haloun (thaloun@google.com) 5# Daniel Petersson (dape@google.com) 6# 7import os 8 9# Keep a global dictionary of library target params for lookups in 10# ExtendComponent(). 11_all_lib_targets = {} 12 13def _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 28def 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 42def 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 56def Object(env, **kwargs): 57 return ExtendComponent(env, 'ComponentObject', **kwargs) 58 59 60def 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 99def 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 128def 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 139def 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 152def 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 173def 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 187def 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 272def 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# 290def 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 310def 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. 362def Allow64BitCompile(env): 363 return (env.Bit('linux') and env.Bit('platform_arch_64bit') 364 ) 365 366def 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 378def 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 520def 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 544def 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 560def RenameKey(d, old, new, append=True): 561 AddToDict(d, new, GetEntry(d, old), append) 562