1
# Copyright 2010 Google Inc.
4
# Author: Tim Haloun (thaloun@google.com)
5
# Daniel Petersson (dape@google.com)
11
"""Records information on the libraries defined in a build configuration.
14
lib_targets: Dictionary of library target params for lookups in
16
prebuilt_libraries: Set of all prebuilt static libraries.
17
system_libraries: Set of libraries not found in the above (used to detect
18
out-of-order build rules).
21
# Dictionary of LibraryInfo objects keyed by BUILD_TYPE value.
26
"""Gets the LibraryInfo object for the current build type.
29
env: The environment object.
32
The LibraryInfo object.
34
return LibraryInfo.__library_info.setdefault(env['BUILD_TYPE'],
39
self.prebuilt_libraries = set()
40
self.system_libraries = set()
43
def _GetLibParams(env, lib):
44
"""Gets the params for the given library if it is a library target.
46
Returns the params that were specified when the given lib target name was
47
created, or None if no such lib target has been defined. In the None case, it
48
additionally records the negative result so as to detect out-of-order
49
dependencies for future targets.
52
env: The environment object.
53
lib: The library's name as a string.
56
Its dictionary of params, or None.
58
info = LibraryInfo.get(env)
59
if lib in info.lib_targets:
60
return info.lib_targets[lib]
62
if lib not in info.prebuilt_libraries and lib not in info.system_libraries:
63
info.system_libraries.add(lib)
67
def _RecordLibParams(env, lib, params):
68
"""Record the params used for a library target.
70
Record the params used for a library target while checking for several error
74
env: The environment object.
75
lib: The library target's name as a string.
76
params: Its dictionary of params.
79
Exception: The lib target has already been recorded, or the lib was
80
previously declared to be prebuilt, or the lib target is being defined
81
after a reverse library dependency.
83
info = LibraryInfo.get(env)
84
if lib in info.lib_targets:
85
raise Exception('Multiple definitions of ' + lib)
86
if lib in info.prebuilt_libraries:
87
raise Exception(lib + ' already declared as a prebuilt library')
88
if lib in info.system_libraries:
89
raise Exception(lib + ' cannot be defined after its reverse library '
91
info.lib_targets[lib] = params
94
def _IsPrebuiltLibrary(env, lib):
95
"""Checks whether or not the given library is a prebuilt static library.
97
Returns whether or not the given library name has been declared to be a
98
prebuilt static library. In the False case, it additionally records the
99
negative result so as to detect out-of-order dependencies for future targets.
102
env: The environment object.
103
lib: The library's name as a string.
108
info = LibraryInfo.get(env)
109
if lib in info.prebuilt_libraries:
112
if lib not in info.lib_targets and lib not in info.system_libraries:
113
info.system_libraries.add(lib)
117
def _RecordPrebuiltLibrary(env, lib):
118
"""Record that a library is a prebuilt static library.
120
Record that the given library name refers to a prebuilt static library while
121
checking for several error conditions.
124
env: The environment object.
125
lib: The library's name as a string.
128
Exception: The lib has already been recorded to be prebuilt, or the lib was
129
previously declared as a target, or the lib is being declared as
130
prebuilt after a reverse library dependency.
132
info = LibraryInfo.get(env)
133
if lib in info.prebuilt_libraries:
134
raise Exception('Multiple prebuilt declarations of ' + lib)
135
if lib in info.lib_targets:
136
raise Exception(lib + ' already defined as a target')
137
if lib in info.system_libraries:
138
raise Exception(lib + ' cannot be declared as prebuilt after its reverse '
139
'library dependencies')
140
info.prebuilt_libraries.add(lib)
143
def _GenericLibrary(env, static, **kwargs):
144
"""Extends ComponentLibrary to support multiplatform builds
145
of dynamic or static libraries.
148
env: The environment object.
149
kwargs: The keyword arguments.
152
See swtoolkit ComponentLibrary
154
params = CombineDicts(kwargs, {'COMPONENT_STATIC': static})
155
return ExtendComponent(env, 'ComponentLibrary', **params)
158
def DeclarePrebuiltLibraries(env, libraries):
159
"""Informs the build engine about external static libraries.
161
Informs the build engine that the given external library name(s) are prebuilt
162
static libraries, as opposed to shared libraries.
165
env: The environment object.
166
libraries: The library or libraries that are being declared as prebuilt
169
if not SCons.Util.is_List(libraries):
170
libraries = [libraries]
171
for library in libraries:
172
_RecordPrebuiltLibrary(env, library)
175
def Library(env, **kwargs):
176
"""Extends ComponentLibrary to support multiplatform builds of static
180
env: The current environment.
181
kwargs: The keyword arguments.
184
See swtoolkit ComponentLibrary
186
return _GenericLibrary(env, True, **kwargs)
189
def DynamicLibrary(env, **kwargs):
190
"""Extends ComponentLibrary to support multiplatform builds
194
env: The environment object.
195
kwargs: The keyword arguments.
198
See swtoolkit ComponentLibrary
200
return _GenericLibrary(env, False, **kwargs)
203
def Object(env, **kwargs):
204
return ExtendComponent(env, 'ComponentObject', **kwargs)
207
def Unittest(env, **kwargs):
208
"""Extends ComponentTestProgram to support unittest built
209
for multiple platforms.
212
env: The current environment.
213
kwargs: The keyword arguments.
216
See swtoolkit ComponentProgram.
218
kwargs['name'] = kwargs['name'] + '_unittest'
220
common_test_params = {
221
'posix_cppdefines': ['GUNIT_NO_GOOGLE3', 'GTEST_HAS_RTTI=0'],
222
'libs': ['unittest_main', 'gunit']
224
if 'explicit_libs' not in kwargs:
225
common_test_params['win_libs'] = [
236
common_test_params['lin_libs'] = [
242
params = CombineDicts(kwargs, common_test_params)
243
return ExtendComponent(env, 'ComponentTestProgram', **params)
246
def App(env, **kwargs):
247
"""Extends ComponentProgram to support executables with platform specific
251
env: The current environment.
252
kwargs: The keyword arguments.
255
See swtoolkit ComponentProgram.
257
if 'explicit_libs' not in kwargs:
258
common_app_params = {
270
params = CombineDicts(kwargs, common_app_params)
273
return ExtendComponent(env, 'ComponentProgram', **params)
275
def WiX(env, **kwargs):
276
""" Extends the WiX builder
278
env: The current environment.
279
kwargs: The keyword arguments.
282
The node produced by the environment's wix builder
284
return ExtendComponent(env, 'WiX', **kwargs)
286
def Repository(env, at, path):
287
"""Maps a directory external to $MAIN_DIR to the given path so that sources
288
compiled from it end up in the correct place under $OBJ_DIR. NOT required
289
when only referring to header files.
292
env: The current environment object.
293
at: The 'mount point' within the current directory.
294
path: Path to the actual directory.
296
env.Dir(at).addRepository(env.Dir(path))
299
def Components(*paths):
300
"""Completes the directory paths with the correct file
301
names such that the directory/directory.scons name
302
convention can be used.
305
paths: The paths to complete. If it refers to an existing
306
file then it is ignored.
309
The completed lif scons files that are needed to build talk.
313
if os.path.isfile(path):
316
files.append(ExpandSconsPath(path))
320
def ExpandSconsPath(path):
321
"""Expands a directory path into the path to the
322
scons file that our build uses.
323
Ex: magiflute/plugin/common => magicflute/plugin/common/common.scons
326
path: The directory path to expand.
331
return '%s/%s.scons' % (path, os.path.basename(path))
334
def ReadVersion(filename):
335
"""Executes the supplied file and pulls out a version definition from it. """
337
execfile(str(filename), defs)
338
if 'version' not in defs:
340
version = defs['version']
341
parts = version.split(',')
342
build = os.environ.get('GOOGLE_VERSION_BUILDNUMBER')
344
parts[-1] = str(build)
345
return '.'.join(parts)
348
#-------------------------------------------------------------------------------
349
# Helper methods for translating talk.Foo() declarations in to manipulations of
350
# environmuent construction variables, including parameter parsing and merging,
352
def PopEntry(dictionary, key):
353
"""Get the value from a dictionary by key. If the key
354
isn't in the dictionary then None is returned. If it is in
355
the dictionary the value is fetched and then is it removed
359
dictionary: The dictionary.
360
key: The key to get the value for.
362
The value or None if the key is missing.
365
if key in dictionary:
366
value = dictionary[key]
371
def MergeAndFilterByPlatform(env, params):
372
"""Take a dictionary of arguments to lists of values, and, depending on
373
which platform we are targetting, merge the lists of associated keys.
374
Merge by combining value lists like so:
375
{win_foo = [a,b], lin_foo = [c,d], foo = [e], mac_bar = [f], bar = [g] }
376
becomes {foo = [a,b,e], bar = [g]} on windows, and
377
{foo = [e], bar = [f,g]} on mac
380
env: The hammer environment which knows which platforms are active
381
params: The keyword argument dictionary.
383
A new dictionary with the filtered and combined entries of params
392
platforms[x] for x in iter(platforms) if env.Bit(x)
394
inactive_prefixes = [
395
platforms[x] for x in iter(platforms) if not env.Bit(x)
399
for arg, values in params.iteritems():
400
inactive_platform = False
404
for prefix in active_prefixes:
405
if arg.startswith(prefix):
406
key = arg[len(prefix):]
408
for prefix in inactive_prefixes:
409
if arg.startswith(prefix):
410
inactive_platform = True
412
if inactive_platform:
415
AddToDict(merged, key, values)
420
def MergeSettingsFromLibraryDependencies(env, params):
422
for lib in params['libs']:
423
libparams = _GetLibParams(env, lib)
425
if 'dependent_target_settings' in libparams:
426
params = CombineDicts(
428
MergeAndFilterByPlatform(
430
libparams['dependent_target_settings']))
434
def ExtendComponent(env, component, **kwargs):
435
"""A wrapper around a scons builder function that preprocesses and post-
436
processes its inputs and outputs. For example, it merges and filters
437
certain keyword arguments before appending them to the environments
438
construction variables. It can build signed targets and 64bit copies
442
env: The hammer environment with which to build the target
443
component: The environment's builder function, e.g. ComponentProgram
444
kwargs: keyword arguments that are either merged, translated, and passed on
445
to the call to component, or which control execution.
446
TODO(): Document the fields, such as cppdefines->CPPDEFINES,
447
prepend_includedirs, include_talk_media_libs, etc.
449
The output node returned by the call to component, or a subsequent signed
454
# prune parameters intended for other platforms, then merge
455
params = MergeAndFilterByPlatform(env, kwargs)
457
# get the 'target' field
458
name = PopEntry(params, 'name')
460
# get the 'packages' field and process it if present (only used for Linux).
461
packages = PopEntry(params, 'packages')
462
if packages and len(packages):
463
params = CombineDicts(params, env.GetPackageParams(packages))
465
# save pristine params of lib targets for future reference
466
if 'ComponentLibrary' == component:
467
_RecordLibParams(env, name, dict(params))
469
# add any dependent target settings from library dependencies
470
params = MergeSettingsFromLibraryDependencies(env, params)
472
# if this is a signed binary we need to make an unsigned version first
473
signed = env.Bit('windows') and PopEntry(params, 'signed')
475
name = 'unsigned_' + name
477
# potentially exit now
478
srcs = PopEntry(params, 'srcs')
479
if not srcs or not hasattr(env, component):
482
# apply any explicit dependencies
483
dependencies = PopEntry(params, 'depends')
484
if dependencies is not None:
485
env.Depends(name, dependencies)
487
# put the contents of params into the environment
488
# some entries are renamed then appended, others renamed then prepended
490
'cppdefines' : 'CPPDEFINES',
491
'libdirs' : 'LIBPATH',
492
'link_flags' : 'LINKFLAGS',
494
'FRAMEWORKS' : 'FRAMEWORKS',
497
if env.Bit('windows'):
498
# MSVC compile flags have precedence at the beginning ...
499
prepends['ccflags'] = 'CCFLAGS'
501
# ... while GCC compile flags have precedence at the end
502
appends['ccflags'] = 'CCFLAGS'
503
if PopEntry(params, 'prepend_includedirs'):
504
prepends['includedirs'] = 'CPPPATH'
506
appends['includedirs'] = 'CPPPATH'
508
for field, var in appends.items():
509
values = PopEntry(params, field)
510
if values is not None:
511
env.Append(**{var : values})
512
for field, var in prepends.items():
513
values = PopEntry(params, field)
514
if values is not None:
515
env.Prepend(**{var : values})
517
# any other parameters are replaced without renaming
518
for field, value in params.items():
519
env.Replace(**{field : value})
521
if env.Bit('linux') and 'LIBS' in env:
523
# When using --as-needed + --start/end-group, shared libraries need to come
524
# after --end-group on the command-line because the pruning decision only
525
# considers the preceding modules and --start/end-group may cause the
526
# effective position of early static libraries on the command-line to be
527
# deferred to the point of --end-group. To effect this, we move shared libs
528
# into _LIBFLAGS, which has the --end-group as its first entry. SCons does
529
# not track dependencies on system shared libraries anyway so we lose
530
# nothing by removing them from LIBS.
531
static_libs = [lib for lib in libs if
532
_GetLibParams(env, lib) or _IsPrebuiltLibrary(env, lib)]
533
shared_libs = ['-l' + lib for lib in libs if not
534
(_GetLibParams(env, lib) or _IsPrebuiltLibrary(env, lib))]
535
env.Replace(LIBS=static_libs)
536
env.Append(_LIBFLAGS=shared_libs)
538
# invoke the builder function
539
builder = getattr(env, component)
541
node = builder(name, srcs)
544
# Get the name of the built binary, then get the name of the final signed
545
# version from it. We need the output path since we don't know the file
546
# extension beforehand.
547
target = node[0].path.split('_', 1)[1]
548
# postsignprefix: If defined, postsignprefix is a string that should be
549
# prepended to the target executable. This is to provide a work around
550
# for EXEs and DLLs with the same name, which thus have PDBs with the
551
# same name. Setting postsignprefix allows the EXE and its PDB
552
# to be renamed and copied in a previous step; then the desired
553
# name of the EXE (but not PDB) is reconstructed after signing.
554
postsignprefix = PopEntry(params, 'postsignprefix')
555
if postsignprefix is not None:
556
target = postsignprefix + target
557
signed_node = env.SignedBinary(
559
target = '$STAGING_DIR/' + target,
561
env.Alias('signed_binaries', signed_node)
567
def AddToDict(dictionary, key, values, append=True):
568
"""Merge the given key value(s) pair into a dictionary. If it contains an
569
entry with that key already, then combine by appending or prepending the
570
values as directed. Otherwise, assign a new keyvalue pair.
575
if key not in dictionary:
576
dictionary[key] = values
579
cur = dictionary[key]
580
# TODO: Make sure that there are no duplicates
581
# in the list. I can't use python set for this since
582
# the nodes that are returned by the SCONS builders
584
# dictionary[key] = list(set(cur).union(set(values)))
586
dictionary[key] = cur + values
588
dictionary[key] = values + cur
591
def CombineDicts(a, b):
592
"""Unions two dictionaries of arrays/dictionaries.
594
Unions two dictionaries of arrays/dictionaries by combining the values of keys
595
shared between them. The original dictionaries should not be used again after
603
The union of a and b.
610
if isinstance(aval, dict) and isinstance(bval, dict):
611
c[key] = CombineDicts(aval, bval)
623
def RenameKey(d, old, new, append=True):
624
AddToDict(d, new, PopEntry(d, old), append)