33584f942c
The comma this allows is a very common variant of the license header (3:1 preferred over the no-comma variant in talk/). Also pacify pylint a bit, and correct a flagrantly incorrect header I happened to come across. BUG=2098,2133 R=henrike@webrtc.org, niklas.enbom@webrtc.org Review URL: https://webrtc-codereview.appspot.com/1866004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4396 4adac7df-926f-26a2-2b94-8c16560cd09d
636 lines
19 KiB
Python
636 lines
19 KiB
Python
# Copyright 2010 Google Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Author: Tim Haloun (thaloun@google.com)
|
|
# Daniel Petersson (dape@google.com)
|
|
#
|
|
import os
|
|
import SCons.Util
|
|
|
|
class LibraryInfo:
|
|
"""Records information on the libraries defined in a build configuration.
|
|
|
|
Attributes:
|
|
lib_targets: Dictionary of library target params for lookups in
|
|
ExtendComponent().
|
|
prebuilt_libraries: Set of all prebuilt static libraries.
|
|
system_libraries: Set of libraries not found in the above (used to detect
|
|
out-of-order build rules).
|
|
"""
|
|
|
|
# Dictionary of LibraryInfo objects keyed by BUILD_TYPE value.
|
|
__library_info = {}
|
|
|
|
@staticmethod
|
|
def get(env):
|
|
"""Gets the LibraryInfo object for the current build type.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
|
|
Returns:
|
|
The LibraryInfo object.
|
|
"""
|
|
return LibraryInfo.__library_info.setdefault(env['BUILD_TYPE'],
|
|
LibraryInfo())
|
|
|
|
def __init__(self):
|
|
self.lib_targets = {}
|
|
self.prebuilt_libraries = set()
|
|
self.system_libraries = set()
|
|
|
|
|
|
def _GetLibParams(env, lib):
|
|
"""Gets the params for the given library if it is a library target.
|
|
|
|
Returns the params that were specified when the given lib target name was
|
|
created, or None if no such lib target has been defined. In the None case, it
|
|
additionally records the negative result so as to detect out-of-order
|
|
dependencies for future targets.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
lib: The library's name as a string.
|
|
|
|
Returns:
|
|
Its dictionary of params, or None.
|
|
"""
|
|
info = LibraryInfo.get(env)
|
|
if lib in info.lib_targets:
|
|
return info.lib_targets[lib]
|
|
else:
|
|
if lib not in info.prebuilt_libraries and lib not in info.system_libraries:
|
|
info.system_libraries.add(lib)
|
|
return None
|
|
|
|
|
|
def _RecordLibParams(env, lib, params):
|
|
"""Record the params used for a library target.
|
|
|
|
Record the params used for a library target while checking for several error
|
|
conditions.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
lib: The library target's name as a string.
|
|
params: Its dictionary of params.
|
|
|
|
Raises:
|
|
Exception: The lib target has already been recorded, or the lib was
|
|
previously declared to be prebuilt, or the lib target is being defined
|
|
after a reverse library dependency.
|
|
"""
|
|
info = LibraryInfo.get(env)
|
|
if lib in info.lib_targets:
|
|
raise Exception('Multiple definitions of ' + lib)
|
|
if lib in info.prebuilt_libraries:
|
|
raise Exception(lib + ' already declared as a prebuilt library')
|
|
if lib in info.system_libraries:
|
|
raise Exception(lib + ' cannot be defined after its reverse library '
|
|
'dependencies')
|
|
info.lib_targets[lib] = params
|
|
|
|
|
|
def _IsPrebuiltLibrary(env, lib):
|
|
"""Checks whether or not the given library is a prebuilt static library.
|
|
|
|
Returns whether or not the given library name has been declared to be a
|
|
prebuilt static library. In the False case, it additionally records the
|
|
negative result so as to detect out-of-order dependencies for future targets.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
lib: The library's name as a string.
|
|
|
|
Returns:
|
|
True or False
|
|
"""
|
|
info = LibraryInfo.get(env)
|
|
if lib in info.prebuilt_libraries:
|
|
return True
|
|
else:
|
|
if lib not in info.lib_targets and lib not in info.system_libraries:
|
|
info.system_libraries.add(lib)
|
|
return False
|
|
|
|
|
|
def _RecordPrebuiltLibrary(env, lib):
|
|
"""Record that a library is a prebuilt static library.
|
|
|
|
Record that the given library name refers to a prebuilt static library while
|
|
checking for several error conditions.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
lib: The library's name as a string.
|
|
|
|
Raises:
|
|
Exception: The lib has already been recorded to be prebuilt, or the lib was
|
|
previously declared as a target, or the lib is being declared as
|
|
prebuilt after a reverse library dependency.
|
|
"""
|
|
info = LibraryInfo.get(env)
|
|
if lib in info.prebuilt_libraries:
|
|
raise Exception('Multiple prebuilt declarations of ' + lib)
|
|
if lib in info.lib_targets:
|
|
raise Exception(lib + ' already defined as a target')
|
|
if lib in info.system_libraries:
|
|
raise Exception(lib + ' cannot be declared as prebuilt after its reverse '
|
|
'library dependencies')
|
|
info.prebuilt_libraries.add(lib)
|
|
|
|
|
|
def _GenericLibrary(env, static, **kwargs):
|
|
"""Extends ComponentLibrary to support multiplatform builds
|
|
of dynamic or static libraries.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
kwargs: The keyword arguments.
|
|
|
|
Returns:
|
|
See swtoolkit ComponentLibrary
|
|
"""
|
|
params = CombineDicts(kwargs, {'COMPONENT_STATIC': static})
|
|
return ExtendComponent(env, 'ComponentLibrary', **params)
|
|
|
|
|
|
def DeclarePrebuiltLibraries(env, libraries):
|
|
"""Informs the build engine about external static libraries.
|
|
|
|
Informs the build engine that the given external library name(s) are prebuilt
|
|
static libraries, as opposed to shared libraries.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
libraries: The library or libraries that are being declared as prebuilt
|
|
static libraries.
|
|
"""
|
|
if not SCons.Util.is_List(libraries):
|
|
libraries = [libraries]
|
|
for library in libraries:
|
|
_RecordPrebuiltLibrary(env, library)
|
|
|
|
|
|
def Library(env, **kwargs):
|
|
"""Extends ComponentLibrary to support multiplatform builds of static
|
|
libraries.
|
|
|
|
Args:
|
|
env: The current environment.
|
|
kwargs: The keyword arguments.
|
|
|
|
Returns:
|
|
See swtoolkit ComponentLibrary
|
|
"""
|
|
return _GenericLibrary(env, True, **kwargs)
|
|
|
|
|
|
def DynamicLibrary(env, **kwargs):
|
|
"""Extends ComponentLibrary to support multiplatform builds
|
|
of dynmic libraries.
|
|
|
|
Args:
|
|
env: The environment object.
|
|
kwargs: The keyword arguments.
|
|
|
|
Returns:
|
|
See swtoolkit ComponentLibrary
|
|
"""
|
|
return _GenericLibrary(env, False, **kwargs)
|
|
|
|
|
|
def Object(env, **kwargs):
|
|
return ExtendComponent(env, 'ComponentObject', **kwargs)
|
|
|
|
|
|
def Unittest(env, **kwargs):
|
|
"""Extends ComponentTestProgram to support unittest built
|
|
for multiple platforms.
|
|
|
|
Args:
|
|
env: The current environment.
|
|
kwargs: The keyword arguments.
|
|
|
|
Returns:
|
|
See swtoolkit ComponentProgram.
|
|
"""
|
|
kwargs['name'] = kwargs['name'] + '_unittest'
|
|
|
|
common_test_params = {
|
|
'posix_cppdefines': ['GUNIT_NO_GOOGLE3', 'GTEST_HAS_RTTI=0'],
|
|
'libs': ['unittest_main', 'gunit']
|
|
}
|
|
if 'explicit_libs' not in kwargs:
|
|
common_test_params['win_libs'] = [
|
|
'advapi32',
|
|
'crypt32',
|
|
'iphlpapi',
|
|
'secur32',
|
|
'shell32',
|
|
'shlwapi',
|
|
'user32',
|
|
'wininet',
|
|
'ws2_32'
|
|
]
|
|
common_test_params['lin_libs'] = [
|
|
'crypto',
|
|
'pthread',
|
|
'ssl',
|
|
]
|
|
|
|
params = CombineDicts(kwargs, common_test_params)
|
|
return ExtendComponent(env, 'ComponentTestProgram', **params)
|
|
|
|
|
|
def App(env, **kwargs):
|
|
"""Extends ComponentProgram to support executables with platform specific
|
|
options.
|
|
|
|
Args:
|
|
env: The current environment.
|
|
kwargs: The keyword arguments.
|
|
|
|
Returns:
|
|
See swtoolkit ComponentProgram.
|
|
"""
|
|
if 'explicit_libs' not in kwargs:
|
|
common_app_params = {
|
|
'win_libs': [
|
|
'advapi32',
|
|
'crypt32',
|
|
'iphlpapi',
|
|
'secur32',
|
|
'shell32',
|
|
'shlwapi',
|
|
'user32',
|
|
'wininet',
|
|
'ws2_32'
|
|
]}
|
|
params = CombineDicts(kwargs, common_app_params)
|
|
else:
|
|
params = kwargs
|
|
return ExtendComponent(env, 'ComponentProgram', **params)
|
|
|
|
def WiX(env, **kwargs):
|
|
""" Extends the WiX builder
|
|
Args:
|
|
env: The current environment.
|
|
kwargs: The keyword arguments.
|
|
|
|
Returns:
|
|
The node produced by the environment's wix builder
|
|
"""
|
|
return ExtendComponent(env, 'WiX', **kwargs)
|
|
|
|
def Repository(env, at, path):
|
|
"""Maps a directory external to $MAIN_DIR to the given path so that sources
|
|
compiled from it end up in the correct place under $OBJ_DIR. NOT required
|
|
when only referring to header files.
|
|
|
|
Args:
|
|
env: The current environment object.
|
|
at: The 'mount point' within the current directory.
|
|
path: Path to the actual directory.
|
|
"""
|
|
env.Dir(at).addRepository(env.Dir(path))
|
|
|
|
|
|
def Components(*paths):
|
|
"""Completes the directory paths with the correct file
|
|
names such that the directory/directory.scons name
|
|
convention can be used.
|
|
|
|
Args:
|
|
paths: The paths to complete. If it refers to an existing
|
|
file then it is ignored.
|
|
|
|
Returns:
|
|
The completed lif scons files that are needed to build talk.
|
|
"""
|
|
files = []
|
|
for path in paths:
|
|
if os.path.isfile(path):
|
|
files.append(path)
|
|
else:
|
|
files.append(ExpandSconsPath(path))
|
|
return files
|
|
|
|
|
|
def ExpandSconsPath(path):
|
|
"""Expands a directory path into the path to the
|
|
scons file that our build uses.
|
|
Ex: magiflute/plugin/common => magicflute/plugin/common/common.scons
|
|
|
|
Args:
|
|
path: The directory path to expand.
|
|
|
|
Returns:
|
|
The expanded path.
|
|
"""
|
|
return '%s/%s.scons' % (path, os.path.basename(path))
|
|
|
|
|
|
def ReadVersion(filename):
|
|
"""Executes the supplied file and pulls out a version definition from it. """
|
|
defs = {}
|
|
execfile(str(filename), defs)
|
|
if 'version' not in defs:
|
|
return '0.0.0.0'
|
|
version = defs['version']
|
|
parts = version.split(',')
|
|
build = os.environ.get('GOOGLE_VERSION_BUILDNUMBER')
|
|
if build:
|
|
parts[-1] = str(build)
|
|
return '.'.join(parts)
|
|
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Helper methods for translating talk.Foo() declarations in to manipulations of
|
|
# environmuent construction variables, including parameter parsing and merging,
|
|
#
|
|
def PopEntry(dictionary, key):
|
|
"""Get the value from a dictionary by key. If the key
|
|
isn't in the dictionary then None is returned. If it is in
|
|
the dictionary the value is fetched and then is it removed
|
|
from the dictionary.
|
|
|
|
Args:
|
|
dictionary: The dictionary.
|
|
key: The key to get the value for.
|
|
Returns:
|
|
The value or None if the key is missing.
|
|
"""
|
|
value = None
|
|
if key in dictionary:
|
|
value = dictionary[key]
|
|
dictionary.pop(key)
|
|
return value
|
|
|
|
|
|
def MergeAndFilterByPlatform(env, params):
|
|
"""Take a dictionary of arguments to lists of values, and, depending on
|
|
which platform we are targetting, merge the lists of associated keys.
|
|
Merge by combining value lists like so:
|
|
{win_foo = [a,b], lin_foo = [c,d], foo = [e], mac_bar = [f], bar = [g] }
|
|
becomes {foo = [a,b,e], bar = [g]} on windows, and
|
|
{foo = [e], bar = [f,g]} on mac
|
|
|
|
Args:
|
|
env: The hammer environment which knows which platforms are active
|
|
params: The keyword argument dictionary.
|
|
Returns:
|
|
A new dictionary with the filtered and combined entries of params
|
|
"""
|
|
platforms = {
|
|
'linux': 'lin_',
|
|
'mac': 'mac_',
|
|
'posix': 'posix_',
|
|
'windows': 'win_',
|
|
}
|
|
active_prefixes = [
|
|
platforms[x] for x in iter(platforms) if env.Bit(x)
|
|
]
|
|
inactive_prefixes = [
|
|
platforms[x] for x in iter(platforms) if not env.Bit(x)
|
|
]
|
|
|
|
merged = {}
|
|
for arg, values in params.iteritems():
|
|
inactive_platform = False
|
|
|
|
key = arg
|
|
|
|
for prefix in active_prefixes:
|
|
if arg.startswith(prefix):
|
|
key = arg[len(prefix):]
|
|
|
|
for prefix in inactive_prefixes:
|
|
if arg.startswith(prefix):
|
|
inactive_platform = True
|
|
|
|
if inactive_platform:
|
|
continue
|
|
|
|
AddToDict(merged, key, values)
|
|
|
|
return merged
|
|
|
|
|
|
def MergeSettingsFromLibraryDependencies(env, params):
|
|
if 'libs' in params:
|
|
for lib in params['libs']:
|
|
libparams = _GetLibParams(env, lib)
|
|
if libparams:
|
|
if 'dependent_target_settings' in libparams:
|
|
params = CombineDicts(
|
|
params,
|
|
MergeAndFilterByPlatform(
|
|
env,
|
|
libparams['dependent_target_settings']))
|
|
return params
|
|
|
|
|
|
def ExtendComponent(env, component, **kwargs):
|
|
"""A wrapper around a scons builder function that preprocesses and post-
|
|
processes its inputs and outputs. For example, it merges and filters
|
|
certain keyword arguments before appending them to the environments
|
|
construction variables. It can build signed targets and 64bit copies
|
|
of targets as well.
|
|
|
|
Args:
|
|
env: The hammer environment with which to build the target
|
|
component: The environment's builder function, e.g. ComponentProgram
|
|
kwargs: keyword arguments that are either merged, translated, and passed on
|
|
to the call to component, or which control execution.
|
|
TODO(): Document the fields, such as cppdefines->CPPDEFINES,
|
|
prepend_includedirs, include_talk_media_libs, etc.
|
|
Returns:
|
|
The output node returned by the call to component, or a subsequent signed
|
|
dependant node.
|
|
"""
|
|
env = env.Clone()
|
|
|
|
# prune parameters intended for other platforms, then merge
|
|
params = MergeAndFilterByPlatform(env, kwargs)
|
|
|
|
# get the 'target' field
|
|
name = PopEntry(params, 'name')
|
|
|
|
# get the 'packages' field and process it if present (only used for Linux).
|
|
packages = PopEntry(params, 'packages')
|
|
if packages and len(packages):
|
|
params = CombineDicts(params, env.GetPackageParams(packages))
|
|
|
|
# save pristine params of lib targets for future reference
|
|
if 'ComponentLibrary' == component:
|
|
_RecordLibParams(env, name, dict(params))
|
|
|
|
# add any dependent target settings from library dependencies
|
|
params = MergeSettingsFromLibraryDependencies(env, params)
|
|
|
|
# if this is a signed binary we need to make an unsigned version first
|
|
signed = env.Bit('windows') and PopEntry(params, 'signed')
|
|
if signed:
|
|
name = 'unsigned_' + name
|
|
|
|
# potentially exit now
|
|
srcs = PopEntry(params, 'srcs')
|
|
if not srcs or not hasattr(env, component):
|
|
return None
|
|
|
|
# apply any explicit dependencies
|
|
dependencies = PopEntry(params, 'depends')
|
|
if dependencies is not None:
|
|
env.Depends(name, dependencies)
|
|
|
|
# put the contents of params into the environment
|
|
# some entries are renamed then appended, others renamed then prepended
|
|
appends = {
|
|
'cppdefines' : 'CPPDEFINES',
|
|
'libdirs' : 'LIBPATH',
|
|
'link_flags' : 'LINKFLAGS',
|
|
'libs' : 'LIBS',
|
|
'FRAMEWORKS' : 'FRAMEWORKS',
|
|
}
|
|
prepends = {}
|
|
if env.Bit('windows'):
|
|
# MSVC compile flags have precedence at the beginning ...
|
|
prepends['ccflags'] = 'CCFLAGS'
|
|
else:
|
|
# ... while GCC compile flags have precedence at the end
|
|
appends['ccflags'] = 'CCFLAGS'
|
|
if PopEntry(params, 'prepend_includedirs'):
|
|
prepends['includedirs'] = 'CPPPATH'
|
|
else:
|
|
appends['includedirs'] = 'CPPPATH'
|
|
|
|
for field, var in appends.items():
|
|
values = PopEntry(params, field)
|
|
if values is not None:
|
|
env.Append(**{var : values})
|
|
for field, var in prepends.items():
|
|
values = PopEntry(params, field)
|
|
if values is not None:
|
|
env.Prepend(**{var : values})
|
|
|
|
# any other parameters are replaced without renaming
|
|
for field, value in params.items():
|
|
env.Replace(**{field : value})
|
|
|
|
if env.Bit('linux') and 'LIBS' in env:
|
|
libs = env['LIBS']
|
|
# When using --as-needed + --start/end-group, shared libraries need to come
|
|
# after --end-group on the command-line because the pruning decision only
|
|
# considers the preceding modules and --start/end-group may cause the
|
|
# effective position of early static libraries on the command-line to be
|
|
# deferred to the point of --end-group. To effect this, we move shared libs
|
|
# into _LIBFLAGS, which has the --end-group as its first entry. SCons does
|
|
# not track dependencies on system shared libraries anyway so we lose
|
|
# nothing by removing them from LIBS.
|
|
static_libs = [lib for lib in libs if
|
|
_GetLibParams(env, lib) or _IsPrebuiltLibrary(env, lib)]
|
|
shared_libs = ['-l' + lib for lib in libs if not
|
|
(_GetLibParams(env, lib) or _IsPrebuiltLibrary(env, lib))]
|
|
env.Replace(LIBS=static_libs)
|
|
env.Append(_LIBFLAGS=shared_libs)
|
|
|
|
# invoke the builder function
|
|
builder = getattr(env, component)
|
|
|
|
node = builder(name, srcs)
|
|
|
|
if env.Bit('mac') and 'ComponentProgram' == component:
|
|
# Build .dSYM debug packages. This is useful even for non-stripped
|
|
# binaries, as the dsym utility will fetch symbols from all
|
|
# statically-linked libraries (the linker doesn't include them in to the
|
|
# final binary).
|
|
build_dsym = env.Command(
|
|
env.Dir('$STAGING_DIR/%s.dSYM' % node[0]),
|
|
node,
|
|
'mkdir -p `dirname $TARGET` && dsymutil -o $TARGET $SOURCE')
|
|
env.Alias('all_dsym', env.Alias('%s.dSYM' % node[0], build_dsym))
|
|
|
|
if signed:
|
|
# Get the name of the built binary, then get the name of the final signed
|
|
# version from it. We need the output path since we don't know the file
|
|
# extension beforehand.
|
|
target = node[0].path.split('_', 1)[1]
|
|
# postsignprefix: If defined, postsignprefix is a string that should be
|
|
# prepended to the target executable. This is to provide a work around
|
|
# for EXEs and DLLs with the same name, which thus have PDBs with the
|
|
# same name. Setting postsignprefix allows the EXE and its PDB
|
|
# to be renamed and copied in a previous step; then the desired
|
|
# name of the EXE (but not PDB) is reconstructed after signing.
|
|
postsignprefix = PopEntry(params, 'postsignprefix')
|
|
if postsignprefix is not None:
|
|
target = postsignprefix + target
|
|
signed_node = env.SignedBinary(
|
|
source = node,
|
|
target = '$STAGING_DIR/' + target,
|
|
)
|
|
env.Alias('signed_binaries', signed_node)
|
|
return signed_node
|
|
|
|
return node
|
|
|
|
|
|
def AddToDict(dictionary, key, values, append=True):
|
|
"""Merge the given key value(s) pair into a dictionary. If it contains an
|
|
entry with that key already, then combine by appending or prepending the
|
|
values as directed. Otherwise, assign a new keyvalue pair.
|
|
"""
|
|
if values is None:
|
|
return
|
|
|
|
if key not in dictionary:
|
|
dictionary[key] = values
|
|
return
|
|
|
|
cur = dictionary[key]
|
|
# TODO(dape): Make sure that there are no duplicates
|
|
# in the list. I can't use python set for this since
|
|
# the nodes that are returned by the SCONS builders
|
|
# are not hashable.
|
|
# dictionary[key] = list(set(cur).union(set(values)))
|
|
if append:
|
|
dictionary[key] = cur + values
|
|
else:
|
|
dictionary[key] = values + cur
|
|
|
|
|
|
def CombineDicts(a, b):
|
|
"""Unions two dictionaries of arrays/dictionaries.
|
|
|
|
Unions two dictionaries of arrays/dictionaries by combining the values of keys
|
|
shared between them. The original dictionaries should not be used again after
|
|
this call.
|
|
|
|
Args:
|
|
a: First dict.
|
|
b: Second dict.
|
|
|
|
Returns:
|
|
The union of a and b.
|
|
"""
|
|
c = {}
|
|
for key in a:
|
|
if key in b:
|
|
aval = a[key]
|
|
bval = b.pop(key)
|
|
if isinstance(aval, dict) and isinstance(bval, dict):
|
|
c[key] = CombineDicts(aval, bval)
|
|
else:
|
|
c[key] = aval + bval
|
|
else:
|
|
c[key] = a[key]
|
|
|
|
for key in b:
|
|
c[key] = b[key]
|
|
|
|
return c
|
|
|
|
|
|
def RenameKey(d, old, new, append=True):
|
|
AddToDict(d, new, PopEntry(d, old), append)
|