diff options
author | gspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-27 23:15:42 +0000 |
---|---|---|
committer | gspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-27 23:15:42 +0000 |
commit | 05b47f7a8c5451f858dc220df0e3a97542edace6 (patch) | |
tree | a2273d619f0625c9d44d40842845ccce2eac1045 /o3d/site_scons | |
parent | 5cdc8bdb4c847cefe7f4542bd10c9880c2c557a0 (diff) | |
download | chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.zip chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.gz chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.bz2 |
This is the O3D source tree's initial commit to the Chromium tree. It
is not built or referenced at all by the chrome build yet, and doesn't
yet build in it's new home. We'll change that shortly.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17035 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d/site_scons')
35 files changed, 7189 insertions, 0 deletions
diff --git a/o3d/site_scons/http_download.py b/o3d/site_scons/http_download.py new file mode 100644 index 0000000..abc64e6 --- /dev/null +++ b/o3d/site_scons/http_download.py @@ -0,0 +1,77 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Download a file from a URL to a file on disk. + +This module supports username and password with basic authentication. +""" + +import base64 +import os +import urllib2 + + +def _CreateDirectory(path): + """Create a directory tree, ignore if it's already there.""" + try: + os.makedirs(path) + return True + except os.error: + return False + + +def HttpDownload(url, target, username=None, password=None): + """Download a file from a remote server. + + Args: + url: A URL to download from. + target: Filename to write download to. + username: Optional username for download. + password: Optional password for download (ignored if no username). + """ + + headers = [('Accept', '*/*')] + if username: + if password: + auth_code = base64.b64encode(username + ':' + password) + else: + auth_code = base64.b64encode(username) + headers.append(('Authorization', 'Basic ' + auth_code)) + opener = urllib2.build_opener() + opener.addheaders = headers + urllib2.install_opener(opener) + src = urllib2.urlopen(url) + data = src.read() + src.close() + + _CreateDirectory(os.path.split(target)[0]) + fh = open(target, 'wb') + fh.write(data) + fh.close() diff --git a/o3d/site_scons/site_init.py b/o3d/site_scons/site_init.py new file mode 100644 index 0000000..09062e8 --- /dev/null +++ b/o3d/site_scons/site_init.py @@ -0,0 +1,453 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Software construction toolkit site_scons configuration. + +This module sets up SCons for use with this toolkit. This should contain setup +which occurs outside of environments. If a method operates within the context +of an environment, it should instead go in a tool in site_tools and be invoked +for the target environment. +""" + +import __builtin__ +import sys +import SCons +import usage_log + + +def _HostPlatform(): + """Returns the current host platform. + + That is, the platform we're actually running SCons on. You shouldn't use + this inside your SConscript files; instead, include the appropriate + target_platform tool for your environments. When you call + BuildEnvironments(), only environments with the current host platform will be + built. If for some reason you really need to examine the host platform, + check env.Bit('host_windows') / env.Bit('host_linux') / env.Bit('host_mac'). + + Returns: + The host platform name - one of ('WINDOWS', 'LINUX', 'MAC'). + """ + + platform_map = { + 'win32': 'WINDOWS', + 'cygwin': 'WINDOWS', + 'linux': 'LINUX', + 'linux2': 'LINUX', + 'darwin': 'MAC', + } + + if sys.platform not in platform_map: + print ('site_init.py warning: platform "%s" is not in platfom map.' % + sys.platform) + + return platform_map.get(sys.platform, sys.platform) + + +#------------------------------------------------------------------------------ + + +def _CheckBuildModes(build_modes, environments, host_platform): + """Checks the build modes for the environments. + + Args: + build_modes: List of build mode strings. + environments: List of SCons environments. + host_platform: Host platform string. + + Raises: + ValueError: build groups and/or types invalid. + """ + # Make sure the list of environments for the current host platform have + # unique BUILD_TYPE. This ensures they won't overwrite each others' build + # output. (It is ok for build types in different host platforms to overlap; + # that is, WINDOWS and MAC can both have a 'dbg' build type.) + all_build_types = [] + all_build_groups = {} + build_desc = {} + for e in environments: + if not e.Overlap(e['HOST_PLATFORMS'], [host_platform, '*']): + continue + if e['BUILD_TYPE'] in all_build_types: + raise ValueError('Multiple environments have the same BUILD_TYPE=%s' % + e['BUILD_TYPE']) + else: + all_build_types.append(e['BUILD_TYPE']) + build_desc[e['BUILD_TYPE']] = e.get('BUILD_TYPE_DESCRIPTION') + + # Keep track of build groups and the build types which belong to them + for g in e['BUILD_GROUPS']: + if g not in all_build_groups: + all_build_groups[g] = [] + # Don't allow build types and groups to share names + if g in all_build_types: + raise ValueError('Build group %s also specified as BUILD_TYPE.' % g) + else: + all_build_types.append(g) + all_build_groups[g].append(e['BUILD_TYPE']) + + # Add help for build types + help_text = ''' +Use --mode=type to specify the type of build to perform. The following types +may be specified: +''' + + for build_type in all_build_types: + if build_type not in all_build_groups: + help_text += ' %-16s %s\n' % ( + build_type, build_desc.get(build_type, '')) + + help_text += ''' +The following build groups may also be specified via --mode. Build groups +build one or more of the other build types. The available build groups are: +''' + + groups_sorted = all_build_groups.keys() + groups_sorted.sort() + for g in groups_sorted: + help_text += ' %-16s %s\n' % (g, ','.join(all_build_groups[g])) + + help_text += ''' +Multiple modes may be specified, separated by commas: --mode=mode1,mode2. If +no mode is specified, the default group will be built. This is equivalent to +specifying --mode=default. + ''' + SCons.Script.Help(help_text) + + # Make sure all build modes specified by the user are ones which apply to + # the current environment. + for mode in build_modes: + if mode not in all_build_types and mode not in all_build_groups: + print ('Warning: Ignoring build mode "%s", which is not defined on this ' + 'platform.' % mode) + + +#------------------------------------------------------------------------------ + + +def BuildEnvironmentSConscripts(env): + """Evaluates SConscripts for the environment. + + Called by BuildEnvironments(). + """ + # Read SConscript for each component + # TODO: Remove BUILD_COMPONENTS once all projects have transitioned to the + # BUILD_SCONSCRIPTS nomenclature. + for c in env.SubstList2('$BUILD_SCONSCRIPTS', '$BUILD_COMPONENTS'): + # Clone the environment so components can't interfere with each other + ec = env.Clone() + + if ec.Entry(c).isdir(): + # The component is a directory, so assume it contains a SConscript + # file. + c_dir = ec.Dir(c) + + # Use 'build.scons' as the default filename, but if that doesn't + # exist, fall back to 'SConscript'. + c_script = c_dir.File('build.scons') + if not c_script.exists(): + c_script = c_dir.File('SConscript') + else: + # The component is a SConscript file. + c_script = ec.File(c) + c_dir = c_script.dir + + # Make c_dir a string. + c_dir = str(c_dir) + + # Use build_dir differently depending on where the SConscript is. + if not ec.RelativePath('$TARGET_ROOT', c_dir).startswith('..'): + # The above expression means: if c_dir is $TARGET_ROOT or anything + # under it. Going from c_dir to $TARGET_ROOT and dropping the not fails + # to include $TARGET_ROOT. + # We want to be able to allow people to use addRepository to back things + # under $TARGET_ROOT/$OBJ_ROOT with things from above the current + # directory. When we are passed a SConscript that is already under + # $TARGET_ROOT, we should not use build_dir. + ec.SConscript(c_script, exports={'env': ec}, duplicate=0) + elif not ec.RelativePath('$MAIN_DIR', c_dir).startswith('..'): + # The above expression means: if c_dir is $MAIN_DIR or anything + # under it. Going from c_dir to $TARGET_ROOT and dropping the not fails + # to include $MAIN_DIR. + # Also, if we are passed a SConscript that + # is not under $MAIN_DIR, we should fail loudly, because it is unclear how + # this will correspond to things under $OBJ_ROOT. + ec.SConscript(c_script, build_dir='$OBJ_ROOT/' + c_dir, + exports={'env': ec}, duplicate=0) + else: + raise SCons.Error.UserError( + 'Bad location for a SConscript. "%s" is not under ' + '\$TARGET_ROOT or \$MAIN_DIR' % c_script) + +def FilterEnvironments(environments): + """Filters out the environments to be actually build from the specified list + + Only environments with HOST_PLATFORMS containing the platform specified by + --host-platform (or the native host platform, if --host-platform was not + specified) will be matched. + + Each matching environment is checked against the modes passed to the --mode + command line argument (or 'default', if no mode(s) were specified). If any + of the modes match the environment's BUILD_TYPE or any of the environment's + BUILD_GROUPS, all the BUILD_SCONSCRIPTS (and for legacy reasons, + BUILD_COMPONENTS) in that environment will be matched. + + Args: + environments: List of SCons environments. + + Returns: + List of environments which were matched + """ + # Get options + build_modes = SCons.Script.GetOption('build_mode') + # TODO: Remove support legacy MODE= argument, once everyone has transitioned + # to --mode. + legacy_mode_option = SCons.Script.ARGUMENTS.get('MODE') + if legacy_mode_option: + build_modes = legacy_mode_option + build_modes = build_modes.split(',') + + # Check build modes + _CheckBuildModes(build_modes, environments, HOST_PLATFORM) + + environments_to_evaluate = [] + for e in environments: + if not e.Overlap(e['HOST_PLATFORMS'], [HOST_PLATFORM, '*']): + continue # Environment requires a host platform which isn't us + + if e.Overlap([e['BUILD_TYPE'], e['BUILD_GROUPS']], build_modes): + environments_to_evaluate.append(e) + return environments_to_evaluate + +def BuildEnvironments(environments): + """Build a collection of SConscripts under a collection of environments. + + The environments are subject to filtering (c.f. FilterEnvironments) + + Args: + environments: List of SCons environments. + + Returns: + List of environments which were actually evaluated (built). + """ + usage_log.log.AddEntry('BuildEnvironments start') + + environments_to_evaluate = FilterEnvironments(environments) + + for e in environments_to_evaluate: + # Make this the root environment for deferred functions, so they don't + # execute until our call to ExecuteDefer(). + e.SetDeferRoot() + + # Defer building the SConscripts, so that other tools can do + # per-environment setup first. + e.Defer(BuildEnvironmentSConscripts) + + # Execute deferred functions + e.ExecuteDefer() + + # Add help on targets. + AddTargetHelp() + + usage_log.log.AddEntry('BuildEnvironments done') + + # Return list of environments actually evaluated + return environments_to_evaluate + + +#------------------------------------------------------------------------------ + + +def _ToolExists(): + """Replacement for SCons tool module exists() function, if one isn't present. + + Returns: + True. This enables modules which always exist not to need to include a + dummy exists() function. + """ + return True + + +def _ToolModule(self): + """Thunk for SCons.Tool.Tool._tool_module to patch in exists() function. + + Returns: + The module from the original SCons.Tool.Tool._tool_module call, with an + exists() method added if it wasn't present. + """ + module = self._tool_module_orig() + if not hasattr(module, 'exists'): + module.exists = _ToolExists + + return module + +#------------------------------------------------------------------------------ + + +def AddSiteDir(site_dir): + """Adds a site directory, as if passed to the --site-dir option. + + Args: + site_dir: Site directory path to add, relative to the location of the + SConstruct file. + + This may be called from the SConscript file to add a local site scons + directory for a project. This does the following: + * Adds site_dir/site_scons to sys.path. + * Imports site_dir/site_init.py. + * Adds site_dir/site_scons to the SCons tools path. + """ + # Call the same function that SCons does for the --site-dir option. + SCons.Script.Main._load_site_scons_dir( + SCons.Node.FS.get_default_fs().SConstruct_dir, site_dir) + + +#------------------------------------------------------------------------------ + + +_new_options_help = ''' +Additional options for SCons: + + --mode=MODE Specify build mode (see below). + --host-platform=PLATFORM Force SCons to use PLATFORM as the host platform, + instead of the actual platform on which SCons is + run. Useful for examining the dependency tree + which would be created, but not useful for + actually running the build because it'll attempt + to use the wrong tools for your actual platform. + --site-path=DIRLIST Comma-separated list of additional site + directory paths; each is processed as if passed + to --site-dir. + --usage-log=FILE Write XML usage log to FILE. +''' + +def SiteInitMain(): + """Main code executed in site_init.""" + + # Bail out if we've been here before. This is needed to handle the case where + # this site_init.py has been dropped into a project directory. + if hasattr(__builtin__, 'BuildEnvironments'): + return + + usage_log.log.AddEntry('Software Construction Toolkit site init') + + # Let people use new global methods directly. + __builtin__.AddSiteDir = AddSiteDir + __builtin__.FilterEnvironments = FilterEnvironments + __builtin__.BuildEnvironments = BuildEnvironments + # Legacy method names + # TODO: Remove these once they're no longer used anywhere. + __builtin__.BuildComponents = BuildEnvironments + + # Set list of default tools for component_setup + __builtin__.component_setup_tools = [ + # Defer must be first so other tools can register environment + # setup/cleanup functions. + 'defer', + # Component_targets must precede component_builders so builders can + # define target groups. + 'component_targets', + 'command_output', + 'component_bits', + 'component_builders', + 'concat_source', + 'environment_tools', + 'publish', + 'replicate', + ] + + # Patch Tool._tool_module method to fill in an exists() method for the + # module if it isn't present. + # TODO: This functionality should be patched into SCons itself by changing + # Tool.__init__(). + SCons.Tool.Tool._tool_module_orig = SCons.Tool.Tool._tool_module + SCons.Tool.Tool._tool_module = _ToolModule + + # Add our options + SCons.Script.AddOption( + '--mode', '--build-mode', + dest='build_mode', + nargs=1, type='string', + action='store', + metavar='MODE', + default='default', + help='build mode(s)') + SCons.Script.AddOption( + '--host-platform', + dest='host_platform', + nargs=1, type='string', + action='store', + metavar='PLATFORM', + help='build mode(s)') + SCons.Script.AddOption( + '--site-path', + dest='site_path', + nargs=1, type='string', + action='store', + metavar='PATH', + help='comma-separated list of site directories') + SCons.Script.AddOption( + '--usage-log', + dest='usage_log', + nargs=1, type='string', + action='store', + metavar='PATH', + help='file to write XML usage log to') + + SCons.Script.Help(_new_options_help) + + # Set up usage log + usage_log_file = SCons.Script.GetOption('usage_log') + if usage_log_file: + usage_log.log.SetOutputFile(usage_log_file) + + # Set current host platform + host_platform = SCons.Script.GetOption('host_platform') + if not host_platform: + host_platform = _HostPlatform() + __builtin__.HOST_PLATFORM = host_platform + + # Check for site path. This is a list of site directories which each are + # processed as if they were passed to --site-dir. + site_path = SCons.Script.GetOption('site_path') + if site_path: + for site_dir in site_path.split(','): + AddSiteDir(site_dir) + + # Since our site dir was specified on the SCons command line, SCons will + # normally only look at our site dir. Add back checking for project-local + # site_scons directories. + if not SCons.Script.GetOption('no_site_dir'): + SCons.Script.Main._load_site_scons_dir( + SCons.Node.FS.get_default_fs().SConstruct_dir, None) + + +# Run main code +SiteInitMain() diff --git a/o3d/site_scons/site_tools/atlmfc_vc80.py b/o3d/site_scons/site_tools/atlmfc_vc80.py new file mode 100644 index 0000000..861b7e30 --- /dev/null +++ b/o3d/site_scons/site_tools/atlmfc_vc80.py @@ -0,0 +1,64 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Windows ATL MFC for VC80 (Visual Studio 2005) tool for SCons. + +Note that ATL MFC requires the commercial (non-free) version of Visual Studio +2005. Using this in an open-source project thus limits the size of the +developer community to those with the commercial version. +""" + +import os + + +def _FindLocalInstall(): + """Returns the directory containing the local install of the tool. + + Returns: + Path to tool (as a string), or None if not found. + """ + # TODO: Should use a better search. Probably needs to be based on msvs tool, + # as msvc detection is. + default_dir = 'C:/Program Files/Microsoft Visual Studio 8/VC/atlmfc' + if os.path.exists(default_dir): + return default_dir + else: + return None + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + if not env.get('ATLMFC_VC80_DIR'): + env['ATLMFC_VC80_DIR'] = _FindLocalInstall() + + env.AppendENVPath('INCLUDE', env.Dir('$ATLMFC_VC80_DIR/include').abspath) + env.AppendENVPath('LIB', env.Dir('$ATLMFC_VC80_DIR/lib').abspath) diff --git a/o3d/site_scons/site_tools/code_coverage.py b/o3d/site_scons/site_tools/code_coverage.py new file mode 100644 index 0000000..75d9210 --- /dev/null +++ b/o3d/site_scons/site_tools/code_coverage.py @@ -0,0 +1,120 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""SCons tool for generating code coverage. + +This module enhances a debug environment to add code coverage. +It is used as follows: + coverage_env = dbg_env.Clone(tools = ['code_coverage']) +""" + + +def AddCoverageSetup(env): + """Add coverage related build steps and dependency links. + + Args: + env: a leaf environment ready to have coverage steps added. + """ + # Add a step to start coverage (for instance windows needs this). + # This step should get run before and tests are run. + if env.get('COVERAGE_START_CMD', None): + start = env.Command('$COVERAGE_START_FILE', [], '$COVERAGE_START_CMD') + env.AlwaysBuild(start) + else: + start = [] + + # Add a step to end coverage (used on basically all platforms). + # This step should get after all the tests have run. + if env.get('COVERAGE_STOP_CMD', None): + stop = env.Command('$COVERAGE_OUTPUT_FILE', [], '$COVERAGE_STOP_CMD') + env.AlwaysBuild(stop) + else: + stop = [] + + # start must happen before tests run, stop must happen after. + for group in env.SubstList2('$COVERAGE_TARGETS'): + group_alias = env.Alias(group) + # Force each alias to happen after start but before stop. + env.Requires(group_alias, start) + env.Requires(stop, group_alias) + # Force each source of the aliases to happen after start but before stop. + # This is needed to work around non-standard aliases in some projects. + for test in group_alias: + for s in test.sources: + env.Requires(s, start) + env.Requires(stop, s) + + # Add an alias for coverage. + env.Alias('coverage', [start, stop]) + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env['COVERAGE_ENABLED'] = True + + env.SetDefault( + # Setup up coverage related tool paths. + # These can be overridden elsewhere, if needed, to relocate the tools. + COVERAGE_MCOV='mcov', + COVERAGE_GENHTML='genhtml', + COVERAGE_ANALYZER='coverage_analyzer.exe', + COVERAGE_VSPERFCMD='VSPerfCmd.exe', + COVERAGE_VSINSTR='vsinstr.exe', + + # Setup coverage related locations. + COVERAGE_DIR='$TARGET_ROOT/coverage', + COVERAGE_HTML_DIR='$COVERAGE_DIR/html', + COVERAGE_START_FILE='$COVERAGE_DIR/start.junk', + COVERAGE_OUTPUT_FILE='$COVERAGE_DIR/coverage.lcov', + + # The list of aliases containing test execution targets. + COVERAGE_TARGETS=['run_all_tests'], + ) + + # Add in coverage flags. These come from target_platform_xxx. + env.Append( + CCFLAGS='$COVERAGE_CCFLAGS', + LIBS='$COVERAGE_LIBS', + LINKFLAGS='$COVERAGE_LINKFLAGS', + SHLINKFLAGS='$COVERAGE_SHLINKFLAGS', + ) + + # Change the definition of Install if required by the platform. + if env.get('COVERAGE_INSTALL'): + env['PRECOVERAGE_INSTALL'] = env['INSTALL'] + env['INSTALL'] = env['COVERAGE_INSTALL'] + + # Add any extra paths. + env.AppendENVPath('PATH', env.SubstList2('$COVERAGE_EXTRA_PATHS')) + + # Add coverage start/stop and processing in deferred steps. + env.Defer(AddCoverageSetup) diff --git a/o3d/site_scons/site_tools/code_signing.py b/o3d/site_scons/site_tools/code_signing.py new file mode 100644 index 0000000..7534e92 --- /dev/null +++ b/o3d/site_scons/site_tools/code_signing.py @@ -0,0 +1,127 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Code signing build tool. + +This module sets up code signing. +It is used as follows: + env = Environment(tools = ["code_signing"]) +To sign an EXE/DLL do: + env.SignedBinary('hello_signed.exe', 'hello.exe', + CERTIFICATE_FILE='bob.pfx', + CERTIFICATE_PASSWORD='123', + TIMESTAMP_SERVER='') +If no certificate file is specified, copying instead of signing will occur. +If an empty timestamp server string is specified, there will be no timestamp. +""" + +import optparse +from SCons.compat._scons_optparse import OptionConflictError +import SCons.Script + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + try: + SCons.Script.AddOption('--certificate-name', + dest='certificate_name', + help='select which certificate to use') + SCons.Script.Help( + ' --certificate-name <NAME> select which signing certificate to use') + except (OptionConflictError, optparse.OptionConflictError): + # This gets catch to prevent duplicate help being added for this option + # for each build type. + pass + + env.SetDefault( + # Path to Microsoft signtool.exe + SIGNTOOL='"$VC80_DIR/common7/tools/bin/signtool.exe"', + # No certificate by default. + CERTIFICATE_PATH='', + # No certificate password by default. + CERTIFICATE_PASSWORD='', + # The default timestamp server. + TIMESTAMP_SERVER='http://timestamp.verisign.com/scripts/timestamp.dll', + # The default certificate store. + CERTIFICATE_STORE='my', + # Set the certificate name from the command line. + CERTIFICATE_NAME=SCons.Script.GetOption('certificate_name'), + ) + + # Setup Builder for Signing + env['BUILDERS']['SignedBinary'] = SCons.Script.Builder( + generator=SignedBinaryGenerator, + emitter=SignedBinaryEmitter) + + +def SignedBinaryEmitter(target, source, env): + """Add the signing certificate (if any) to the source dependencies.""" + if env.subst('$CERTIFICATE_PATH'): + source.append(env.subst('$CERTIFICATE_PATH')) + return target, source + + +def SignedBinaryGenerator(source, target, env, for_signature): + """A builder generator for code signing.""" + source = source # Silence gpylint. + target = target # Silence gpylint. + for_signature = for_signature # Silence gpylint. + + # Alway copy and make writable. + commands = [ + SCons.Script.Copy('$TARGET', '$SOURCE'), + SCons.Script.Chmod('$TARGET', 0755), + ] + + # Only do signing if there is a certificate file or certificate name. + if env.subst('$CERTIFICATE_PATH') or env.subst('$CERTIFICATE_NAME'): + # The command used to do signing (target added on below). + signing_cmd = '$SIGNTOOL sign ' + # Add in certificate file if any. + if env.subst('$CERTIFICATE_PATH'): + signing_cmd += ' /f "$CERTIFICATE_PATH"' + # Add certificate password if any. + if env.subst('$CERTIFICATE_PASSWORD'): + signing_cmd += ' /p "$CERTIFICATE_PASSWORD"' + # Add certificate store if any. + if env.subst('$CERTIFICATE_NAME'): + # The command used to do signing (target added on below). + signing_cmd += ' /s "$CERTIFICATE_STORE" /n "$CERTIFICATE_NAME"' + # Add timestamp server if any. + if env.subst('$TIMESTAMP_SERVER'): + signing_cmd += ' /t "$TIMESTAMP_SERVER"' + # Add in target name + signing_cmd += ' "$TARGET"' + # Add the signing to the list of commands to perform. + commands.append(signing_cmd) + + return commands diff --git a/o3d/site_scons/site_tools/collada_dom.py b/o3d/site_scons/site_tools/collada_dom.py new file mode 100644 index 0000000..1fc5c05 --- /dev/null +++ b/o3d/site_scons/site_tools/collada_dom.py @@ -0,0 +1,44 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Collada DOM 1.3.0 tool for SCons.""" + + +import sys + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env.Append(CCFLAGS=[ + '-I$COLLADA_DIR/include', + '-I$COLLADA_DIR/include/1.4', + ]) diff --git a/o3d/site_scons/site_tools/command_output.py b/o3d/site_scons/site_tools/command_output.py new file mode 100644 index 0000000..88b8c39c --- /dev/null +++ b/o3d/site_scons/site_tools/command_output.py @@ -0,0 +1,236 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Command output builder for SCons.""" + + +import os +import signal +import subprocess +import sys +import threading +import time +import SCons.Script + + +# TODO: Move KillProcessTree() and RunCommand() into their own module, since +# they're used by other tools. + + +def KillProcessTree(pid): + """Kills the process and all of its child processes. + + Args: + pid: process to kill. + + Raises: + OSError: Unsupported OS. + """ + + if sys.platform in ('win32', 'cygwin'): + # Use Windows' taskkill utility + killproc_path = '%s;%s\\system32;%s\\system32\\wbem' % ( + (os.environ['SYSTEMROOT'],) * 3) + killproc_cmd = 'taskkill /F /T /PID %d' % pid + killproc_task = subprocess.Popen(killproc_cmd, shell=True, + stdout=subprocess.PIPE, + env={'PATH':killproc_path}) + killproc_task.communicate() + + elif sys.platform in ('linux', 'linux2', 'darwin'): + # Use ps to get a list of processes + ps_task = subprocess.Popen(['/bin/ps', 'x', '-o', 'pid,ppid'], stdout=subprocess.PIPE) + ps_out = ps_task.communicate()[0] + + # Parse out a dict of pid->ppid + ppid = {} + for ps_line in ps_out.split('\n'): + w = ps_line.strip().split() + if len(w) < 2: + continue # Not enough words in this line to be a process list + try: + ppid[int(w[0])] = int(w[1]) + except ValueError: + pass # Header or footer + + # For each process, kill it if it or any of its parents is our child + for p in ppid: + p2 = p + while p2: + if p2 == pid: + os.kill(p, signal.SIGKILL) + break + p2 = ppid.get(p2) + + else: + raise OSError('Unsupported OS for KillProcessTree()') + + +def RunCommand(cmdargs, cwdir=None, env=None, echo_output=True, timeout=None, + timeout_errorlevel=14): + """Runs an external command. + + Args: + cmdargs: A command string, or a tuple containing the command and its + arguments. + cwdir: Working directory for the command, if not None. + env: Environment variables dict, if not None. + echo_output: If True, output will be echoed to stdout. + timeout: If not None, timeout for command in seconds. If command times + out, it will be killed and timeout_errorlevel will be returned. + timeout_errorlevel: The value to return if the command times out. + + Returns: + The integer errorlevel from the command. + The combined stdout and stderr as a string. + """ + # Force unicode string in the environment to strings. + if env: + env = dict([(k, str(v)) for k, v in env.items()]) + start_time = time.time() + child = subprocess.Popen(cmdargs, cwd=cwdir, env=env, shell=True, + universal_newlines=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + child_out = [] + child_retcode = None + + def _ReadThread(): + """Thread worker function to read output from child process. + + Necessary since there is no cross-platform way of doing non-blocking + reads of the output pipe. + """ + read_run = True + while read_run: + time.sleep(.1) # So we don't poll too frequently + # Need to have a delay of 1 cycle between child completing and + # thread exit, to pick up the final output from the child. + if child_retcode is not None: + read_run = False + new_out = child.stdout.read() + if new_out: + if echo_output: + print new_out, + child_out.append(new_out) + + read_thread = threading.Thread(target=_ReadThread) + read_thread.setDaemon(True) + read_thread.start() + + # Wait for child to exit or timeout + while child_retcode is None: + time.sleep(.1) # So we don't poll too frequently + child_retcode = child.poll() + if timeout and child_retcode is None: + elapsed = time.time() - start_time + if elapsed > timeout: + print '*** RunCommand() timeout:', cmdargs + KillProcessTree(child.pid) + child_retcode = timeout_errorlevel + + # Wait a bit for worker thread to pick up final output and die. No need to + # worry if it's still alive at the end of this, since it's a daemon thread + # and won't block python from exiting. (And since it's blocked, it doesn't + # chew up CPU.) + read_thread.join(5) + + if echo_output: + print # end last line of output + return child_retcode, ''.join(child_out) + + +def CommandOutputBuilder(target, source, env): + """Command output builder. + + Args: + self: Environment in which to build + target: List of target nodes + source: List of source nodes + + Returns: + None or 0 if successful; nonzero to indicate failure. + + Runs the command specified in the COMMAND_OUTPUT_CMDLINE environment variable + and stores its output in the first target file. Additional target files + should be specified if the command creates additional output files. + + Runs the command in the COMMAND_OUTPUT_RUN_DIR subdirectory. + """ + env = env.Clone() + + cmdline = env.subst('$COMMAND_OUTPUT_CMDLINE', target=target, source=source) + cwdir = env.subst('$COMMAND_OUTPUT_RUN_DIR', target=target, source=source) + if cwdir: + cwdir = os.path.normpath(cwdir) + env.AppendENVPath('PATH', cwdir) + env.AppendENVPath('LD_LIBRARY_PATH', cwdir) + else: + cwdir = None + cmdecho = env.get('COMMAND_OUTPUT_ECHO', True) + timeout = env.get('COMMAND_OUTPUT_TIMEOUT') + timeout_errorlevel = env.get('COMMAND_OUTPUT_TIMEOUT_ERRORLEVEL') + + retcode, output = RunCommand(cmdline, cwdir=cwdir, env=env['ENV'], + echo_output=cmdecho, timeout=timeout, + timeout_errorlevel=timeout_errorlevel) + + # Save command line output + output_file = open(str(target[0]), 'w') + output_file.write(output) + output_file.close() + + return retcode + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add the builder and tell it which build environment variables we use. + action = SCons.Script.Action( + CommandOutputBuilder, + 'Output "$COMMAND_OUTPUT_CMDLINE" to $TARGET', + varlist=[ + 'COMMAND_OUTPUT_CMDLINE', + 'COMMAND_OUTPUT_RUN_DIR', + 'COMMAND_OUTPUT_TIMEOUT', + 'COMMAND_OUTPUT_TIMEOUT_ERRORLEVEL', + # We use COMMAND_OUTPUT_ECHO also, but that doesn't change the + # command being run or its output. + ], ) + builder = SCons.Script.Builder(action = action) + env.Append(BUILDERS={'CommandOutput': builder}) + + # Default command line is to run the first input + env['COMMAND_OUTPUT_CMDLINE'] = '$SOURCE' + + # TODO: Add a pseudo-builder which takes an additional command line as an + # argument. diff --git a/o3d/site_scons/site_tools/component_bits.py b/o3d/site_scons/site_tools/component_bits.py new file mode 100644 index 0000000..a7b7055 --- /dev/null +++ b/o3d/site_scons/site_tools/component_bits.py @@ -0,0 +1,285 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Environment bit support for software construction toolkit. + +This module is automatically included by the component_setup tool. +""" + + +import __builtin__ +import types +import SCons + + +_bit_descriptions = {} +_bits_with_options = set() +_bit_exclusive_groups = {} + +#------------------------------------------------------------------------------ + + +def _CheckDeclared(bits): + """Checks each of the bits to make sure it's been declared. + + Args: + bits: List of bits to check. + + Raises: + ValueError: A bit has not been declared. + """ + for bit in bits: + if bit not in _bit_descriptions: + raise ValueError('Bit "%s" used before DeclareBit()' % + bit) + + +def _CheckExclusive(already_set, proposed): + """Checks if setting proposed bits would violate any exclusive groups. + + Args: + already_set: List of bits already set. + proposed: List of bits attempting to be set. + + Raises: + ValueError: A proposed bit belongs to an exclusive group which already has + a bit set. + """ + # Remove any already-set bits from proposed (note this makes a copy of + # proposed so we don't alter the passed list). + proposed = [bit for bit in proposed if bit not in already_set] + + for group_name, group_bits in _bit_exclusive_groups.items(): + set_match = group_bits.intersection(already_set) + proposed_match = group_bits.intersection(proposed) + if set_match and proposed_match: + raise ValueError('Unable to set bit "%s" because it belongs to the same ' + 'exclusive group "%s" as already-set bit "%s"' % ( + proposed_match.pop(), group_name, set_match.pop())) + + +#------------------------------------------------------------------------------ + + +def DeclareBit(bit_name, desc, exclusive_groups=None): + """Declares and describes the bit. + + Args: + bit_name: Name of the bit being described. + desc: Description of bit. + exclusive_groups: Bit groups which this bit belongs to. At most one bit + may be set in each exclusive group. May be a string, list of string, + or None. + + Raises: + ValueError: The bit has already been defined with a different description, + or the description is empty. + + Adds a description for the bit in the global dictionary of bit names. All + bits should be described before being used in Bit()/AllBits()/AnyBits(). + """ + + if not desc: + raise ValueError('Must supply a description for bit "%s"' % bit_name) + + existing_desc = _bit_descriptions.get(bit_name) + if existing_desc and desc != existing_desc: + raise ValueError('Cannot describe bit "%s" as "%s" because it has already' + 'been described as "%s".' % + (bit_name, desc, existing_desc)) + + _bit_descriptions[bit_name] = desc + + # Add bit to its exclusive groups + if exclusive_groups: + if type(exclusive_groups) == types.StringType: + exclusive_groups = [exclusive_groups] + for g in exclusive_groups: + if g not in _bit_exclusive_groups: + _bit_exclusive_groups[g] = set() + _bit_exclusive_groups[g].add(bit_name) + +#------------------------------------------------------------------------------ + +def Bit(env, bit_name): + """Checks if the environment has the bit. + + Args: + env: Environment to check. + bit_name: Name of the bit to check. + + Returns: + True if the bit is present in the environment. + """ + _CheckDeclared([bit_name]) + return bit_name in env['_BITS'] + +#------------------------------------------------------------------------------ + + +def AllBits(env, *args): + """Checks if the environment has all the bits. + + Args: + env: Environment to check. + args: List of bit names to check. + + Returns: + True if every bit listed is present in the environment. + """ + _CheckDeclared(args) + return set(args).issubset(env['_BITS']) + +#------------------------------------------------------------------------------ + + +def AnyBits(env, *args): + """Checks if the environment has at least one of the bits. + + Args: + env: Environment to check. + args: List of bit names to check. + + Returns: + True if at least one bit listed is present in the environment. + """ + _CheckDeclared(args) + return set(args).intersection(env['_BITS']) + +#------------------------------------------------------------------------------ + + +def SetBits(env, *args): + """Sets the bits in the environment. + + Args: + env: Environment to check. + args: List of bit names to set. + """ + _CheckDeclared(args) + _CheckExclusive(env['_BITS'], args) + env['_BITS'] = env['_BITS'].union(args) + +#------------------------------------------------------------------------------ + + +def ClearBits(env, *args): + """Clears the bits in the environment. + + Args: + env: Environment to check. + args: List of bit names to clear (remove). + """ + _CheckDeclared(args) + env['_BITS'] = env['_BITS'].difference(args) + +#------------------------------------------------------------------------------ + + +def SetBitFromOption(env, bit_name, default): + """Sets the bit in the environment from a command line option. + + Args: + env: Environment to check. + bit_name: Name of the bit to set from a command line option. + default: Default value for bit if command line option is not present. + """ + _CheckDeclared([bit_name]) + + # Add the command line option, if not already present + if bit_name not in _bits_with_options: + _bits_with_options.add(bit_name) + SCons.Script.AddOption('--' + bit_name, + dest=bit_name, + action='store_true', + help='set bit:' + _bit_descriptions[bit_name]) + SCons.Script.AddOption('--no-' + bit_name, + dest=bit_name, + action='store_false', + help='clear bit:' + _bit_descriptions[bit_name]) + + bit_set = env.GetOption(bit_name) + if bit_set is None: + # Not specified on command line, so use default + bit_set = default + + if bit_set: + env['_BITS'].add(bit_name) + elif bit_name in env['_BITS']: + env['_BITS'].remove(bit_name) + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add methods to builtin + __builtin__.DeclareBit = DeclareBit + + # Add methods to environment + env.AddMethod(AllBits) + env.AddMethod(AnyBits) + env.AddMethod(Bit) + env.AddMethod(ClearBits) + env.AddMethod(SetBitFromOption) + env.AddMethod(SetBits) + + env['_BITS'] = set() + + # Declare bits for common target platforms + DeclareBit('linux', 'Target platform is linux.', + exclusive_groups=('target_platform')) + DeclareBit('mac', 'Target platform is mac.', + exclusive_groups=('target_platform')) + DeclareBit('windows', 'Target platform is windows.', + exclusive_groups=('target_platform')) + + # Declare bits for common host platforms + DeclareBit('host_linux', 'Host platform is linux.', + exclusive_groups=('host_platform')) + DeclareBit('host_mac', 'Host platform is mac.', + exclusive_groups=('host_platform')) + DeclareBit('host_windows', 'Host platform is windows.', + exclusive_groups=('host_platform')) + + # Declare other common bits from target_ tools + DeclareBit('debug', 'Build is debug, not optimized.') + DeclareBit('posix', 'Target platform is posix.') + + # Set the appropriate host platform bit + host_platform_to_bit = { + 'MAC': 'host_mac', + 'LINUX': 'host_linux', + 'WINDOWS': 'host_windows', + } + if HOST_PLATFORM in host_platform_to_bit: + env.SetBits(host_platform_to_bit[HOST_PLATFORM]) diff --git a/o3d/site_scons/site_tools/component_builders.py b/o3d/site_scons/site_tools/component_builders.py new file mode 100644 index 0000000..69b52d4 --- /dev/null +++ b/o3d/site_scons/site_tools/component_builders.py @@ -0,0 +1,619 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Software construction toolkit builders for SCons.""" + + +import SCons + + +__component_list = {} + + +def _InitializeComponentBuilders(env): + """Re-initializes component builders module. + + Args: + env: Environment context + """ + env = env # Silence gpylint + + __component_list.clear() + + +def _RetrieveComponents(component_name, filter_components=None): + """Get the list of all components required by the specified component. + + Args: + component_name: Name of the base component. + filter_components: List of components NOT to include. + + Returns: + A list of the transitive closure of all components required by the base + component. That is, if A requires B and B requires C, this returns [B, C]. + + """ + if filter_components: + filter_components = set(filter_components) + else: + filter_components = set() + + components = set([component_name]) # Components always require themselves + new_components = set(components) + while new_components: + # Take next new component and add it to the list we've already scanned. + c = new_components.pop() + components.add(c) + # Add to the list of new components any of c's components that we haven't + # seen before. + new_components.update(__component_list.get(c, set()) + - components - filter_components) + + return list(components) + + +def _StoreComponents(self, component_name): + """Stores the list of child components for the specified component. + + Args: + self: Environment containing component. + component_name: Name of the component. + + Adds component references based on the LIBS and COMPONENTS variables in the + current environment. Should be called at primary SConscript execution time; + use _RetrieveComponents() to get the final components lists in a Defer()'d + function. + """ + + components = set() + for clist in ('LIBS', 'COMPONENTS'): + components.update(map(self.subst, self.Flatten(self[clist]))) + + if component_name not in __component_list: + __component_list[component_name] = set() + __component_list[component_name].update(components) + + +def _ComponentPlatformSetup(env, builder_name, **kwargs): + """Modify an environment to work with a component builder. + + Args: + env: Environment to clone. + builder_name: Name of the builder. + kwargs: Keyword arguments. + + Returns: + A modified clone of the environment. + """ + # Clone environment so we can modify it + env = env.Clone() + + # Add all keyword arguments to the environment + for k, v in kwargs.items(): + env[k] = v + + # Add compiler flags for included headers, if any + env['INCLUDES'] = env.Flatten(env.subst_list(['$INCLUDES'])) + for h in env['INCLUDES']: + env.Append(CCFLAGS=['${CCFLAG_INCLUDE}%s' % h]) + + # Call platform-specific component setup function, if any + if env.get('COMPONENT_PLATFORM_SETUP'): + env['COMPONENT_PLATFORM_SETUP'](env, builder_name) + + # Return the modified environment + return env + +#------------------------------------------------------------------------------ + +# TODO: Should be possible to refactor programs, test programs, libs to all +# publish as packages, for simplicity and code reuse. + + +def ComponentPackageDeferred(env): + """Deferred build steps for component package. + + Args: + env: Environment from ComponentPackage(). + + Sets up the aliases to build the package. + """ + package_name = env['PACKAGE_NAME'] + + # Install program and resources + all_outputs = [] + package_filter = env.Flatten(env.subst_list('$COMPONENT_PACKAGE_FILTER')) + components = _RetrieveComponents(package_name, package_filter) + for resource, dest_dir in env.get('COMPONENT_PACKAGE_RESOURCES').items(): + all_outputs += env.ReplicatePublished(dest_dir, components, resource) + + # Add installed program and resources to the alias + env.Alias(package_name, all_outputs) + + +def ComponentPackage(self, package_name, dest_dir, **kwargs): + """Pseudo-builder for package containing other components. + + Args: + self: Environment in which we were called. + package_name: Name of package. + dest_dir: Destination directory for package. + kwargs: Keyword arguments. + + Returns: + The alias node for the package. + """ + # Clone and modify environment + env = _ComponentPlatformSetup(self, 'ComponentPackage', **kwargs) + + env.Replace( + PACKAGE_NAME=package_name, + PACKAGE_DIR=dest_dir, + ) + + # Add an empty alias for the package and add it to the right groups + a = env.Alias(package_name, []) + for group in env['COMPONENT_PACKAGE_GROUPS']: + SCons.Script.Alias(group, a) + + # Store list of components for this program + env._StoreComponents(package_name) + + # Let component_targets know this target is available in the current mode + env.SetTargetProperty(package_name, TARGET_PATH=dest_dir) + + # Set up deferred call to replicate resources + env.Defer(ComponentPackageDeferred) + + # Return the alias, since it's the only node we have + return a + +#------------------------------------------------------------------------------ + + +def ComponentObject(self, *args, **kwargs): + """Pseudo-builder for object to handle platform-dependent type. + + Args: + self: Environment in which we were called. + args: Positional arguments. + kwargs: Keyword arguments. + + Returns: + Passthrough return code from env.StaticLibrary() or env.SharedLibrary(). + + TODO: Perhaps this should be a generator builder, so it can take a list of + inputs and return a list of outputs? + """ + # Clone and modify environment + env = _ComponentPlatformSetup(self, 'ComponentObject', **kwargs) + + # Make appropriate object type + if env.get('COMPONENT_STATIC'): + o = env.StaticObject(*args, **kwargs) + else: + o = env.SharedObject(*args, **kwargs) + + # Add dependencies on includes + env.Depends(o, env['INCLUDES']) + + return o + +#------------------------------------------------------------------------------ + + +def ComponentLibrary(self, lib_name, *args, **kwargs): + """Pseudo-builder for library to handle platform-dependent type. + + Args: + self: Environment in which we were called. + lib_name: Library name. + args: Positional arguments. + kwargs: Keyword arguments. + + Returns: + Passthrough return code from env.StaticLibrary() or env.SharedLibrary(). + """ + # Clone and modify environment + env = _ComponentPlatformSetup(self, 'ComponentLibrary', **kwargs) + + # Make appropriate library type + if env.get('COMPONENT_STATIC'): + lib_outputs = env.StaticLibrary(lib_name, *args, **kwargs) + else: + lib_outputs = env.SharedLibrary(lib_name, *args, **kwargs) + + # Add dependencies on includes + env.Depends(lib_outputs, env['INCLUDES']) + + # Scan library outputs for files we need to link against this library, and + # files we need to run executables linked against this library. + need_for_link = [] + need_for_debug = [] + need_for_run = [] + for o in lib_outputs: + if o.suffix in env['COMPONENT_LIBRARY_LINK_SUFFIXES']: + need_for_link.append(o) + if o.suffix in env['COMPONENT_LIBRARY_DEBUG_SUFFIXES']: + need_for_debug.append(o) + if o.suffix == env['SHLIBSUFFIX']: + need_for_run.append(o) + all_outputs = lib_outputs + + # Install library in intermediate directory, so other libs and programs can + # link against it + all_outputs += env.Replicate('$LIB_DIR', need_for_link) + + # Publish output + env.Publish(lib_name, 'link', need_for_link) + env.Publish(lib_name, 'run', need_for_run) + env.Publish(lib_name, 'debug', need_for_debug) + + # Add an alias to build and copy the library, and add it to the right groups + a = self.Alias(lib_name, all_outputs) + for group in env['COMPONENT_LIBRARY_GROUPS']: + SCons.Script.Alias(group, a) + + # Store list of components for this library + env._StoreComponents(lib_name) + + # Let component_targets know this target is available in the current mode. + env.SetTargetProperty(lib_name, TARGET_PATH=lib_outputs[0]) + + # If library should publish itself, publish as if it was a program + if env.get('COMPONENT_LIBRARY_PUBLISH'): + env['PROGRAM_BASENAME'] = lib_name + env.Defer(ComponentProgramDeferred) + + # Return the library outputs + return lib_outputs + +#------------------------------------------------------------------------------ + + +def ComponentTestProgramDeferred(env): + """Deferred build steps for test program. + + Args: + env: Environment from ComponentTestProgram(). + + Sets up the aliases to compile and run the test program. + """ + prog_name = env['PROGRAM_BASENAME'] + + # Install program and resources + all_outputs = [] + components = _RetrieveComponents(prog_name) + for resource, dest_dir in env.get('COMPONENT_TEST_RESOURCES').items(): + all_outputs += env.ReplicatePublished(dest_dir, components, resource) + + # Add installed program and resources to the alias + env.Alias(prog_name, all_outputs) + + # Add target properties + env.SetTargetProperty( + prog_name, + # The copy of the program we care about is the one in the tests dir + EXE='$TESTS_DIR/$PROGRAM_NAME', + RUN_CMDLINE='$COMPONENT_TEST_CMDLINE', + RUN_DIR='$TESTS_DIR', + TARGET_PATH='$TESTS_DIR/$PROGRAM_NAME', + ) + + # Add an alias for running the test in the test directory, if the test is + # runnable and has a test command line. + if env.get('COMPONENT_TEST_RUNNABLE') and env.get('COMPONENT_TEST_CMDLINE'): + env.Replace( + COMMAND_OUTPUT_CMDLINE=env['COMPONENT_TEST_CMDLINE'], + COMMAND_OUTPUT_RUN_DIR='$TESTS_DIR', + ) + test_out_name = '$TEST_OUTPUT_DIR/${PROGRAM_BASENAME}.out.txt' + if (env.GetOption('component_test_retest') + and env.File(test_out_name).exists()): + # Delete old test results, so test will rerun. + env.Execute(SCons.Script.Delete(test_out_name)) + + # Set timeout based on test size + timeout = env.get('COMPONENT_TEST_TIMEOUT') + if type(timeout) is dict: + timeout = timeout.get(env.get('COMPONENT_TEST_SIZE')) + if timeout: + env['COMMAND_OUTPUT_TIMEOUT'] = timeout + + # Test program is the first run resource we replicated. (Duplicate + # replicate is not harmful, and is a handy way to pick out the correct + # file from all those we replicated above.) + test_program = env.ReplicatePublished('$TESTS_DIR', prog_name, 'run') + + # Run the test. Note that we need to refer to the file by name, so that + # SCons will recreate the file node after we've deleted it; if we used the + # env.File() we created in the if statement above, SCons would still think + # it exists and not rerun the test. + test_out = env.CommandOutput(test_out_name, test_program) + + # Running the test requires the test and its libs copied to the tests dir + env.Depends(test_out, all_outputs) + env.ComponentTestOutput('run_' + prog_name, test_out) + + # Add target properties + env.SetTargetProperty(prog_name, RUN_TARGET='run_' + prog_name) + + +def ComponentTestProgram(self, prog_name, *args, **kwargs): + """Pseudo-builder for test program to handle platform-dependent type. + + Args: + self: Environment in which we were called. + prog_name: Test program name. + args: Positional arguments. + kwargs: Keyword arguments. + + Returns: + Output node list from env.Program(). + """ + # Clone and modify environment + env = _ComponentPlatformSetup(self, 'ComponentTestProgram', **kwargs) + + env['PROGRAM_BASENAME'] = prog_name + env['PROGRAM_NAME'] = '$PROGPREFIX$PROGRAM_BASENAME$PROGSUFFIX' + + # Call env.Program() + out_nodes = env.Program(prog_name, *args, **kwargs) + + # Add dependencies on includes + env.Depends(out_nodes, env['INCLUDES']) + + # Publish output + env.Publish(prog_name, 'run', out_nodes[0]) + env.Publish(prog_name, 'debug', out_nodes[1:]) + + # Add an alias to build the program to the right groups + a = env.Alias(prog_name, out_nodes) + for group in env['COMPONENT_TEST_PROGRAM_GROUPS']: + SCons.Script.Alias(group, a) + + # Store list of components for this program + env._StoreComponents(prog_name) + + # Let component_targets know this target is available in the current mode + env.SetTargetProperty(prog_name, TARGET_PATH=out_nodes[0]) + + # Set up deferred call to replicate resources and run test + env.Defer(ComponentTestProgramDeferred) + + # Return the output node + return out_nodes + +#------------------------------------------------------------------------------ + + +def ComponentProgramDeferred(env): + """Deferred build steps for program. + + Args: + env: Environment from ComponentProgram(). + + Sets up the aliases to compile the program. + """ + prog_name = env['PROGRAM_BASENAME'] + + # Install program and resources + all_outputs = [] + components = _RetrieveComponents(prog_name) + for resource, dest_dir in env.get('COMPONENT_PROGRAM_RESOURCES').items(): + all_outputs += env.ReplicatePublished(dest_dir, components, resource) + + # Add installed program and resources to the alias + env.Alias(prog_name, all_outputs) + + +def ComponentProgram(self, prog_name, *args, **kwargs): + """Pseudo-builder for program to handle platform-dependent type. + + Args: + self: Environment in which we were called. + prog_name: Test program name. + args: Positional arguments. + kwargs: Keyword arguments. + + Returns: + Output node list from env.Program(). + """ + # Clone and modify environment + env = _ComponentPlatformSetup(self, 'ComponentProgram', **kwargs) + + env['PROGRAM_BASENAME'] = prog_name + + # Call env.Program() + out_nodes = env.Program(prog_name, *args, **kwargs) + + # Add dependencies on includes + env.Depends(out_nodes, env['INCLUDES']) + + # Publish output + env.Publish(prog_name, 'run', out_nodes[0]) + env.Publish(prog_name, 'debug', out_nodes[1:]) + + # Add an alias to build the program to the right groups + a = env.Alias(prog_name, out_nodes) + for group in env['COMPONENT_PROGRAM_GROUPS']: + SCons.Script.Alias(group, a) + + # Store list of components for this program + env._StoreComponents(prog_name) + + # Let component_targets know this target is available in the current mode + env.SetTargetProperty(prog_name, TARGET_PATH=out_nodes[0]) + + # Set up deferred call to replicate resources + env.Defer(ComponentProgramDeferred) + + # Return the output nodes + return out_nodes + +#------------------------------------------------------------------------------ + + +def ComponentTestOutput(self, test_name, nodes, **kwargs): + """Pseudo-builder for test output. + + Args: + self: Environment in which we were called. + test_name: Test name. + nodes: List of files/Nodes output by the test. + kwargs: Keyword arguments. + + Returns: + Passthrough return code from env.Alias(). + """ + + # Clone and modify environment + env = _ComponentPlatformSetup(self, 'ComponentTestObject', **kwargs) + + # Add an alias for the test output + a = env.Alias(test_name, nodes) + + # Determine groups test belongs in + if env.get('COMPONENT_TEST_ENABLED'): + groups = env.SubstList2('$COMPONENT_TEST_OUTPUT_GROUPS') + if env.get('COMPONENT_TEST_SIZE'): + groups.append(env.subst('run_${COMPONENT_TEST_SIZE}_tests')) + else: + # Disabled tests only go in the explicit disabled tests group + groups = ['run_disabled_tests'] + + for group in groups: + SCons.Script.Alias(group, a) + + # Let component_targets know this target is available in the current mode + env.SetTargetProperty(test_name, TARGET_PATH=nodes[0]) + + # Return the output node + return a + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env.Replace( + LIB_DIR='$TARGET_ROOT/lib', + # TODO: Remove legacy COMPONENT_LIBRARY_DIR, once all users have + # transitioned to LIB_DIR + COMPONENT_LIBRARY_DIR='$LIB_DIR', + STAGING_DIR='$TARGET_ROOT/staging', + TESTS_DIR='$TARGET_ROOT/tests', + TEST_OUTPUT_DIR='$TARGET_ROOT/test_output', + # Default command line for a test is just the name of the file. + # TODO: Why doesn't the following work: + # COMPONENT_TEST_CMDLINE='${SOURCE.abspath}', + # (it generates a SCons error) + COMPONENT_TEST_CMDLINE='${PROGRAM_NAME}', + # Component tests are runnable by default. + COMPONENT_TEST_RUNNABLE=True, + # Default test size is large + COMPONENT_TEST_SIZE='large', + # Default timeouts for component tests + COMPONENT_TEST_TIMEOUT={'large': 900, 'medium': 450, 'small': 180}, + # Tests are enabled by default + COMPONENT_TEST_ENABLED=True, + # Static linking is a sensible default + COMPONENT_STATIC=True, + # Don't publish libraries to the staging dir by themselves by default. + COMPONENT_LIBRARY_PUBLISH=False, + ) + env.Append( + LIBPATH=['$LIB_DIR'], + RPATH=['$LIB_DIR'], + + # Default alias groups for component builders + COMPONENT_PACKAGE_GROUPS=['all_packages'], + COMPONENT_LIBRARY_GROUPS=['all_libraries'], + COMPONENT_PROGRAM_GROUPS=['all_programs'], + COMPONENT_TEST_PROGRAM_GROUPS=['all_test_programs'], + COMPONENT_TEST_OUTPUT_GROUPS=['run_all_tests'], + + # Additional components whose resources should be copied into program + # directories, in addition to those from LIBS and the program itself. + LIBS=[], + COMPONENTS=[], + + # Dicts of what resources should go in each destination directory for + # programs and test programs. + COMPONENT_PACKAGE_RESOURCES={ + 'run': '$PACKAGE_DIR', + 'debug': '$PACKAGE_DIR', + }, + COMPONENT_PROGRAM_RESOURCES={ + 'run': '$STAGING_DIR', + 'debug': '$STAGING_DIR', + }, + COMPONENT_TEST_RESOURCES={ + 'run': '$TESTS_DIR', + 'debug': '$TESTS_DIR', + 'test_input': '$TESTS_DIR', + }, + ) + + # Add command line option for retest + SCons.Script.AddOption( + '--retest', + dest='component_test_retest', + action='store_true', + help='force all tests to rerun') + SCons.Script.Help(' --retest ' + 'Rerun specified tests, ignoring cached results.\n') + + # Defer per-environment initialization, but do before building SConscripts + env.Defer(_InitializeComponentBuilders) + env.Defer('BuildEnvironmentSConscripts', after=_InitializeComponentBuilders) + + # Add our pseudo-builder methods + env.AddMethod(_StoreComponents) + env.AddMethod(ComponentPackage) + env.AddMethod(ComponentObject) + env.AddMethod(ComponentLibrary) + env.AddMethod(ComponentProgram) + env.AddMethod(ComponentTestProgram) + env.AddMethod(ComponentTestOutput) + + # Add our target groups + AddTargetGroup('all_libraries', 'libraries can be built') + AddTargetGroup('all_programs', 'programs can be built') + AddTargetGroup('all_test_programs', 'tests can be built') + AddTargetGroup('all_packages', 'packages can be built') + AddTargetGroup('run_all_tests', 'tests can be run') + AddTargetGroup('run_disabled_tests', 'tests are disabled') + AddTargetGroup('run_small_tests', 'small tests can be run') + AddTargetGroup('run_medium_tests', 'medium tests can be run') + AddTargetGroup('run_large_tests', 'large tests can be run') + diff --git a/o3d/site_scons/site_tools/component_setup.py b/o3d/site_scons/site_tools/component_setup.py new file mode 100644 index 0000000..a3be218 --- /dev/null +++ b/o3d/site_scons/site_tools/component_setup.py @@ -0,0 +1,270 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Main setup for software construction toolkit. + +This module is a SCons tool which should be include in all environments. +It is used as follows: + env = Environment(tools = ['component_setup']) +and should be the first tool from this toolkit referenced by any environment. +""" + +import os +import sys +import SCons +import usage_log + + +#------------------------------------------------------------------------------ + + +def InstallUsingLink(target, source, env): + """Install function for environment which uses link in preference to copy. + + Args: + target: Destintion filename + source: Source filename + env: Environment + + Returns: + Return code from SCons Node link function. + """ + + # Use link function for Install() and InstallAs(), since it's much much + # faster than copying. This is ok for the way we build clients, where we're + # installing to a build output directory and not to a permanent location such + # as /usr/bin. + # Need to force the target and source to be lists of nodes + return SCons.Node.FS.LinkFunc([env.Entry(target)], [env.Entry(source)], env) + + +def PreEvaluateVariables(env): + """Deferred function to pre-evaluate SCons varables for each build mode. + + Args: + env: Environment for the current build mode. + """ + # Convert directory variables to strings. Must use .abspath not str(), since + # otherwise $OBJ_ROOT is converted to a relative path, which evaluates + # improperly in SConscripts not in $MAIN_DIR. + for var in env.SubstList2('$PRE_EVALUATE_DIRS'): + env[var] = env.Dir('$' + var).abspath + + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Use MD5 to tell when files differ, if the timestamps differ. This is + # better than pure MD5 (since if the timestamps are the same, we don't need + # to rescan the file), and also better than pure timestamp (since if a file + # is rebuilt to the same contents, we don't need to trigger the build steps + # which depend on it). + env.Decider('MD5-timestamp') + + # For duplication order, use hard links then fall back to copying. Don't use + # soft links, since those won't do the right thing if the output directory + # is tar'd up and moved elsewhere. + SCons.Script.SetOption('duplicate', 'hard-copy') + + # Remove the alias namespace lookup function from the list which SCons uses + # when coercing strings into nodes. This prevents SCons from looking up + # aliases in input/output lists if they're not explicitly coerced via + # Alias(), and removes a conflict where a program has the same shorthand + # alias as the program name itself. This conflict manifests itself as a + # python exception if you try to build a program in multiple modes on linux, + # for example: + # hammer --mode=dbg,opt port_test + new_lookup_list = [] + for func in env.lookup_list: + if func.im_class != SCons.Node.Alias.AliasNameSpace: + new_lookup_list.append(func) + env.lookup_list = new_lookup_list + + # Add command line options + SCons.Script.AddOption( + '--brief', + dest='brief_comstr', + default=True, + action='store_true', + help='brief command line output') + SCons.Script.AddOption( + '--verbose', + dest='brief_comstr', + default=True, + action='store_false', + help='verbose command line output') + + # Add help for command line options + SCons.Script.Help("""\ + --verbose Print verbose output while building, including + the full command lines for all commands. + --brief Print brief output while building (the default). + This and --verbose are opposites. Use --silent + to turn off all output. +""") + + # Cover part of the environment + env.Replace( + # Add a reference to our python executable, so subprocesses can find and + # invoke python. + PYTHON = env.File(sys.executable), + + # Get the absolute path to the directory containing main.scons (or + # SConstruct). This should be used in place of the SCons variable '#', + # since '#' is not always replaced (for example, when being used to set + # an environment variable). + MAIN_DIR = env.Dir('#').abspath, + # Supply deprecated SCONSTRUCT_DIR for legacy suport + # TODO: remove legacy support once everyone has switched over. + SCONSTRUCT_DIR = env.Dir('#').abspath, + + # Use install function above, which uses links in preference to copying. + INSTALL = InstallUsingLink, + ) + + # Specify defaults for variables where we don't need to force replacement + env.SetDefault( + # Environments are in the 'all' group by default + BUILD_GROUPS=['all'], + + # Directories + DESTINATION_ROOT='$MAIN_DIR/scons-out$HOST_PLATFORM_SUFFIX', + TARGET_ROOT='$DESTINATION_ROOT/$BUILD_TYPE', + OBJ_ROOT='$TARGET_ROOT/obj', + ARTIFACTS_DIR='$TARGET_ROOT/artifacts', + ) + + # Add default list of variables we should pre-evaluate for each build mode + env.Append(PRE_EVALUATE_DIRS = [ + 'ARTIFACTS_DIR', + 'DESTINATION_ROOT', + 'OBJ_ROOT', + 'SOURCE_ROOT', + 'TARGET_ROOT', + 'TOOL_ROOT', + ]) + + # If a host platform was specified on the command line, need to put the SCons + # output in its own destination directory. + force_host_platform = SCons.Script.GetOption('host_platform') + if force_host_platform: + env['HOST_PLATFORM_SUFFIX'] = '-' + force_host_platform + + # Put the .sconsign.dblite file in our destination root directory, so that we + # don't pollute the source tree. Use the '_' + sys.platform suffix to prevent + # the .sconsign.dblite from being shared between host platforms, even in the + # case where the --host_platform option is not used (for instance when the + # project has platform suffixes on all the build types). + # + # This will prevent host platforms from mistakenly using each other's + # .sconsign databases and will allow two host platform builds to occur in the + # same # shared tree simulataneously. + # + # Note that we use sys.platform here rather than HOST_PLATFORM, since we need + # different sconsign databases for cygwin vs. win32. + sconsign_dir = env.Dir('$DESTINATION_ROOT').abspath + sconsign_filename = '$DESTINATION_ROOT/.sconsign_%s' % sys.platform + sconsign_file = env.File(sconsign_filename).abspath + # SConsignFile() doesn't seem to like it if the destination directory + # doesn't already exist, so make sure it exists. + # TODO: Remove once SCons has fixed this bug. + if not os.path.isdir(sconsign_dir): + os.makedirs(sconsign_dir) + SCons.Script.SConsignFile(sconsign_file) + + # Build all by default + # TODO: This would be more nicely done by creating an 'all' alias and mapping + # that to $DESTINATION_ROOT (or the accumulation of all $TARGET_ROOT's for + # the environments which apply to the current host platform). Ideally, that + # would be done in site_init.py and not here. But since we can't do that, + # just set the default to be DESTINATION_ROOT here. Note that this currently + # forces projects which want to override the default to do so after including + # the component_setup tool (reasonable, since component_setup should pretty + # much be the first thing in a SConstruct. + env.Default('$DESTINATION_ROOT') + + # Use brief command line strings if necessary + if env.GetOption('brief_comstr'): + env.SetDefault( + ARCOMSTR='________Creating library $TARGET', + ASCOMSTR='________Assembling $TARGET', + CCCOMSTR='________Compiling $TARGET', + CONCAT_SOURCE_COMSTR='________ConcatSource $TARGET', + CXXCOMSTR='________Compiling $TARGET', + LDMODULECOMSTR='________Building loadable module $TARGET', + LINKCOMSTR='________Linking $TARGET', + MANIFEST_COMSTR='________Updating manifest for $TARGET', + MIDLCOMSTR='________Compiling IDL $TARGET', + PCHCOMSTR='________Precompiling $TARGET', + RANLIBCOMSTR='________Indexing $TARGET', + RCCOMSTR='________Compiling resource $TARGET', + SHCCCOMSTR='________Compiling $TARGET', + SHCXXCOMSTR='________Compiling $TARGET', + SHLINKCOMSTR='________Linking $TARGET', + SHMANIFEST_COMSTR='________Updating manifest for $TARGET', + ) + + # Add other default tools from our toolkit + # TODO: Currently this needs to be before SOURCE_ROOT in case a tool needs to + # redefine it. Need a better way to handle order-dependency in tool setup. + for t in component_setup_tools: + env.Tool(t) + + # The following environment replacements use env.Dir() to force immediate + # evaluation/substitution of SCons variables. They can't be part of the + # preceding env.Replace() since they they may rely indirectly on variables + # defined there, and the env.Dir() calls would be evaluated before the + # env.Replace(). + + # Set default SOURCE_ROOT if there is none, assuming we're in a local + # site_scons directory for the project. + source_root_relative = os.path.normpath( + os.path.join(os.path.dirname(__file__), '../..')) + source_root = env.get('SOURCE_ROOT', source_root_relative) + env['SOURCE_ROOT'] = env.Dir(source_root).abspath + + usage_log.log.SetParam('component_setup.project_path', + env.RelativePath('$SOURCE_ROOT', '$MAIN_DIR')) + + # Make tool root separate from source root so it can be overridden when we + # have a common location for tools outside of the current clientspec. Need + # to check if it's defined already, so it can be set prior to this tool + # being included. + tool_root = env.get('TOOL_ROOT', '$SOURCE_ROOT') + env['TOOL_ROOT'] = env.Dir(tool_root).abspath + + # Defer pre-evaluating some environment variables, but do before building + # SConscripts. + env.Defer(PreEvaluateVariables) + env.Defer('BuildEnvironmentSConscripts', after=PreEvaluateVariables) diff --git a/o3d/site_scons/site_tools/component_targets.py b/o3d/site_scons/site_tools/component_targets.py new file mode 100644 index 0000000..582f6b9 --- /dev/null +++ b/o3d/site_scons/site_tools/component_targets.py @@ -0,0 +1,269 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Software construction toolkit target management for SCons.""" + + +import __builtin__ +import SCons.Script + + +# Dict of target groups (TargetGroup indexed by group name) +__target_groups = {} + +# Dict of targets (Target indexed by target name) +__targets = {} + +# Dict of target modes (TargetMode indexed by mode name) +__target_modes = {} + +#------------------------------------------------------------------------------ + + +class TargetGroup(object): + """Target group, as used by AddTargetGroup() and GetTargetGroups().""" + + def __init__(self, name, description): + """Initializes the target group. + + Args: + name: Name of the target group. + description: Description of group. + """ + self.name = name + self.description = description + + def GetTargetNames(self): + """Returns a list of target name strings for the group.""" + items = map(str, SCons.Script.Alias(self.name)[0].sources) + # Remove duplicates from multiple environments + return list(set(items)) + +#------------------------------------------------------------------------------ + + +class TargetMode(object): + """Target mode, as used by GetTargetModes().""" + + def __init__(self, name, description): + """Initializes the target mode. + + Args: + name: Name of the target mode. + description: Description of mode. + """ + self.name = name + self.description = description + + def GetTargetNames(self): + """Returns a list of target name strings for the group.""" + items = map(str, SCons.Script.Alias(self.name)[0].sources) + # Remove duplicates from multiple environments + return list(set(items)) + +#------------------------------------------------------------------------------ + + +class Target(object): + """Target object.""" + + def __init__(self, name): + """Initializes the target. + + Args: + name: Name of the target. + """ + self.name = name + self.properties = {} # Global properties + self.mode_properties = {} # Dict of modes to mode-specific properties + +#------------------------------------------------------------------------------ + + +def AddTargetGroup(name, description): + """Adds a target group, used for printing help. + + Args: + name: Name of target group. This should be the name of an alias which + points to other aliases for the specific targets. + description: Description of the target group. Should read properly when + appended to 'The following ' - for example, 'programs can be built'. + """ + + # Warn if the target group already exists with a different description + if (name in __target_groups + and __target_groups[name].description != description): + print ('Warning: Changing description of target group "%s" from "%s" to ' + '"%s"' % (name, __target_groups[name].description, description)) + __target_groups[name].description = description + else: + __target_groups[name] = TargetGroup(name, description) + + +def GetTargetGroups(): + """Gets the dict of target groups. + + Returns: + The dict of target groups, indexed by group name. + + This dict is not fully populated until after BuildEnvironments() has been + called. + """ + return __target_groups + + +def GetTargetModes(): + """Gets the dict of target modes. + + Returns: + The dict of target modes, indexed by mode name. + + This dict is not fully populated until after BuildEnvironments() has been + called. + """ + # TODO: Better to rename this to # GetTargetBuildEnvironments()? That's a + # more description name. + return __target_modes + + +def GetTargets(): + """Gets the dict of targets. + + Returns: + The dict of targets, indexed by target name. + + This dict is not fully populated until after BuildEnvironments() has been + called. + """ + return __targets + + +def SetTargetProperty(self, target_name, all_modes=False, **kwargs): + """Sets one or more properties for a target. + + Args: + self: Environment context. + target_name: Name of the target. + all_modes: If True, property applies to all modes. If false, it applies + only to the current mode (determined by self['BUILD_TYPE']). + kwargs: Keyword args are used to set properties. Properties will be + converted to strings via env.subst(). + + For example: + foo_test = env.Program(...)[0] + env.SetTargetProperty('foo_test', global=True, DESCRIPTION='Foo test') + env.SetTargetProperty('foo_test', EXE=foo_test) + """ + # Get the target + if target_name not in __targets: + __targets[target_name] = Target(target_name) + target = __targets[target_name] + + if all_modes: + add_to_dict = target.properties + else: + mode = self.get('BUILD_TYPE') + if mode not in target.mode_properties: + target.mode_properties[mode] = {} + add_to_dict = target.mode_properties[mode] + + # Add values + for k, v in kwargs.items(): + add_to_dict[k] = self.subst(str(v)) + + +def AddTargetHelp(): + """Adds SCons help for the targets, groups, and modes. + + This is called automatically by BuildEnvironments().""" + help_text = '' + + for group in GetTargetGroups().values(): + items = group.GetTargetNames() + items.sort() + if items: + help_text += '\nThe following %s:' % group.description + colwidth = max(map(len, items)) + 2 + cols = 77 / colwidth + if cols < 1: + cols = 1 # If target names are really long, one per line + rows = (len(items) + cols - 1) / cols + for row in range(0, rows): + help_text += '\n ' + for i in range(row, len(items), rows): + help_text += '%-*s' % (colwidth, items[i]) + help_text += '\n %s (do all of the above)\n' % group.name + + SCons.Script.Help(help_text) + + +def SetTargetDescription(self, target_name, description): + """Convenience function to set a target's global DESCRIPTION property. + + Args: + self: Environment context. + target_name: Name of the target. + description: Description of the target. + """ + self.SetTargetProperty(target_name, all_modes=True, DESCRIPTION=description) + + +def AddTargetMode(env): + """Adds the environment as a target mode. + + Args: + env: Environment context. + + Called via env.Defer() for each build mode. + """ + # Save the build mode and description + mode = env.get('BUILD_TYPE') + __target_modes[mode] = TargetMode(mode, env.get('BUILD_TYPE_DESCRIPTION')) + + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + env = env # Silence gpylint + + __builtin__.AddTargetGroup = AddTargetGroup + __builtin__.AddTargetHelp = AddTargetHelp + __builtin__.GetTargetGroups = GetTargetGroups + __builtin__.GetTargetModes = GetTargetModes + __builtin__.GetTargets = GetTargets + + env.AddMethod(SetTargetDescription) + env.AddMethod(SetTargetProperty) + + # Defer per-mode setup + env.Defer(AddTargetMode) diff --git a/o3d/site_scons/site_tools/component_targets_msvs.py b/o3d/site_scons/site_tools/component_targets_msvs.py new file mode 100644 index 0000000..78d5b85 --- /dev/null +++ b/o3d/site_scons/site_tools/component_targets_msvs.py @@ -0,0 +1,999 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Visual Studio solution output of component targets for SCons.""" + +import copy +import md5 +import os +import sys +import xml.dom +import xml.dom.minidom +import SCons + + +#------------------------------------------------------------------------------ + + +def MakeGuid(name, seed='component_targets_msvs'): + """Returns a GUID for the specified target name. + + Args: + name: Target name. + seed: Seed for MD5 hash. + Returns: + A GUID-line string calculated from the name and seed. + + This generates something which looks like a GUID, but depends only on the + name and seed. This means the same name/seed will always generate the same + GUID, so that projects and solutions which refer to each other can explicitly + determine the GUID to refer to explicitly. It also means that the GUID will + not change when the project for a target is rebuilt. + """ + # Calculate a MD5 signature for the seed and name. + d = md5.new(str(seed) + str(name)).hexdigest().upper() + # Convert most of the signature to GUID form (discard the rest) + guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20] + + '-' + d[20:32] + '}') + return guid + +#------------------------------------------------------------------------------ + + +def GetGuidAndNameFromVSProject(project_path): + """Reads the GUID from a Visual Studio project file. + + Args: + project_path: Path to the Visual Studio project file. + + Returns: + The GUID string from the file. + The project name from the file. + """ + doc = xml.dom.minidom.parse(project_path) + try: + n_root = doc.documentElement + if n_root.nodeName != 'VisualStudioProject': + raise SCons.Errors.UserError('%s is not a Visual Studio project.' % + project_path) + return ( + str(n_root.attributes['ProjectGUID'].nodeValue), + str(n_root.attributes['Name'].nodeValue), + ) + finally: + # Clean up doc + doc.unlink() + +#------------------------------------------------------------------------------ + + +class VSProjectWriter(object): + """Visual Studio XML project writer.""" + + def __init__(self, project_path): + """Initializes the project. + + Args: + project_path: Path to the project file. + """ + self.project_path = project_path + self.doc = None + + def Create(self, name): + """Creates the project document. + + Args: + name: Name of the project. + """ + self.name = name + self.guid = MakeGuid(name) + + # Create XML doc + xml_impl = xml.dom.getDOMImplementation() + self.doc = xml_impl.createDocument(None, 'VisualStudioProject', None) + + # Add attributes to root element + self.n_root = self.doc.documentElement + self.n_root.setAttribute('ProjectType', 'Visual C++') + self.n_root.setAttribute('Version', '8.00') + self.n_root.setAttribute('Name', self.name) + self.n_root.setAttribute('ProjectGUID', self.guid) + self.n_root.setAttribute('RootNamespace', self.name) + self.n_root.setAttribute('Keyword', 'MakeFileProj') + + # Add platform list + n_platform = self.doc.createElement('Platforms') + self.n_root.appendChild(n_platform) + n = self.doc.createElement('Platform') + n.setAttribute('Name', 'Win32') + n_platform.appendChild(n) + + # Add empty ToolFiles section + self.n_root.appendChild(self.doc.createElement('ToolFiles')) + + # Add configurations section + self.n_configs = self.doc.createElement('Configurations') + self.n_root.appendChild(self.n_configs) + + # Add files section + self.n_files = self.doc.createElement('Files') + self.n_root.appendChild(self.n_files) + + # Add empty Globals section + self.n_root.appendChild(self.doc.createElement('Globals')) + + def AddConfig(self, name, attrs, tool_attrs): + """Adds a configuration to the project. + + Args: + name: Configuration name. + attrs: Dict of configuration attributes. + tool_attrs: Dict of tool attributes. + """ + # Add configuration node + n_config = self.doc.createElement('Configuration') + n_config.setAttribute('Name', '%s|Win32' % name) + n_config.setAttribute('ConfigurationType', '0') + for k, v in attrs.items(): + n_config.setAttribute(k, v) + self.n_configs.appendChild(n_config) + + # Add tool node + n_tool = self.doc.createElement('Tool') + n_tool.setAttribute('Name', 'VCNMakeTool') + n_tool.setAttribute('IncludeSearchPath', '') + n_tool.setAttribute('ForcedIncludes', '') + n_tool.setAttribute('AssemblySearchPath', '') + n_tool.setAttribute('ForcedUsingAssemblies', '') + n_tool.setAttribute('CompileAsManaged', '') + n_tool.setAttribute('PreprocessorDefinitions', '') + for k, v in tool_attrs.items(): + n_tool.setAttribute(k, v) + n_config.appendChild(n_tool) + + def _WalkFolders(self, folder_dict, parent): + """Recursively walks the folder tree. + + Args: + folder_dict: Dict of folder entries. Entry is + either subdir_name:subdir_dict or relative_path_to_file:None. + parent: Parent node (folder node for that dict) + """ + entries = folder_dict.keys() + entries.sort() + for e in entries: + if folder_dict[e]: + # Folder + n_subfolder = self.doc.createElement('Filter') + n_subfolder.setAttribute('Name', e) + parent.appendChild(n_subfolder) + self._WalkFolders(folder_dict[e], n_subfolder) + else: + # File + n_file = self.doc.createElement('File') + n_file.setAttribute('RelativePath', e) + parent.appendChild(n_file) + + def AddFiles(self, name, files_dict): + """Adds files to the project. + + Args: + name: Name of the folder. If None, files/folders will be added directly + to the files list. + files_dict: A dict of files / folders. + + Within the files_dict: + * A file entry is relative_path:None + * A folder entry is folder_name:files_dict, where files_dict is another + dict of this form. + """ + # Create subfolder if necessary + if name: + n_folder = self.doc.createElement('Filter') + n_folder.setAttribute('Name', name) + self.n_files.appendChild(n_folder) + else: + n_folder = self.n_files + + # Recursively add files to the folder + self._WalkFolders(files_dict, n_folder) + + def Write(self): + """Writes the project file.""" + f = open(self.project_path, 'wt') + self.doc.writexml(f, encoding='Windows-1252', addindent=' ', newl='\n') + f.close() + +#------------------------------------------------------------------------------ + + +def ComponentVSProjectBuilder(target, source, env): + """Visual Studio project builder. + + Args: + target: Destination file. + source: List of sources to be added to the target. + env: Environment context. + + Returns: + Zero if successful. + """ + source = source # Silence gpylint + + target_name = env['TARGET_NAME'] + project_file = target[0].path + project_to_main = env.RelativePath(target[0].dir, env.Dir('$MAIN_DIR'), + sep='/') + + env_hammer_bat = env.Clone(VS_PROJECT_TO_MAIN_DIR=project_to_main) + hammer_bat = env_hammer_bat.subst('$COMPONENT_VS_PROJECT_SCRIPT_PATH', raw=1) + + vsp = VSProjectWriter(project_file) + vsp.Create(target_name) + + # Add configuration per build mode supported by this target + target_path = env['TARGET_PATH'] + for mode, path in target_path.items(): + attrs = {} + attrs['OutputDirectory'] = '$(ProjectDir)/%s/%s/out' % (mode, target_name) + attrs['IntermediateDirectory'] = ('$(ProjectDir)/%s/%s/tmp' % + (mode, target_name)) + + tool_attrs = {} + if path: + tool_attrs['Output'] = env.RelativePath(target[0].dir, + env.Entry(path), sep='/') + build_cmd = '%s --mode=%s %s' % (hammer_bat, mode, target_name) + clean_cmd = '%s --mode=%s -c %s' % (hammer_bat, mode, target_name) + tool_attrs['BuildCommandLine'] = build_cmd + tool_attrs['CleanCommandLine'] = clean_cmd + tool_attrs['ReBuildCommandLine'] = clean_cmd + ' && ' + build_cmd + + vsp.AddConfig(mode, attrs, tool_attrs) + + # TODO: Fill in files - at least, the .scons file invoking the target. + + # Write project + vsp.Write() + return 0 + + +def ComponentVSProject(self, target_name, **kwargs): + """Visual Studio project pseudo-builder for the specified target. + + Args: + self: Environment context. + target_name: Name of the target. + kwargs: Optional keyword arguments override environment variables in the + derived environment used to create the project. + + Returns: + A list of output nodes. + """ + # Builder only works on Windows + if sys.platform not in ('win32', 'cygwin'): + return [] + + # Clone environment and add keyword args + env = self.Clone() + for k, v in kwargs.items(): + env[k] = v + + # Save the target name + env['TARGET_NAME'] = target_name + + # Extract the target properties and save in the environment for use by the + # real builder. + t = GetTargets().get(target_name) + env['TARGET_PATH'] = {} + if t: + for mode, mode_properties in t.mode_properties.items(): + # Since the target path is what Visual Studio will run, use the EXE + # property in preference to TARGET_PATH. + target_path = mode_properties.get('EXE', + mode_properties.get('TARGET_PATH')) + env.Append(TARGET_PATH={mode: target_path}) + else: + # No modes declared for this target. Could be a custom alias created by + # a SConscript, rather than a component builder. Assume it can be built in + # all modes, but produces no output. + for mode in GetTargetModes(): + env.Append(TARGET_PATH={mode: None}) + + # Call the real builder + return env.ComponentVSProjectBuilder( + '$COMPONENT_VS_PROJECT_DIR/${TARGET_NAME}', []) + +#------------------------------------------------------------------------------ + + +class SourceWalker(object): + """Iterator for walking a node tree and collecting sources. + + This is depth-first, children are visited before the parent. The object + can be initialized with any node, and returns the next node on the descent + with each Next() call. + + This class does not get caught in node cycles caused, for example, by C + header file include loops. + + Based on SCons.Node.Walker. + """ + + def __init__(self, node, seen, print_interval = 1000): + """Initializes the source walker. + + Args: + node: Node to start walk from. + seen: Set to add seen nodes to, if not None. + print_interval: Interval in nodes examined at which to print a progress + indicator. + """ + self.interval = print_interval + # Put node on stack + self.stack = [node] + # Scan for node's children, then copy them to node.wkids + node.wkids = copy.copy(node.children(scan=1)) + # Keep track of nodes we've seen (returned) + if seen is None: + seen = set() + self.seen = seen + # Add node to history for cycle detection + self.history = set() + self.history.add(node) + # We've seen one node now + self.nodes_examined = 1 + self.unique_nodes = 1 + + + def Next(self): + """Get the next node for this walk of the tree. + + Returns: + The next node, or None if no more nodes. + + This function is intentionally iterative, not recursive, to sidestep any + issues of stack size limitations. + """ + while self.stack: + if self.stack[-1].wkids: + # Node has children we haven't examined, so iterate into the first + # child + node = self.stack[-1].wkids.pop(0) + if not self.stack[-1].wkids: + # No more children of this node + self.stack[-1].wkids = None + self.nodes_examined += 1 + if self.interval and not self.nodes_examined % self.interval: + self.PrintProgress() + if (node not in self.history) and (node not in self.seen): + # Haven't hit a cycle or a node we've already seen + node.wkids = copy.copy(node.children(scan=1)) + self.stack.append(node) + self.history.add(node) + else: + # Coming back from iterating, so return the next node on the stack. + node = self.stack.pop() + self.history.remove(node) + self.seen.add(node) + self.unique_nodes += 1 + return node + return None + + def PrintProgress(self): + """Prints a progress indicator.""" + print ' Examined %d nodes, found %d unique...' % ( + self.nodes_examined, self.unique_nodes) + + def WalkAll(self): + """Walks all nodes in the the tree.""" + while self.Next(): + pass + if self.interval: + self.PrintProgress() + + +def ComponentVSSourceProjectBuilder(target, source, env): + """Visual Studio source project builder. + + Args: + target: Destination file. + source: List of sources to be added to the target. + env: Environment context. + + Returns: + Zero if successful. + """ + source = source # Silence gpylint + + target_name = env['PROJECT_NAME'] + project_file = target[0].path + project_dir = target[0].dir + + # Get list of suffixes to include + suffixes = env.SubstList2('$COMPONENT_VS_SOURCE_SUFFIXES') + + # Convert source folders to absolute paths + folders = [] + for f in env['COMPONENT_VS_SOURCE_FOLDERS']: + # (folder name, folder abspath, dict of contents) + folders.append((f[0], env.Dir(f[1]).abspath, {})) + + # TODO: Additional enhancements: + # * Should be able to specify paths in folder name (i.e., foo/bar) and + # create the nested folder nodes ('foo' and 'bar') + # * Should be tolerant of a folder being specified more than once with + # the same name (probably necessary to support nested folder nodes anyway) + # Can probably accomplish both of those by creating a parent fodler dict and + # calling WalkFolders() only once. + # Create a temporary solution alias to point to all the targets, so we can + # make a single call to SourceWalker() + tmp_alias = env.Alias('vs_source_project_' + target_name, + map(env.Alias, env['COMPONENT_VS_SOURCE_TARGETS'])) + + # Scan all targets and add unique nodes to set of sources + print ' Scanning dependency tree ...' + all_srcs = set() + walker = SourceWalker(tmp_alias[0], all_srcs) + walker.WalkAll() + + # Walk all sources and build directory trees + print ' Building source tree...' + for n in all_srcs: + if not hasattr(n, 'rfile'): + continue # Not a file + n = n.rfile() + if not hasattr(n, 'isfile') or not n.isfile(): + continue # Not a file + if n.has_builder(): + continue # Not a leaf node + if n.suffix not in suffixes: + continue # Not a file type we include + + path = n.abspath + for f in folders: + if path.startswith(f[1]): + if f[0] is None: + # Folder name of None is a filter + break + relpath = path[len(f[1]) + 1:].split(os.sep) + folder_dict = f[2] + # Recursively add subdirs + for pathseg in relpath[:-1]: + if pathseg not in folder_dict: + folder_dict[pathseg] = {} + folder_dict = folder_dict[pathseg] + # Add file to last subdir. No dict, since this isn't a subdir + folder_dict[env.RelativePath(project_dir, path)] = None + break + + print ' Writing project file...' + + vsp = VSProjectWriter(project_file) + vsp.Create(target_name) + + # One configuration for all build modes + vsp.AddConfig('all', {}, {}) + + # Add files + for f in folders: + if f[0] is None: + continue # Skip filters + vsp.AddFiles(f[0], f[2]) + + vsp.Write() + return 0 + + +def ComponentVSSourceProject(self, project_name, target_names, **kwargs): + """Visual Studio source project pseudo-builder. + + Args: + self: Environment context. + project_name: Name of the project. + target_names: List of target names to include source for. + kwargs: Optional keyword arguments override environment variables in the + derived environment used to create the project. + + Returns: + A list of output nodes. + """ + # Builder only works on Windows + if sys.platform not in ('win32', 'cygwin'): + return [] + + # Clone environment and add keyword args + env = self.Clone() + for k, v in kwargs.items(): + env[k] = v + + # Save the project name and targets + env['PROJECT_NAME'] = project_name + env['COMPONENT_VS_SOURCE_TARGETS'] = target_names + + # Call the real builder + return env.ComponentVSSourceProjectBuilder( + '$COMPONENT_VS_PROJECT_DIR/${PROJECT_NAME}', []) + +#------------------------------------------------------------------------------ + + +def FindSources(env, dest, source, suffixes=None): + """Recursively finds sources and adds them to the destination set. + + Args: + env: Environment context. + dest: Set to add source nodes to. + source: Source file(s) to find. May be a string, Node, or a list of + mixed strings or Nodes. Strings will be passed through env.Glob() to + evaluate wildcards. If a source evaluates to a directory, the entire + directory will be recursively added. + suffixes: List of suffixes to include. If not None, only files with these + suffixes will be added to dest. + """ + for source_entry in env.Flatten(source): + if type(source_entry) == str: + # Search for matches for each source entry + source_nodes = env.Glob(source_entry) + else: + # Source entry is already a file or directory node; no need to glob it + source_nodes = [source_entry] + for s in source_nodes: + if str(s.__class__) == 'SCons.Node.FS.Dir': + # Recursively search subdir. Since glob('*') doesn't match dot files, + # also glob('.*'). + FindSources(env, dest, [s.abspath + '/*', s.abspath + '/.*'], suffixes) + elif suffixes and s.suffix in suffixes: + dest.add(s) + + +def ComponentVSDirProjectBuilder(target, source, env): + """Visual Studio directory project builder. + + Args: + target: Destination file. + source: List of sources to be added to the target. + env: Environment context. + + Returns: + Zero if successful. + """ + source = source # Silence gpylint + + target_name = env['PROJECT_NAME'] + project_file = target[0].path + project_dir = target[0].dir + + # Convert source folders to absolute paths + folders = [] + for f in env['COMPONENT_VS_SOURCE_FOLDERS']: + # (folder name, folder abspath, dict of contents) + folders.append((f[0], env.Dir(f[1]).abspath, {})) + + # Recursively scan source directories + print ' Scanning directories for source...' + all_srcs = set() + FindSources(env, all_srcs, env['PROJECT_SOURCES'], + suffixes=env.SubstList2('$COMPONENT_VS_SOURCE_SUFFIXES')) + + # Walk all sources and build directory trees + print ' Building source tree...' + for n in all_srcs: + # Map addRepository'd source to its real location. + path = n.rfile().abspath + for f in folders: + if path.startswith(f[1]): + if f[0] is None: + # Folder name of None is a filter + break + relpath = path[len(f[1]) + 1:].split(os.sep) + folder_dict = f[2] + # Recursively add subdirs + for pathseg in relpath[:-1]: + if pathseg not in folder_dict: + folder_dict[pathseg] = {} + folder_dict = folder_dict[pathseg] + # Add file to last subdir. No dict, since this isn't a subdir + folder_dict[env.RelativePath(project_dir, path)] = None + break + + print ' Writing project file...' + + vsp = VSProjectWriter(project_file) + vsp.Create(target_name) + + # One configuration for all build modes + vsp.AddConfig('all', {}, {}) + + # Add files + for f in folders: + if f[0] is None: + continue # Skip filters + vsp.AddFiles(f[0], f[2]) + + vsp.Write() + return 0 + + +def ComponentVSDirProject(self, project_name, source, **kwargs): + """Visual Studio directory project pseudo-builder. + + Args: + self: Environment context. + project_name: Name of the project. + source: List of source files and/or directories. + kwargs: Optional keyword arguments override environment variables in the + derived environment used to create the project. + + Returns: + A list of output nodes. + """ + # Builder only works on Windows + if sys.platform not in ('win32', 'cygwin'): + return [] + + # Clone environment and add keyword args + env = self.Clone() + for k, v in kwargs.items(): + env[k] = v + + # Save the project name and sources + env['PROJECT_NAME'] = project_name + env['PROJECT_SOURCES'] = source + + # Call the real builder + return env.ComponentVSDirProjectBuilder( + '$COMPONENT_VS_PROJECT_DIR/${PROJECT_NAME}', []) + +#------------------------------------------------------------------------------ + + +def ComponentVSSolutionBuilder(target, source, env): + """Visual Studio solution builder. + + Args: + target: Destination file. + source: List of sources to be added to the target. + env: Environment context. + + Returns: + Zero if successful. + """ + source = source # Silence gpylint + + solution_file = target[0].path + projects = env['SOLUTION_PROJECTS'] + folders = env['SOLUTION_FOLDERS'] + + # Scan externally-generated projects + external_projects = [] + for p in source: + guid, name = GetGuidAndNameFromVSProject(p.abspath) + external_projects.append((p, name, guid)) + + f = open(solution_file, 'wt') + f.write('Microsoft Visual Studio Solution File, Format Version 9.00\n') + f.write('# Visual Studio 2005\n') + + # Projects generated by ComponentVSSolution() + for p in projects: + project_file = env.File( + '$COMPONENT_VS_PROJECT_DIR/%s$COMPONENT_VS_PROJECT_SUFFIX' % p) + f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( + '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID + p, # Project name + env.RelativePath(target[0].dir, project_file), # Path to project file + MakeGuid(p), # Project GUID + )) + + # Projects generated elsewhere + for p, name, guid in external_projects: + f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( + # TODO: What if this project isn't type external makefile? + # How to tell what type it is? + '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID + name, # Project name + env.RelativePath(target[0].dir, p), # Path to project file + guid, # Project GUID + )) + + # Folders from build groups + # TODO: Currently no way to add external project (specified in sources) to a + # solution folder. + for folder in folders: + f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( + '{2150E333-8FDC-42A3-9474-1A3956D46DE8}', # Solution folder GUID + folder, # Folder name + folder, # Folder name (again) + # Use a different seed so the folder won't get the same GUID as a + # project. + MakeGuid(folder, seed='folder'), # Project GUID + )) + + f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') + for mode in GetTargetModes(): + f.write('\t\t%s|Win32 = %s|Win32\n' % (mode, mode)) + f.write('\tEndGlobalSection\n') + + # Determine which projects should be enabled + # TODO: This is somewhat clunky. DEFAULT_TARGETS is global, and what we + # really need is something mode-specific. In theory we could make this a + # mode-specific dict rather than a list, but that'd also be a pain to + # populate. + # These variable names are also getting REALLY long. Perhaps we should + # define shorter ones (with the default value redirecting to the longer + # ones for legacy compatibility). + enable_projects = env.SubstList2('$COMPONENT_VS_ENABLED_PROJECTS') + + f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') + + # Projects generated by ComponentVSSolution() + for p in projects: + for mode in GetTargetModes(): + f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % ( + MakeGuid(p), # Project GUID + mode, # Solution build configuration + mode, # Project build config for that solution config + )) + + t = GetTargets().get(p) + + # Determine if project should be enabled in this mode + enabled = t and mode in t.mode_properties + if enable_projects and p not in enable_projects: + # Enable list specified, but target isn't in it + # TODO: Since we env.Default(scons-out) elsewhere, this likely causes + # all projects to be disabled by default. But that's realistically + # better than enabling them all... + enabled = False + + if enabled: + # Target can be built in this mode + f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % ( + MakeGuid(p), # Project GUID + mode, # Solution build configuration + mode, # Project build config for that solution config + )) + + # Projects generated elsewhere + for p, name, guid in external_projects: + for mode in GetTargetModes(): + f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % ( + guid, # Project GUID + mode, # Solution build configuration + mode, # Project build config for that solution config + )) + + if name in enable_projects or not enable_projects: + # Build target in this mode + f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % ( + guid, # Project GUID + mode, # Solution build configuration + mode, # Project build config for that solution config + )) + + f.write('\tEndGlobalSection\n') + + f.write('\tGlobalSection(SolutionProperties) = preSolution\n') + f.write('\t\tHideSolutionNode = FALSE\n') + f.write('\tEndGlobalSection\n') + + if folders: + f.write('\tGlobalSection(NestedProjects) = preSolution\n') + for p, folder in projects.items(): + f.write('\t\t%s = %s\n' % (MakeGuid(p), MakeGuid(folder, seed='folder'))) + f.write('\tEndGlobalSection\n') + + f.write('EndGlobal\n') + f.close() + + return 0 + + +def ComponentVSSolution(self, solution_name, target_names, projects=None, + **kwargs): + """Visual Studio solution pseudo-builder. + + Args: + self: Environment context. + solution_name: Name of the solution. + target_names: Names of targets or target groups to include in the solution. + This will automatically build projects for them. + projects: List of aditional projects not generated by this solution to + include in the solution. + kwargs: Optional keyword arguments override environment variables in the + derived environment used to create the solution. + + Returns: + The list of output nodes. + """ + # TODO: Should have option to build source project also. Perhaps select + # using a --source_project option, since it needs to use gather_inputs + # to scan the DAG and will blow up the null build time. + # TODO: Should also have autobuild_projects option. If false, don't build + # them. + # TODO: Should also be able to specify a target group directly + # (i.e. 'run_all_tests') + + # Builder only works on Windows + if sys.platform not in ('win32', 'cygwin'): + return [] + + # Clone environment and add keyword args + env = self.Clone() + for k, v in kwargs.items(): + env[k] = v + + # Save the target name + env['SOLUTION_NAME'] = solution_name + + # Get list of targets to make projects for. At this point we haven't + # determined whether they're groups or targets. + target_names = env.SubstList2(target_names) + env['SOLUTION_TARGETS'] = target_names + + # Save the default targets list as an environment variable + env['COMPONENT_VS_SCONS_DEFAULT_TARGETS'] = SCons.Script.DEFAULT_TARGETS + + # Expand target_names into project names, and create project-to-folder + # mappings + project_names = {} + folders = [] + if target_names: + # Expand target_names into project names + for target in target_names: + if target in GetTargetGroups(): + # Add target to folders + folders.append(target) + # Add all project_names in the group + for t in GetTargetGroups()[target].GetTargetNames(): + project_names[t] = target + elif target in GetTargets(): + # Just a target name + project_names[target] = None + else: + print 'Warning: ignoring unknown target "%s"' % target + else: + # No target names specified, so use all projects + for t in GetTargets(): + project_names[t] = None + + env['SOLUTION_FOLDERS'] = folders + env['SOLUTION_PROJECTS'] = project_names + + # Call the real builder + out_nodes = env.ComponentVSSolutionBuilder( + '$COMPONENT_VS_SOLUTION_DIR/${SOLUTION_NAME}', projects or []) + + # Call the real builder for the projects we generate + for p in project_names: + out_nodes += env.ComponentVSProject(p) + + # Add the solution target + # TODO: Should really defer the rest of the work above, since otherwise we + # can't build a solution which has a target to rebuild itself. + env.Alias('all_solutions', env.Alias(solution_name, out_nodes)) + + # TODO: To rebuild the solution properly, need to override its project + # configuration so it only has '--mode=all' (or some other way of setting the + # subset of modes which it should use to rebuild itself). Rebuilding with + # the property below will strip it down to just the current build mode, which + # isn't what we want. + # Let component_targets know this target is available in the current mode + #self.SetTargetProperty(solution_name, TARGET_PATH=out_nodes[0]) + + return out_nodes + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add pseudo-builders to set up the project and solution builders. These + # need to be available on all platforms so that SConscripts which reference + # them will work. + env.AddMethod(ComponentVSDirProject) + env.AddMethod(ComponentVSProject) + env.AddMethod(ComponentVSSolution) + env.AddMethod(ComponentVSSourceProject) + + # If not on Windows, don't do anything else + if sys.platform not in ('win32', 'cygwin'): + return + + # Include tools we need + env.Tool('gather_inputs') + + env.SetDefault( + COMPONENT_VS_PROJECT_DIR='$COMPONENT_VS_SOLUTION_DIR/projects', + COMPONENT_VS_PROJECT_SCRIPT_NAME = 'hammer.bat', + COMPONENT_VS_PROJECT_SCRIPT_PATH = ( + '$$(ProjectDir)/$VS_PROJECT_TO_MAIN_DIR/' + '$COMPONENT_VS_PROJECT_SCRIPT_NAME'), + COMPONENT_VS_PROJECT_SUFFIX='.vcproj', + + COMPONENT_VS_SOLUTION_DIR='$DESTINATION_ROOT/solution', + COMPONENT_VS_SOLUTION_SUFFIX='.sln', + COMPONENT_VS_ENABLED_PROJECTS=['$COMPONENT_VS_SCONS_DEFAULT_TARGETS'], + + COMPONENT_VS_SOURCE_SUFFIXES=['$CPPSUFFIXES', '.rc', '.scons'], + COMPONENT_VS_SOURCE_FOLDERS=[('source', '$MAIN_DIR')], + ) + + AddTargetGroup('all_solutions', 'solutions can be built') + + # Add builders + vcprojaction = SCons.Script.Action(ComponentVSProjectBuilder, varlist=[ + 'COMPONENT_VS_PROJECT_SCRIPT_PATH', + 'TARGET_NAME', + 'TARGET_PATH', + ]) + vcprojbuilder = SCons.Script.Builder( + action=vcprojaction, + suffix='$COMPONENT_VS_PROJECT_SUFFIX') + + source_vcproj_action = SCons.Script.Action( + ComponentVSSourceProjectBuilder, varlist=[ + 'COMPONENT_VS_SOURCE_FOLDERS', + 'COMPONENT_VS_SOURCE_SUFFIXES', + 'COMPONENT_VS_SOURCE_TARGETS', + ]) + source_vcproj_builder = SCons.Script.Builder( + action=source_vcproj_action, + suffix='$COMPONENT_VS_PROJECT_SUFFIX') + + dir_vcproj_action = SCons.Script.Action( + ComponentVSDirProjectBuilder, varlist=[ + 'COMPONENT_VS_SOURCE_FOLDERS', + 'COMPONENT_VS_SOURCE_SUFFIXES', + 'PROJECT_SOURCES', + ]) + dir_vcproj_builder = SCons.Script.Builder( + action=dir_vcproj_action, + suffix='$COMPONENT_VS_PROJECT_SUFFIX') + + slnaction = SCons.Script.Action(ComponentVSSolutionBuilder, varlist=[ + 'COMPONENT_VS_ENABLED_PROJECTS', + 'SOLUTION_FOLDERS', + 'SOLUTION_PROJECTS', + 'SOLUTION_TARGETS', + ]) + slnbuilder = SCons.Script.Builder( + action=slnaction, + suffix='$COMPONENT_VS_SOLUTION_SUFFIX') + + env.Append(BUILDERS={ + 'ComponentVSDirProjectBuilder': dir_vcproj_builder, + 'ComponentVSProjectBuilder': vcprojbuilder, + 'ComponentVSSolutionBuilder': slnbuilder, + 'ComponentVSSourceProjectBuilder': source_vcproj_builder, + }) diff --git a/o3d/site_scons/site_tools/component_targets_xml.py b/o3d/site_scons/site_tools/component_targets_xml.py new file mode 100644 index 0000000..81f9f88 --- /dev/null +++ b/o3d/site_scons/site_tools/component_targets_xml.py @@ -0,0 +1,125 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""XML output of component targets for SCons.""" + + +import xml.dom +import SCons.Script + + +def TargetXMLHelp(target, source, env): + """Generates target information in XML format. + + Args: + target: Destination file. + source: List of sources. Should be empty, otherwise this will actually + require the sources to be built first. + env: Environment context. + """ + env = env + source = source # Silence gpylint + + target_path = target[0].abspath + + xml_impl = xml.dom.getDOMImplementation() + doc = xml_impl.createDocument(None, 'help', None) + + mode_list = doc.createElement('mode_list') + doc.documentElement.appendChild(mode_list) + for mode in GetTargetModes().values(): + n = doc.createElement('build_mode') + n.setAttribute('name', mode.name) + n.setAttribute('description', mode.description) + mode_list.appendChild(n) + + group_list = doc.createElement('target_groups') + doc.documentElement.appendChild(group_list) + for group in GetTargetGroups().values(): + items = group.GetTargetNames() + if not items: + continue + + ngroup = doc.createElement('target_group') + ngroup.setAttribute('name', group.name) + group_list.appendChild(ngroup) + + for i in items: + ntarget = doc.createElement('build_target') + ntarget.setAttribute('name', i) + ngroup.appendChild(ntarget) + + # Get properties for target, if any + target = GetTargets().get(i) + if target: + # All modes + for k, v in target.properties.items(): + n = doc.createElement('target_property') + n.setAttribute('name', k) + n.setAttribute('value', v) + ntarget.appendChild(n) + + # Mode-specific + for mode, mode_properties in target.mode_properties.items(): + nmode = doc.createElement('target_mode') + nmode.setAttribute('name', mode) + ntarget.appendChild(nmode) + + for k, v in mode_properties.items(): + n = doc.createElement('target_property') + n.setAttribute('name', k) + n.setAttribute('value', v) + nmode.appendChild(n) + + f = open(target_path, 'wt') + doc.writexml(f, encoding='UTF-8', addindent=' ', newl='\n') + f.close() + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + env = env # Silence gpylint + + SCons.Script.Help('''\ + targets_xml Write information on the build mode's targets to + targets.xml. Most useful with --mode=all. +''') + + # Add build target to generate help + p = env.Command('$DESTINATION_ROOT/targets.xml', [], + env.Action(TargetXMLHelp)) + + # Always build xml info if requested. + # TODO: Is there a better way to determine the xml info is up to date? + env.AlwaysBuild(p) + env.Alias('targets_xml', p) diff --git a/o3d/site_scons/site_tools/concat_source.py b/o3d/site_scons/site_tools/concat_source.py new file mode 100644 index 0000000..5b448c1 --- /dev/null +++ b/o3d/site_scons/site_tools/concat_source.py @@ -0,0 +1,130 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Source concatenation builder for SCons.""" + + +import SCons.Script + + +def ConcatSourceBuilder(target, source, env): + """ConcatSource builder. + + Args: + target: List of target nodes + source: List of source nodes + env: Environment in which to build + + Returns: + None if successful; 1 if error. + """ + if len(target) != 1: + print 'ERROR: multiple ConcatSource targets when 1 expected' + return 1 + + output_lines = [ + '// This file is auto-generated by the ConcatSource builder.'] + + for source_path in map(str, source): + if env.get('CC') == 'cl': + # Add message pragma for nicer progress indication when building with + # MSVC. + output_lines.append('#pragma message("--%s")' % ( + source_path.replace("\\", "/"))) + + output_lines.append('#include "%s"' % source_path) + + output_file = open(str(target[0]), 'w') + # Need an EOL at the end of the file for more finicky build tools + output_file.write('\n'.join(output_lines) + '\n') + output_file.close() + + +def ConcatSourcePseudoBuilder(self, target, source): + """ConcatSource pseudo-builder; calls builder or passes through source nodes. + + Args: + self: Environment in which to build + target: List of target nodes + source: List of source nodes + + Returns: + If self['CONCAT_SOURCE_ENABLE'], calls self.ConcatSource and returns + the list of target nodes. Otherwise, returns the list of source nodes. + Source nodes which are not CPP files are passed through unchanged to the + list of output nodes. + """ + if self.get('CONCAT_SOURCE_ENABLE', True): + # Scan down source list and separate CPP sources (which we concatenate) + # from other files (which we pass through). + cppsource = [] + outputs = [] + suffixes = self.Flatten(self.subst_list('$CONCAT_SOURCE_SUFFIXES')) + for source_file in self.arg2nodes(source): + if source_file.suffix in suffixes: + cppsource.append(source_file) + else: + outputs.append(source_file) + + if len(cppsource) > 1: + # More than one file, so concatenate them together + outputs += self.ConcatSourceBuilder(target, cppsource) + else: + # <2 files, so pass them through; no need for a ConcatSource target + outputs += cppsource + return outputs + else: + # ConcatSource is disabled, so pass through the list of source nodes. + return source + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add the builder + action = SCons.Script.Action(ConcatSourceBuilder, '$CONCAT_SOURCE_COMSTR', + varlist = ['CONCAT_SOURCE_SUFFIXES']) + builder = SCons.Script.Builder(action = action, suffix = '$CXXFILESUFFIX') + env.Append(BUILDERS={'ConcatSourceBuilder': builder}) + + env.SetDefault( + CONCAT_SOURCE_COMSTR = 'Creating ConcatSource $TARGET from $SOURCES', + # Suffixes of sources we can concatenate. Files not in this list will be + # passed through untouched. (Note that on Mac, Objective C/C++ files + # cannot be concatenated with regular C/C++ files.) + # TODO: Probably shouldn't mix C, C++ either... + CONCAT_SOURCE_SUFFIXES = ['.c', '.C', '.cxx', '.cpp', '.c++', '.cc', + '.h', '.H', '.hxx', '.hpp', '.hh'], + ) + + # Add a psuedo-builder method which can look at the environment to determine + # whether to call the ConcatSource builder or not + env.AddMethod(ConcatSourcePseudoBuilder, 'ConcatSource') diff --git a/o3d/site_scons/site_tools/defer.py b/o3d/site_scons/site_tools/defer.py new file mode 100644 index 0000000..878665a --- /dev/null +++ b/o3d/site_scons/site_tools/defer.py @@ -0,0 +1,322 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Defer tool for SCons.""" + + +import os +import sys +import types +import SCons.Errors + + +# Current group name being executed by ExecuteDefer(). Set to None outside +# of ExecuteDefer(). +_execute_defer_context = None + + +class DeferGroup: + """Named list of functions to be deferred.""" + # If we derive DeferGroup from object, instances of it return type + # <class 'defer.DeferGroup'>, which prevents SCons.Util.semi_deepcopy() + # from calling its __semi_deepcopy__ function. + # TODO: Make semi_deepcopy() capable of handling classes derived from + # object. + + def __init__(self): + """Initialize deferred function object.""" + self.func_env_cwd = [] + self.after = set() + + def __semi_deepcopy__(self): + """Makes a semi-deep-copy of this object. + + Returns: + A semi-deep-copy of this object. + + This means it copies the sets and lists contained by this object, but + doesn't make copies of the function pointers and environments pointed to by + those lists. + + Needed so env.Clone() makes a copy of the defer list, so that functions + and after-relationships subsequently added to the clone are not added to + the parent. + """ + c = DeferGroup() + c.func_env_cwd = self.func_env_cwd[:] + c.after = self.after.copy() + return c + + +def SetDeferRoot(self): + """Sets the current environment as the root environment for defer. + + Args: + self: Current environment context. + + Functions deferred by environments cloned from the root environment (that is, + function deferred by children of the root environment) will be executed when + ExecuteDefer() is called from the root environment. + + Functions deferred by environments from which the root environment was cloned + (that is, functions deferred by parents of the root environment) will be + passed the root environment instead of the original parent environment. + (Otherwise, they would have no way to determine the root environment.) + """ + # Set the current environment as the root for holding defer groups + self['_DEFER_ROOT_ENV'] = self + + # Deferred functions this environment got from its parents will be run in the + # new root context. + for group in GetDeferGroups(self).values(): + new_list = [(func, self, cwd) for (func, env, cwd) in group.func_env_cwd] + group.func_env_cwd = new_list + + +def GetDeferRoot(self): + """Returns the root environment for defer. + + Args: + self: Current environment context. + + Returns: + The root environment for defer. If one of this environment's parents + called SetDeferRoot(), returns that environment. Otherwise returns the + current environment. + """ + return self.get('_DEFER_ROOT_ENV', self) + + +def GetDeferGroups(env): + """Returns the dict of defer groups from the root defer environment. + + Args: + env: Environment context. + + Returns: + The dict of defer groups from the root defer environment. + """ + return env.GetDeferRoot()['_DEFER_GROUPS'] + + +def ExecuteDefer(self): + """Executes deferred functions. + + Args: + self: Current environment context. + """ + # Check for re-entrancy + global _execute_defer_context + if _execute_defer_context: + raise SCons.Errors.UserError('Re-entrant call to ExecuteDefer().') + + # Save directory, so SConscript functions can occur in the right subdirs + oldcwd = os.getcwd() + + # If defer root is set and isn't this environment, we're being called from a + # sub-environment. That's not where we should be called. + if self.GetDeferRoot() != self: + print ('Warning: Ignoring call to ExecuteDefer() from child of the ' + 'environment passed to SetDeferRoot().') + return + + # Get list of defer groups from ourselves. + defer_groups = GetDeferGroups(self) + + # Loop through deferred functions + try: + while defer_groups: + did_work = False + for name, group in defer_groups.items(): + if group.after.intersection(defer_groups.keys()): + continue # Still have dependencies + + # Set defer context + _execute_defer_context = name + + # Remove this group from the list of defer groups now, in case one of + # the functions it calls adds back a function into that defer group. + del defer_groups[name] + + if group.func_env_cwd: + # Run all the functions in our named group + for func, env, cwd in group.func_env_cwd: + os.chdir(cwd) + func(env) + + # The defer groups have been altered, so restart the search for + # functions that can be executed. + did_work = True + break + + if not did_work: + errmsg = 'Error in ExecuteDefer: dependency cycle detected.\n' + for name, group in defer_groups.items(): + errmsg += ' %s after: %s\n' % (name, group.after) + raise SCons.Errors.UserError(errmsg) + finally: + # No longer in a defer context + _execute_defer_context = None + + # Restore directory + os.chdir(oldcwd) + + +def PrintDefer(self, print_functions=True): + """Prints the current defer dependency graph. + + Args: + self: Environment in which PrintDefer() was called. + print_functions: Print individual functions in defer groups. + """ + # Get the defer dict + # Get list of defer groups from ourselves. + defer_groups = GetDeferGroups(self) + dgkeys = defer_groups.keys() + dgkeys.sort() + for k in dgkeys: + print ' +- %s' % k + group = defer_groups[k] + after = list(group.after) + if after: + print ' | after' + after.sort() + for a in after: + print ' | +- %s' % a + if print_functions and group.func_env_cwd: + print ' functions' + for func, env, cwd in group.func_env_cwd: + print ' | +- %s %s' % (func.__name__, cwd) + + +def Defer(self, *args, **kwargs): + """Adds a deferred function or modifies defer dependencies. + + Args: + self: Environment in which Defer() was called + args: Positional arguments + kwargs: Named arguments + + The deferred function will be passed the environment used to call Defer(), + and will be executed in the same working directory as the calling SConscript. + (Exception: if this environment is cloned and the clone calls SetDeferRoot() + and then ExecuteDefer(), the function will be passed the root environment, + instead of the environment used to call Defer().) + + All deferred functions run after all SConscripts. Additional dependencies + may be specified with the after= keyword. + + Usage: + + env.Defer(func) + # Defer func() until after all SConscripts + + env.Defer(func, after=otherfunc) + # Defer func() until otherfunc() runs + + env.Defer(func, 'bob') + # Defer func() until after SConscripts, put in group 'bob' + + env.Defer(func2, after='bob') + # Defer func2() until after all funcs in 'bob' group have run + + env.Defer(func3, 'sam') + # Defer func3() until after SConscripts, put in group 'sam' + + env.Defer('bob', after='sam') + # Defer all functions in group 'bob' until after all functions in group + # 'sam' have run. + + env.Defer(func4, after=['bob', 'sam']) + # Defer func4() until after all functions in groups 'bob' and 'sam' have + # run. + """ + # Get name of group to defer and/or the a function + name = None + func = None + for a in args: + if isinstance(a, str): + name = a + elif isinstance(a, types.FunctionType): + func = a + if func and not name: + name = func.__name__ + + # TODO: Why not allow multiple functions? Should be ok + + # Get list of names and/or functions this function should defer until after + after = [] + for a in self.Flatten(kwargs.get('after')): + if isinstance(a, str): + # TODO: Should check if '$' in a, and if so, subst() it and recurse into + # it. + after.append(a) + elif isinstance(a, types.FunctionType): + after.append(a.__name__) + elif a is not None: + # Deferring + raise ValueError('Defer after=%r is not a function or name' % a) + + # Find the deferred function + defer_groups = GetDeferGroups(self) + if name not in defer_groups: + defer_groups[name] = DeferGroup() + group = defer_groups[name] + + # If we were given a function, also save environment and current directory + if func: + group.func_env_cwd.append((func, self, os.getcwd())) + + # Add dependencies for the function + group.after.update(after) + + # If we are already inside a call to ExecuteDefer(), any functions which are + # deferring until after the current function must also be deferred until + # after this new function. In short, this means that if b() defers until + # after a() and a() calls Defer() to defer c(), then b() must also defer + # until after c(). + if _execute_defer_context and name != _execute_defer_context: + for other_name, other_group in GetDeferGroups(self).items(): + if other_name == name: + continue # Don't defer after ourselves + if _execute_defer_context in other_group.after: + other_group.after.add(name) + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + env.Append(_DEFER_GROUPS={}) + + env.AddMethod(Defer) + env.AddMethod(ExecuteDefer) + env.AddMethod(GetDeferRoot) + env.AddMethod(PrintDefer) + env.AddMethod(SetDeferRoot) diff --git a/o3d/site_scons/site_tools/directx_9_0_c.py b/o3d/site_scons/site_tools/directx_9_0_c.py new file mode 100644 index 0000000..3e883fe --- /dev/null +++ b/o3d/site_scons/site_tools/directx_9_0_c.py @@ -0,0 +1,40 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Windows DirectX 9.0c tool for SCons.""" + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env['DIRECTX9_DIR'] = '$DIRECTX9_0_C_DIR' + env.AppendENVPath('INCLUDE', env.Dir('$DIRECTX9_DIR/include').abspath) + env.AppendENVPath('LIB', env.Dir('$DIRECTX9_DIR/lib').abspath) diff --git a/o3d/site_scons/site_tools/directx_9_18_944_0_partial.py b/o3d/site_scons/site_tools/directx_9_18_944_0_partial.py new file mode 100644 index 0000000..38ff986 --- /dev/null +++ b/o3d/site_scons/site_tools/directx_9_18_944_0_partial.py @@ -0,0 +1,40 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Windows DirectX 9.18.944.0 tool for SCons.""" + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env['DIRECTX9_DIR'] = '$DIRECTX9_18_944_0_PARTIAL_DIR' + env.AppendENVPath('INCLUDE', env.Dir('$DIRECTX9_DIR/include').abspath) + env.AppendENVPath('LIB', env.Dir('$DIRECTX9_DIR/lib/x86').abspath) diff --git a/o3d/site_scons/site_tools/distcc.py b/o3d/site_scons/site_tools/distcc.py new file mode 100644 index 0000000..e7a8743 --- /dev/null +++ b/o3d/site_scons/site_tools/distcc.py @@ -0,0 +1,91 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Distcc support for SCons. + +Since this modifies the C compiler strings, it must be specified after the +compiler tool in the tool list. + +Distcc support can be enabled by specifying --distcc on the SCons command +line. +""" + + +import optparse +import os +import sys +from SCons.compat._scons_optparse import OptionConflictError +import SCons.Script + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + if not env.Detect('distcc'): + return + + try: + SCons.Script.AddOption( + '--distcc', + dest='distcc', + action='store_true', + help='enable distcc support') + SCons.Script.Help(' --distcc Enable distcc suport.\n') + + except (OptionConflictError, optparse.OptionConflictError): + # The distcc tool can be specified for multiple platforms, but the + # --distcc option can only be added once. Ignore the error which + # results from trying to add it a second time. + pass + + # If distcc isn't enabled, stop now + if not env.GetOption('distcc'): + return + + # Copy DISTCC_HOSTS and HOME environment variables from system environment + for envvar in ('DISTCC_HOSTS', 'HOME'): + value = env.get(envvar, os.environ.get(envvar)) + if not value: + print 'Warning: %s not set in environment; disabling distcc.' % envvar + return + env['ENV'][envvar] = value + + # Set name of distcc tool + env['DISTCC'] = 'distcc' + + # Modify compilers we support + distcc_compilers = env.get('DISTCC_COMPILERS', ['cc', 'gcc', 'c++', 'g++']) + for compiler_var in ('CC', 'CXX'): + compiler = env.get(compiler_var) + if compiler in distcc_compilers: + if sys.platform == 'darwin': + # On Mac, distcc requires the full path to the compiler + compiler = env.WhereIs(compiler) + env[compiler_var] = '$DISTCC ' + compiler diff --git a/o3d/site_scons/site_tools/environment_tools.py b/o3d/site_scons/site_tools/environment_tools.py new file mode 100644 index 0000000..41ae0a1 --- /dev/null +++ b/o3d/site_scons/site_tools/environment_tools.py @@ -0,0 +1,284 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Set up tools for environments for for software construction toolkit. + +This module is a SCons tool which should be include in all environments. It +will automatically be included by the component_setup tool. +""" + + +import os +import SCons + + +#------------------------------------------------------------------------------ + + +def FilterOut(self, **kw): + """Removes values from existing construction variables in an Environment. + + The values to remove should be a list. For example: + + self.FilterOut(CPPDEFINES=['REMOVE_ME', 'ME_TOO']) + + Args: + self: Environment to alter. + kw: (Any other named arguments are values to remove). + """ + + kw = SCons.Environment.copy_non_reserved_keywords(kw) + for key, val in kw.items(): + envval = self.get(key, None) + if envval is None: + # No existing variable in the environment, so nothing to delete. + continue + + for vremove in val: + # Use while not if, so we can handle duplicates. + while vremove in envval: + envval.remove(vremove) + + self[key] = envval + + # TODO: SCons.Environment.Append() has much more logic to deal with various + # types of values. We should handle all those cases in here too. (If + # variable is a dict, etc.) + +#------------------------------------------------------------------------------ + + +def Overlap(self, values1, values2): + """Checks for overlap between the values. + + Args: + self: Environment to use for variable substitution. + values1: First value(s) to compare. May be a string or list of strings. + values2: Second value(s) to compare. May be a string or list of strings. + + Returns: + The list of values in common after substitution, or an empty list if + the values do not overlap. + + Converts the values to a set of plain strings via self.SubstList2() before + comparison, so SCons $ variables are evaluated. + """ + set1 = set(self.SubstList2(values1)) + set2 = set(self.SubstList2(values2)) + return list(set1.intersection(set2)) + +#------------------------------------------------------------------------------ + + +def ApplySConscript(self, sconscript_file): + """Applies a SConscript to the current environment. + + Args: + self: Environment to modify. + sconscript_file: Name of SConscript file to apply. + + Returns: + The return value from the call to SConscript(). + + ApplySConscript() should be used when an existing SConscript which sets up an + environment gets too large, or when there is common setup between multiple + environments which can't be reduced into a parent environment which the + multiple child environments Clone() from. The latter case is necessary + because env.Clone() only enables single inheritance for environments. + + ApplySConscript() is NOT intended to replace the Tool() method. If you need + to add methods or builders to one or more environments, do that as a tool + (and write unit tests for them). + + ApplySConscript() is equivalent to the following SCons call: + SConscript(sconscript_file, exports={'env':self}) + + The called SConscript should import the 'env' variable to get access to the + calling environment: + Import('env') + + Changes made to env in the called SConscript will be applied to the + environment calling ApplySConscript() - that is, env in the called SConscript + is a reference to the calling environment. + + If you need to export multiple variables to the called SConscript, or return + variables from it, use the existing SConscript() function. + """ + return self.SConscript(sconscript_file, exports={'env': self}) + +#------------------------------------------------------------------------------ + + +def BuildSConscript(self, sconscript_file): + """Builds a SConscript based on the current environment. + + Args: + self: Environment to clone and pass to the called SConscript. + sconscript_file: Name of SConscript file to build. If this is a directory, + this method will look for sconscript_file+'/build.scons', and if that + is not found, sconscript_file+'/SConscript'. + + Returns: + The return value from the call to SConscript(). + + BuildSConscript() should be used when an existing SConscript which builds a + project gets too large, or when a group of SConscripts are logically related + but should not directly affect each others' environments (for example, a + library might want to build a number of unit tests which exist in + subdirectories, but not allow those tests' SConscripts to affect/pollute the + library's environment. + + BuildSConscript() is NOT intended to replace the Tool() method. If you need + to add methods or builders to one or more environments, do that as a tool + (and write unit tests for them). + + BuildSConscript() is equivalent to the following SCons call: + SConscript(sconscript_file, exports={'env':self.Clone()}) + or if sconscript_file is a directory: + SConscript(sconscript_file+'/build.scons', exports={'env':self.Clone()}) + + The called SConscript should import the 'env' variable to get access to the + calling environment: + Import('env') + + Changes made to env in the called SConscript will NOT be applied to the + environment calling BuildSConscript() - that is, env in the called SConscript + is a clone/copy of the calling environment, not a reference to that + environment. + + If you need to export multiple variables to the called SConscript, or return + variables from it, use the existing SConscript() function. + """ + # Need to look for the source node, since by default SCons will look for the + # entry in the variant_dir, which won't exist (and thus won't be a directory + # or a file). This isn't a problem in BuildComponents(), since the variant + # dir is only set inside its call to SConscript(). + if self.Entry(sconscript_file).srcnode().isdir(): + # Building a subdirectory, so look for build.scons or SConscript + script_file = sconscript_file + '/build.scons' + if not self.File(script_file).srcnode().exists(): + script_file = sconscript_file + '/SConscript' + else: + script_file = sconscript_file + + self.SConscript(script_file, exports={'env': self.Clone()}) + +#------------------------------------------------------------------------------ + + +def SubstList2(self, *args): + """Replacement subst_list designed for flags/parameters, not command lines. + + Args: + self: Environment context. + args: One or more strings or lists of strings. + + Returns: + A flattened, substituted list of strings. + + SCons's built-in subst_list evaluates (substitutes) variables in its + arguments, and returns a list of lists (one per positional argument). Since + it is designed for use in command line expansion, the list items are + SCons.Subst.CmdStringHolder instances. These instances can't be passed into + env.File() (or subsequent calls to env.subst(), either). The returned + nested lists also need to be flattened via env.Flatten() before the caller + can iterate over the contents. + + SubstList2() does a subst_list, flattens the result, then maps the flattened + list to strings. + + It is better to do: + for x in env.SubstList2('$MYPARAMS'): + than to do: + for x in env.get('MYPARAMS', []): + and definitely better than: + for x in env['MYPARAMS']: + which will throw an exception if MYPARAMS isn't defined. + """ + return map(str, self.Flatten(self.subst_list(args))) + + +#------------------------------------------------------------------------------ + + +def RelativePath(self, source, target, sep=os.sep, source_is_file=False): + """Calculates the relative path from source to target. + + Args: + self: Environment context. + source: Source path or node. + target: Target path or node. + sep: Path separator to use in returned relative path. + source_is_file: If true, calculates the relative path from the directory + containing the source, rather than the source itself. Note that if + source is a node, you can pass in source.dir instead, which is shorter. + + Returns: + The relative path from source to target. + """ + # Split source and target into list of directories + source = self.Entry(str(source)) + if source_is_file: + source = source.dir + source = source.abspath.split(os.sep) + target = self.Entry(str(target)).abspath.split(os.sep) + + # Handle source and target identical + if source == target: + if source_is_file: + return source[-1] # Bare filename + else: + return '.' # Directory pointing to itself + + # TODO: Handle UNC paths and drive letters (fine if they're the same, but if + # they're different, there IS no relative path) + + # Remove common elements + while source and target and source[0] == target[0]: + source.pop(0) + target.pop(0) + # Join the remaining elements + return sep.join(['..'] * len(source) + target) + + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add methods to environment + env.AddMethod(ApplySConscript) + env.AddMethod(BuildSConscript) + env.AddMethod(FilterOut) + env.AddMethod(Overlap) + env.AddMethod(RelativePath) + env.AddMethod(SubstList2) diff --git a/o3d/site_scons/site_tools/gather_inputs.py b/o3d/site_scons/site_tools/gather_inputs.py new file mode 100644 index 0000000..2e9ea7a --- /dev/null +++ b/o3d/site_scons/site_tools/gather_inputs.py @@ -0,0 +1,121 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Input gathering tool for SCons.""" + + +import re +import SCons.Script + + +def GatherInputs(env, target, groups=['.*'], exclude_pattern=None): + """Find all (non-generated) input files used for a target. + + Args: + target: a target node to find source files for + For example: File('bob.exe') + groups: a list of patterns to use as categories + For example: ['.*\\.c$', '.*\\.h$'] + exclude_pattern: a pattern to exclude from the search + For example: '.*third_party.*' + Returns: + A list of lists of files for each category. + Each file will be placed in the first category which matches, + even if categories overlap. + For example: + [['bob.c', 'jim.c'], ['bob.h', 'jim.h']] + """ + + # Compile exclude pattern if any + if exclude_pattern: + exclude_pattern = re.compile(exclude_pattern) + + def _FindSources(ptrns, tgt, all): + """Internal Recursive function to find all pattern matches.""" + # Recursively process lists + if SCons.Util.is_List(tgt): + for t in tgt: + _FindSources(ptrns, t, all) + else: + # Get key to use for tracking whether we've seen this node + target_abspath = None + if hasattr(tgt, 'abspath'): + # Use target's absolute path as the key + target_abspath = tgt.abspath + target_key = target_abspath + else: + # Hope node's representation is unique enough (the default repr + # contains a pointer to the target as a string). This works for + # Alias() nodes. + target_key = repr(tgt) + + # Skip if we have been here before + if target_key in all: return + # Note that we have been here + all[target_key] = True + # Skip ones that match an exclude pattern, if we have one. + if (exclude_pattern and target_abspath + and exclude_pattern.match(target_abspath)): + return + + # Handle non-leaf nodes recursively + lst = tgt.children(scan=1) + if lst: + _FindSources(ptrns, lst, all) + return + + # Get real file (backed by repositories). + rfile = tgt.rfile() + rfile_is_file = rfile.isfile() + # See who it matches + for pattern, lst in ptrns.items(): + # Add files to the list for the first pattern that matches (implicitly, + # don't add directories). + if rfile_is_file and pattern.match(rfile.path): + lst.append(rfile.abspath) + break + + # Prepare a group for each pattern. + patterns = {} + for g in groups: + patterns[re.compile(g, re.IGNORECASE)] = [] + + # Do the search. + _FindSources(patterns, target, {}) + + return patterns.values() + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add a method to gather all inputs needed by a target. + env.AddMethod(GatherInputs, 'GatherInputs') diff --git a/o3d/site_scons/site_tools/naclsdk.py b/o3d/site_scons/site_tools/naclsdk.py new file mode 100644 index 0000000..6295a8a --- /dev/null +++ b/o3d/site_scons/site_tools/naclsdk.py @@ -0,0 +1,233 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Nacl SDK tool SCons.""" + +import __builtin__ +import os +import sys +import SCons.Script +import subprocess +import sync_tgz + + +NACL_PLATFORM_DIR_MAP = { + 'win32': 'windows', + 'cygwin': 'windows', + 'posix': 'linux', + 'linux': 'linux', + 'linux2': 'linux', + 'darwin': 'mac', +} + + +def _GetNaclSdkRoot(env, sdk_mode): + """Return the path to the sdk. + + Args: + env: The SCons environment in question. + sdk_mode: A string indicating which location to select the tools from. + Returns: + The path to the sdk. + """ + if sdk_mode == 'local': + if env['PLATFORM'] in ['win32', 'cygwin']: + # Try to use cygpath under the assumption we are running thru cygwin. + # If this is not the case, then 'local' doesn't really make any sense, + # so then we should complain. + try: + path = subprocess.Popen( + ['cygpath', '-m', '/usr/local/nacl-sdk'], + env={'PATH': os.environ['PRESCONS_PATH']}, shell=True, + stdout=subprocess.PIPE).communicate()[0].replace('\n', '') + except WindowsError: + raise NotImplementedError( + 'Not able to decide where /usr/local/nacl-sdk is on this platform,' + 'use naclsdk_mode=custom:...') + return path + else: + return '/usr/local/nacl-sdk' + + elif sdk_mode == 'download': + return ('$MAIN_DIR/../third_party/nacl_sdk/' + + NACL_PLATFORM_DIR_MAP[env['PLATFORM']] + '/sdk/nacl-sdk') + + elif sdk_mode.startswith('custom:'): + return os.path.abspath(sdk_mode[len('custom:'):]) + + else: + assert 0 + + +def _DownloadSdk(env): + """Download and untar the latest sdk. + + Args: + env: SCons environment in question. + """ + + # Only try to download once. + try: + if __builtin__.nacl_sdk_downloaded: return + except AttributeError: + __builtin__.nacl_sdk_downloaded = True + + # Get path to extract to. + target = env.subst('$MAIN_DIR/../third_party/nacl_sdk/' + + NACL_PLATFORM_DIR_MAP[env['PLATFORM']]) + + # Set NATIVE_CLIENT_SDK_PLATFORM before substitution. + env['NATIVE_CLIENT_SDK_PLATFORM'] = NACL_PLATFORM_DIR_MAP[env['PLATFORM']] + + # Allow sdk selection function to be used instead. + if env.get('NATIVE_CLIENT_SDK_SOURCE'): + url = env['NATIVE_CLIENT_SDK_SOURCE'](env) + else: + # Pick download url. + url = [ + env.subst(env.get( + 'NATIVE_CLIENT_SDK_URL', + 'http://nativeclient.googlecode.com/svn/data/sdk_tarballs/' + 'naclsdk_${NATIVE_CLIENT_SDK_PLATFORM}.tgz')), + env.get('NATIVE_CLIENT_SDK_USERNAME'), + env.get('NATIVE_CLIENT_SDK_PASSWORD'), + ] + + sync_tgz.SyncTgz(url[0], target, url[1], url[2]) + + +def _ValidateSdk(env, sdk_mode): + """Check that the sdk is present. + + Args: + env: SCons environment in question. + sdk_mode: mode string indicating where to get the sdk from. + """ + + # Try to download SDK if in download mode and using download version. + if env.GetOption('download') and sdk_mode == 'download': + _DownloadSdk(env) + + # Check if stdio.h is present as a cheap check for the sdk. + if not os.path.exists(env.subst('$NACL_SDK_ROOT/nacl/include/stdio.h')): + sys.stderr.write('NativeClient SDK not present in %s\n' + 'Run again with the --download flag\n' + 'and the naclsdk_mode=download option,\n' + 'or build the SDK yourself.\n' % + env.subst('$NACL_SDK_ROOT')) + sys.exit(1) + + +def generate(env): + """SCons entry point for this tool. + + Args: + env: The SCons environment in question. + + NOTE: SCons requires the use of this name, which fails lint. + """ + + # Add the download option. + try: + env.GetOption('download') + except AttributeError: + SCons.Script.AddOption('--download', + dest='download', + metavar='DOWNLOAD', + default=False, + action='store_true', + help='allow tools to download') + + # Pick default sdk source. + default_mode = env.get('NATIVE_CLIENT_SDK_DEFAULT_MODE', + 'custom:../third_party/nacl_sdk/' + + NACL_PLATFORM_DIR_MAP[env['PLATFORM']] + + '/sdk/nacl-sdk') + + # Get sdk mode (support sdk_mode for backward compatibility). + sdk_mode = SCons.Script.ARGUMENTS.get('sdk_mode', default_mode) + sdk_mode = SCons.Script.ARGUMENTS.get('naclsdk_mode', sdk_mode) + + # Decide where to get the SDK. + env.Replace(NACL_SDK_ROOT=_GetNaclSdkRoot(env, sdk_mode)) + + # Validate the sdk unless disabled from the command line. + env.SetDefault(NACL_SDK_VALIDATE='1') + if int(SCons.Script.ARGUMENTS.get('naclsdk_validate', + env.subst('$NACL_SDK_VALIDATE'))): + _ValidateSdk(env, sdk_mode) + + if env.subst('$NACL_SDK_ROOT_ONLY'): return + + # Invoke the various unix tools that the NativeClient SDK resembles. + env.Tool('g++') + env.Tool('gcc') + env.Tool('gnulink') + env.Tool('ar') + env.Tool('as') + + env.Replace( + HOST_PLATFORMS=['*'], # NaCl builds on all platforms. + + COMPONENT_LINKFLAGS=['-Wl,-rpath-link,$COMPONENT_LIBRARY_DIR'], + COMPONENT_LIBRARY_LINK_SUFFIXES=['.so', '.a'], + COMPONENT_LIBRARY_DEBUG_SUFFIXES=[], + + # TODO: This is needed for now to work around unc paths. Take this out + # when unc paths are fixed. + IMPLICIT_COMMAND_DEPENDENCIES=False, + + # Setup path to NativeClient tools. + NACL_SDK_BIN='$NACL_SDK_ROOT/bin', + NACL_SDK_INCLUDE='$NACL_SDK_ROOT/nacl/include', + NACL_SDK_LIB='$NACL_SDK_ROOT/nacl/lib', + + # Replace the normal unix tools with the NaCl ones. + CC='nacl-gcc', + CXX='nacl-g++', + AR='nacl-ar', + AS='nacl-as', + LINK='nacl-g++', # use g++ for linking so we can handle c AND c++ + RANLIB='nacl-ranlib', + + # TODO: this could be .nexe and then all the .nexe stuff goes away? + PROGSUFFIX='' # Force PROGSUFFIX to '' on all platforms. + ) + + env.PrependENVPath('PATH', [env.subst('$NACL_SDK_BIN')]) + env.PrependENVPath('INCLUDE', [env.subst('$NACL_SDK_INCLUDE')]) + env.Prepend(LINKFLAGS='-L' + env.subst('$NACL_SDK_LIB')) + + env.Append( + CCFLAGS=[ + '-fno-stack-protector', + '-fno-builtin', + ], + ) diff --git a/o3d/site_scons/site_tools/publish.py b/o3d/site_scons/site_tools/publish.py new file mode 100644 index 0000000..08bf724 --- /dev/null +++ b/o3d/site_scons/site_tools/publish.py @@ -0,0 +1,253 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Publish tool for SCons.""" + + +# List of published resources. This is a dict indexed by group name. Each +# item in this dict is a dict indexed by resource type. Items in that dict +# are lists of files for that resource. +__published = {} + +#------------------------------------------------------------------------------ + + +class PublishItem(object): + """Item to be published.""" + + def __init__(self, source, subdir): + """Initialize object. + + Args: + source: Source node. + subdir: If not None, subdirectory to copy node into in + ReplicatePublished(). + """ + object.__init__(self) + self.source = source + self.subdir = subdir + +#------------------------------------------------------------------------------ + + +def _InitializePublish(env): + """Re-initializes published resources. + + Args: + env: Parent environment + """ + env=env # Silence gpylint + + # Clear the dict of published resources + __published.clear() + + +def ReplicatePublished(self, target, group_name, resource_type): + """Replicate published resources for the group to the target directory. + + Args: + self: Environment in which this function was called. + target: Target directory for resources. + group_name: Name of resource group, or a list of names of resource groups. + resource_type: Type of resources (string), or a list of resource types. + + Uses the subdir parameter passed to Publish() when replicating source nodes + to the target. + + Returns: + The list of target nodes from the calls to Replicate(). + + Since this is based on Replicate(), it will also use the REPLICATE_REPLACE + variable, if it's set in the calling environment. + """ + target_path = self.Dir(target).abspath + #GOOGLE_CHANGE(pss) - FROM THIS: + #GOOGLE_CHANGE(pss) - TO THIS: + source_list = self.GetPublishedWithSubdirs(group_name, resource_type) + #GOOGLE_CHANGE(pss) - END CHANGES + dest_nodes = [] + #GOOGLE_CHANGE(pss) - FROM THIS: + # for group in self.SubstList2(group_name): + # for resource in self.SubstList2(resource_type): + # # Get items for publish group and resource type + # items = __published.get(group, {}).get(resource, []) + # for i in items: + # if i.subdir: + # dest_nodes += self.Replicate(target_path + '/' + i.subdir, i.source) + # else: + # dest_nodes += self.Replicate(target_path, i.source) + #GOOGLE_CHANGE(pss) - TO THIS: + for source in source_list: + # Add the subdir if there is one in the source tuple. + if source[1]: + dest_nodes += self.Replicate(target_path + '/' + source[1], source[0]) + else: + dest_nodes += self.Replicate(target_path, source[0]) + #GOOGLE_CHANGE(pss) - END CHANGES + return dest_nodes + + +#GOOGLE_CHANGE(pss) - FROM THIS: +# def GetPublished(self, group_name, resource_type): +# """Returns a list of the published resources of the specified type. +# +# Args: +# self: Environment in which this function was called. +# group_name: Name of resource group, or a list of names of resource groups. +# resource_type: Type of resources (string), or a list of resource types. +# +# Returns: +# A flattened list of the source nodes from calls to Publish() for the +# specified group and resource type. Returns an empty list if there are +# no matching resources. +# """ +#GOOGLE_CHANGE(pss) - TO THIS: +def GetPublishedWithSubdirs(self, group_name, resource_type): + """Returns a list of the published resources of the specified type. + + Args: + self: Environment in which this function was called. + group_name: Name of resource group, or a list of names of resource groups. + resource_type: Type of resources (string), or a list of resource types. + + Returns: + A flattened list of the source nodes from calls to Publish() for the + specified group and resource type. Each source node is represented + by a pair consisting of (source_node, subdir). Returns an empty list + if there are no matching resources. + """ +#GOOGLE_CHANGE(pss) - END CHANGES + source_list = [] + for group in self.SubstList2(group_name): + # Get items for publish group and resource type + for resource in self.SubstList2(resource_type): + items = __published.get(group, {}).get(resource, []) + for i in items: + #GOOGLE_CHANGE(pss) - FROM THIS: + # source_list.append(i.source) + #GOOGLE_CHANGE(pss) - TO THIS: + source_list.append((i.source, i.subdir)) + #GOOGLE_CHANGE(pss) - END CHANGES + + return source_list + + +#GOOGLE_CHANGE(pss) - FROM THIS: +#GOOGLE_CHANGE(pss) - TO THIS: +def GetPublished(self, group_name, resource_type): + """Returns a list of the published resources of the specified type. + + Args: + self: Environment in which this function was called. + group_name: Name of resource group, or a list of names of resource groups. + resource_type: Type of resources (string), or a list of resource types. + + Returns: + A flattened list of the source nodes from calls to Publish() for the + specified group and resource type. Returns an empty list if there are + no matching resources. + """ + source_list = self.GetPublishedWithSubdirs(group_name, resource_type) + return [source[0] for source in source_list] + + +#GOOGLE_CHANGE(pss) - END CHANGES +def Publish(self, group_name, resource_type, source, subdir=None): + """Publishes resources for use by other scripts. + + Args: + self: Environment in which this function was called. + group_name: Name of resource group. + resource_type: Type of resources (string). + source: Source file(s) to copy. May be a string, Node, or a list of + mixed strings or Nodes. Strings will be passed through env.Glob() to + evaluate wildcards. If a source evaluates to a directory, the entire + directory will be recursively copied. + subdir: Subdirectory to which the resources should be copied, relative to + the primary directory for that resource type, if not None. + """ + if subdir is None: + subdir = '' # Make string so we can append to it + + # Evaluate SCons variables in group name + # TODO: Should Publish() be able to take a list of group names and publish + # the resource to all of them? + group_name = self.subst(group_name) + + # Get list of sources + items = [] + for source_entry in self.Flatten(source): + if isinstance(source_entry, str): + # Search for matches for each source entry + # TODO: Should generate an error if there were no matches? But need to + # skip this warning if this is a recursive call to self.Publish() from + # below. + source_nodes = self.Glob(source_entry) + else: + # Source entry is already a file or directory node; no need to glob it + source_nodes = [source_entry] + for s in source_nodes: + if str(s.__class__) == 'SCons.Node.FS.Dir': + # Recursively publish all files in subdirectory. Since glob('*') + # doesn't match dot files, also glob('.*'). + self.Publish(group_name, resource_type, + [s.abspath + '/*', s.abspath + '/.*'], + subdir=subdir + '/' + s.name) + else: + items.append(PublishItem(s, subdir)) + + # Publish items, if any + if items: + # Get publish group + if group_name not in __published: + __published[group_name] = {} + group = __published[group_name] + if resource_type not in group: + group[resource_type] = [] + + # Publish items into group + group[resource_type] += items + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Defer initializing publish, but do before building SConscripts + env.Defer(_InitializePublish) + env.Defer('BuildEnvironmentSConscripts', after=_InitializePublish) + + #GOOGLE_CHANGE(pss) - FROM THIS: + #GOOGLE_CHANGE(pss) - TO THIS: + env.AddMethod(GetPublishedWithSubdirs) + #GOOGLE_CHANGE(pss) - END CHANGES + env.AddMethod(GetPublished) + env.AddMethod(Publish) + env.AddMethod(ReplicatePublished) diff --git a/o3d/site_scons/site_tools/replace_strings.py b/o3d/site_scons/site_tools/replace_strings.py new file mode 100644 index 0000000..a0881f5 --- /dev/null +++ b/o3d/site_scons/site_tools/replace_strings.py @@ -0,0 +1,82 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Search and replace builder for SCons.""" + + +import re +import SCons.Script + + +def ReplaceStrings(target, source, env): + """Replace Strings builder, does regex substitution on files. + + Args: + target: A single target file node. + source: A single input file node. + env: Environment in which to build. + + From env: + REPLACE_STRINGS: A list of pairs of regex search and replacement strings. + The body of the source file has substitution performed on each + pair (search_regex, replacement) in order. SCons variables in the + replacement strings will be evaluated. + + Returns: + The target node, a file with contents from source, with the substitutions + from REPLACE_STRINGS performed on it. + + For example: + env.ReplaceStrings('out', 'in', + REPLACE_STRINGS = [('a*', 'b'), ('b', 'CCC')]) + With 'in' having contents: Haaapy. + Outputs: HCCCpy. + """ + # Load text. + fh = open(source[0].abspath, 'rb') + text = fh.read() + fh.close() + # Do replacements. + for r in env['REPLACE_STRINGS']: + text = re.sub(r[0], env.subst(r[1]), text) + # Write it out. + fh = open(target[0].abspath, 'wb') + fh.write(text) + fh.close() + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add the builder + act = SCons.Script.Action(ReplaceStrings, varlist=['REPLACE_STRINGS']) + bld = SCons.Script.Builder(action=act, single_source=True) + env.Append(BUILDERS={'ReplaceStrings': bld}) diff --git a/o3d/site_scons/site_tools/replicate.py b/o3d/site_scons/site_tools/replicate.py new file mode 100644 index 0000000..a434c72 --- /dev/null +++ b/o3d/site_scons/site_tools/replicate.py @@ -0,0 +1,125 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Replicate tool for SCons.""" + + +import re + + +def Replicate(env, target, source, **kw): + """Replicates (copies) source files/directories to the target directory. + + Much like env.Install(), with the following differences: + * If the source is a directory, recurses through it and calls + env.Install() on each source file, rather than copying the entire + directory at once. This provides more opportunity for hard linking, and + also makes the destination files/directories all writable. + * Can take sources which contain env.Glob()-style wildcards. + * Can take multiple target directories; will copy to all of them. + * Handles duplicate requests. + + Args: + env: Environment in which to operate. + target: Destination(s) for copy. Must evaluate to a directory via + env.Dir(), or a list of directories. If more than one directory is + passed, the entire source list will be copied to each target + directory. + source: Source file(s) to copy. May be a string, Node, or a list of + mixed strings or Nodes. Strings will be passed through env.Glob() to + evaluate wildcards. If a source evaluates to a directory, the entire + directory will be recursively copied. + + From env: + REPLICATE_REPLACE: A list of pairs of regex search and replacement strings. + Each full destination path has substitution performed on each pair + (search_regex, replacement) in order. + + env.Replicate('destdir', ['footxt.txt'], REPLICATE_REPLACE = [ + ('\\.txt', '.bar'), ('est', 'ist')]) + will copy to 'distdir/footxt.bar' + + In the example above, note the use of \\ to escape the '.' character, + so that it doesn't act like the regexp '.' and match any character. + + Returns: + A list of the destination nodes from the calls to env.Install(). + """ + replace_list = kw.get('REPLICATE_REPLACE', env.get('REPLICATE_REPLACE', [])) + + dest_nodes = [] + for target_entry in env.Flatten(target): + for source_entry in env.Flatten(source): + if type(source_entry) == str: + # Search for matches for each source entry + source_nodes = env.Glob(source_entry) + else: + # Source entry is already a file or directory node; no need to glob it + source_nodes = [source_entry] + for s in source_nodes: + target_name = env.Dir(target_entry).abspath + '/' + s.name + # We need to use the following incantation rather than s.isdir() in + # order to handle chained replicates (A -> B -> C). The isdir() + # function is not properly defined in all of the Node type classes in + # SCons. This change is particularly crucial if hardlinks are present, + # in which case using isdir() can cause files to be unintentionally + # deleted. + # TODO: Look into fixing the innards of SCons so this isn't needed. + if str(s.__class__) == 'SCons.Node.FS.Dir': + # Recursively copy all files in subdir. Since glob('*') doesn't + # match dot files, also glob('.*'). + dest_nodes += env.Replicate( + target_name, [s.abspath + '/*', s.abspath + '/.*'], + REPLICATE_REPLACE=replace_list) + else: + # Apply replacement strings, if any + for r in replace_list: + target_name = re.sub(r[0], r[1], target_name) + target = env.File(target_name) + if (target.has_builder() + and hasattr(target.get_builder(), 'name') + and target.get_builder().name == 'InstallBuilder' + and target.sources == [s]): + # Already installed that file, so pass through the destination node + # TODO: Is there a better way to determine if this is a duplicate + # call to install? + dest_nodes += [target] + else: + dest_nodes += env.InstallAs(target_name, s) + + # Return list of destination nodes + return dest_nodes + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env.AddMethod(Replicate) diff --git a/o3d/site_scons/site_tools/sdl.py b/o3d/site_scons/site_tools/sdl.py new file mode 100644 index 0000000..b23679a --- /dev/null +++ b/o3d/site_scons/site_tools/sdl.py @@ -0,0 +1,184 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Simple DirectMedia Layer tool for SCons. + +This tool sets up an environment to use the SDL library. +""" + +import os +import sys +import SCons.Script + + +def _HermeticSDL(env): + """Set things up if sdl is hermetically setup somewhere.""" + + if sys.platform in ['win32', 'cygwin']: + env.SetDefault( + SDL_DIR='$SDL_HERMETIC_WINDOWS_DIR', + SDL_CPPPATH=['$SDL_DIR/include'], + SDL_LIBPATH=['$SDL_DIR/lib'], + SDL_LIBS=['SDL', 'SDLmain'], + SDL_FRAMEWORKPATH=[], + SDL_FRAMEWORKS=[], + ) + elif sys.platform in ['darwin']: + env.SetDefault( + SDL_DIR='$SDL_HERMETIC_MAC_DIR', + SDL_CPPPATH=[ + '$SDL_DIR/SDL.framework/Headers', + ], + SDL_LIBPATH=[], + SDL_LIBS=[], + SDL_FRAMEWORKPATH=['$SDL_DIR'], + SDL_FRAMEWORKS=['SDL', 'Cocoa'], + ) + elif sys.platform in ['linux', 'linux2', 'posix']: + env.SetDefault( + SDL_DIR='$SDL_HERMETIC_LINUX_DIR', + SDL_CPPPATH='$SDL_DIR/include', + SDL_LIBPATH='$SDL_DIR/lib', + SDL_LIBS=['SDL', 'SDLmain'], + SDL_FRAMEWORKPATH=[], + SDL_FRAMEWORKS=[], + ) + else: + env.SetDefault( + SDL_VALIDATE_PATHS=[ + ('unsupported_platform', + ('Not supported on this platform.',)), + ], + SDL_IS_MISSING=True, + ) + + if not env.get('SDL_IS_MISSING', False): + env.SetDefault( + SDL_VALIDATE_PATHS=[ + ('$SDL_DIR', + ('You are missing a hermetic copy of SDL...',)), + ], + ) + + +def _LocalSDL(env): + """Set things up if sdl is locally installed.""" + + if sys.platform in ['win32', 'cygwin']: + env.SetDefault( + SDL_DIR='c:/SDL-1.2.13', + SDL_CPPPATH='$SDL_DIR/include', + SDL_LIBPATH='$SDL_DIR/lib', + SDL_LIBS=['SDL', 'SDLmain'], + SDL_FRAMEWORKPATH=[], + SDL_FRAMEWORKS=[], + SDL_VALIDATE_PATHS=[ + ('$SDL_DIR', + ('You are missing SDL-1.2.13 on your system.', + 'It was supposed to be in: ${SDL_DIR}', + 'You can download it from:', + ' http://www.libsdl.org/download-1.2.php')), + ], + ) + elif sys.platform in ['darwin']: + env.SetDefault( + SDL_CPPPATH=['/Library/Frameworks/SDL.framework/Headers'], + SDL_LIBPATH=[], + SDL_LIBS=[], + SDL_FRAMEWORKPATH=[], + SDL_FRAMEWORKS=['SDL', 'Cocoa'], + SDL_VALIDATE_PATHS=[ + ('/Library/Frameworks/SDL.framework/SDL', + ('You are missing the SDL framework on your system.', + 'You can download it from:', + 'http://www.libsdl.org/download-1.2.php')), + ], + ) + elif sys.platform in ['linux', 'linux2', 'posix']: + env.SetDefault( + SDL_CPPPATH='/usr/include/SDL', + SDL_LIBPATH='/usr/lib', + SDL_LIBS=['SDL', 'SDLmain'], + SDL_FRAMEWORKPATH=[], + SDL_FRAMEWORKS=[], + SDL_VALIDATE_PATHS=[ + ('/usr/lib/libSDL.so', + ('You are missing SDL on your system.', + 'Run sudo apt-get install libsdl1.2-dev.')), + ], + ) + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Default to hermetic mode + # TODO: Should default to local for open-source installs + env.SetDefault(SDL_MODE='hermetic') + + # Allow the hermetic copy to be disabled on the command line. + sdl_mode = SCons.Script.ARGUMENTS.get('sdl', env.subst('$SDL_MODE')) + env['SDL_MODE'] = sdl_mode + if sdl_mode == 'local': + _LocalSDL(env) + elif sdl_mode == 'hermetic': + _HermeticSDL(env) + elif sdl_mode == 'none': + return + else: + assert False + + validate_paths = env['SDL_VALIDATE_PATHS'] + + # TODO: Should just print a warning if SDL isn't installed. Perhaps check + # for an env['SDL_EXIT_IF_NOT_FOUND'] variable so that projects can decide + # whether to fail if SDL is missing. + if not validate_paths: + sys.stderr.write('*' * 77 + '\n') + sys.stderr.write('ERROR - SDL not supported on this platform.\n') + sys.stderr.write('*' * 77 + '\n') + sys.exit(-1) + + for i in validate_paths: + if not os.path.exists(env.subst(i[0])): + sys.stderr.write('*' * 77 + '\n') + for j in i[1]: + sys.stderr.write(env.subst(j) + '\n') + sys.stderr.write('*' * 77 + '\n') + sys.exit(-1) + + env.Append( + CPPPATH=['$SDL_CPPPATH'], + LIBPATH=['$SDL_LIBPATH'], + LIBS=['$SDL_LIBS'], + FRAMEWORKPATH=['$SDL_FRAMEWORKPATH'], + FRAMEWORKS=['$SDL_FRAMEWORKS'], + ) diff --git a/o3d/site_scons/site_tools/seven_zip.py b/o3d/site_scons/site_tools/seven_zip.py new file mode 100644 index 0000000..d0d34e9 --- /dev/null +++ b/o3d/site_scons/site_tools/seven_zip.py @@ -0,0 +1,145 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + + +"""SCons tool for 7zip.""" + + +import os +import shutil +import subprocess +import tempfile +import SCons.Script + + +def SevenZipGetFiles(env, source): + """SCons emitter for 7zip extract. + + Examines the source 7z archive to determine the list of files which will be + created by extract/unzip operation. + Args: + env: The SCons environment to get the 7zip command line from. + source: The 7zip archive to examine. + Returns: + The list of filenames in the archive. + """ + # Expand the command to list archive contents. + cmd = env.subst('$SEVEN_ZIP l "%s"' % source) + # Run it and capture output. + output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] + # Strip off 7-line header and 3-line trailer from 7zip output. + lines = output.split('\r\n')[7:-3] + # Trim out just the files and their names. + files = [i[53:] for i in lines if i[20] != 'D'] + return files + + +def SevenZipEmitter(target, source, env): + """An emitter that decides what nodes are vented from a 7zip archive. + + Args: + target: The target directory node. + source: The source archive node. + env: The environment in which the emit takes place. + Returns: + The pair (target, source) which lists the emitted targets and sources. + """ + # Remember out dir for later. + env['SEVEN_ZIP_OUT_DIR'] = target[0].dir + # Get out contents + files = SevenZipGetFiles(env, env.subst('$SOURCE', source=source)) + # Extract a layer deeper if there is only one, and it extension is 7z. + if env.get('SEVEN_ZIP_PEEL_LAYERS', False): + assert len(files) == 1 and os.path.splitext(files[0])[1] == '.7z' + # Create a temporary directory. + tmp_dir = tempfile.mkdtemp() + # Expand the command to extract the archive to a temporary location. + cmd = env.subst('$SEVEN_ZIP x $SOURCE -o"%s"' % tmp_dir, source=source[0]) + # Run it and swallow output. + subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate() + # Get contents. + inner_files = SevenZipGetFiles(env, os.path.join(tmp_dir, files[0])) + # Make into file nodes. + inner_files = [target[0].dir.File(i) for i in inner_files] + # Delete temp_dir. + shutil.rmtree(tmp_dir) + # Decide where to extra working file to. + working_file = env.Dir(target[0].dir.abspath + + '.7zip_extract').File(files[0]) + # Combine everything. + files = [working_file] + inner_files + else: + # Make into file nodes. + files = [target[0].dir.File(i) for i in files] + # Return files as actual target. + return (files, source) + + +def SevenZipGenerator(source, target, env, for_signature): + """The generator function which decides how to extract a file.""" + + # Silence lint. + source = source + target = target + for_signature = for_signature + + if env.get('SEVEN_ZIP_PEEL_LAYERS', False): + return [SCons.Script.Delete('$SEVEN_ZIP_OUT_DIR'), + '$SEVEN_ZIP x $SOURCE -o"${TARGET.dir}"', + '$SEVEN_ZIP x $TARGET -o"$SEVEN_ZIP_OUT_DIR"'] + else: + return [SCons.Script.Delete('$SEVEN_ZIP_OUT_DIR'), + '$SEVEN_ZIP x $SOURCE -o"$SEVEN_ZIP_OUT_DIR"'] + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + env.Replace( + SEVEN_ZIP='$SEVEN_ZIP_DIR/7za.exe', + SEVEN_ZIP_ARCHIVE_OPTIONS = ['-t7z', '-mx0'], + SEVEN_ZIP_COMPRESS_OPTIONS = ['-t7z', '-mx9'], + ) + + b = SCons.Script.Builder(generator=SevenZipGenerator, + emitter=SevenZipEmitter) + env['BUILDERS']['Extract7zip'] = b + + b = SCons.Script.Builder( + action=('cd $SOURCE && ' + '$SEVEN_ZIP a $SEVEN_ZIP_ARCHIVE_OPTIONS ${TARGET.abspath} ./')) + env['BUILDERS']['Archive7zip'] = b + + b = SCons.Script.Builder( + action=('cd ${SOURCE.dir} && ' + '$SEVEN_ZIP a $SEVEN_ZIP_COMPRESS_OPTIONS ' + '${TARGET.abspath} ${SOURCE.file}')) + env['BUILDERS']['Compress7zip'] = b diff --git a/o3d/site_scons/site_tools/target_debug.py b/o3d/site_scons/site_tools/target_debug.py new file mode 100644 index 0000000..e307fdc --- /dev/null +++ b/o3d/site_scons/site_tools/target_debug.py @@ -0,0 +1,52 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Build tool setup for debug environments. + +This module is a SCons tool which setups environments for debugging. +It is used as follows: + debug_env = env.Clone(tools = ['target_debug']) +""" + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Set target platform bits + env.SetBits('debug') + + env['TARGET_DEBUG'] = True + + env.Append( + CPPDEFINES=['_DEBUG'] + env.get('CPPDEFINES_DEBUG', []), + CCFLAGS=env.get('CCFLAGS_DEBUG', []), + LINKFLAGS=env.get('LINKFLAGS_DEBUG', []), + ) diff --git a/o3d/site_scons/site_tools/target_optimized.py b/o3d/site_scons/site_tools/target_optimized.py new file mode 100644 index 0000000..603be5b --- /dev/null +++ b/o3d/site_scons/site_tools/target_optimized.py @@ -0,0 +1,50 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Build tool setup for optimized environments. + +This module is a SCons tool which setups environments for optimized builds. +It is used as follows: + optimized_env = env.Clone(tools = ['target_optimized']) +""" + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add in general options. + env['TARGET_DEBUG'] = False + + env.Append( + CPPDEFINES=['NDEBUG'] + env.get('CPPDEFINES_OPTIMIZED', []), + CCFLAGS=env.get('CCFLAGS_OPTIMIZED', []), + LINKFLAGS=env.get('LINKFLAGS_OPTIMIZED', []), + ) diff --git a/o3d/site_scons/site_tools/target_platform_linux.py b/o3d/site_scons/site_tools/target_platform_linux.py new file mode 100644 index 0000000..ff15cc4 --- /dev/null +++ b/o3d/site_scons/site_tools/target_platform_linux.py @@ -0,0 +1,109 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Build tool setup for Linux. + +This module is a SCons tool which should be include in the topmost windows +environment. +It is used as follows: + env = base_env.Clone(tools = ['component_setup']) + linux_env = base_env.Clone(tools = ['target_platform_linux']) +""" + + +def ComponentPlatformSetup(env, builder_name): + """Hook to allow platform to modify environment inside a component builder. + + Args: + env: Environment to modify + builder_name: Name of the builder + """ + if env.get('ENABLE_EXCEPTIONS'): + env.FilterOut(CCFLAGS=['-fno-exceptions']) + env.Append(CCFLAGS=['-fexceptions']) + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Preserve some variables that get blown away by the tools. + saved = dict() + for k in ['CFLAGS', 'CCFLAGS', 'CXXFLAGS', 'LINKFLAGS', 'LIBS']: + saved[k] = env.get(k, []) + env[k] = [] + + # Use g++ + env.Tool('g++') + env.Tool('gcc') + env.Tool('gnulink') + env.Tool('ar') + env.Tool('as') + + # Set target platform bits + env.SetBits('linux', 'posix') + + env.Replace( + TARGET_PLATFORM='LINUX', + COMPONENT_PLATFORM_SETUP=ComponentPlatformSetup, + CCFLAG_INCLUDE='-include', # Command line option to include a header + + # Code coverage related. + COVERAGE_CCFLAGS=['-ftest-coverage', '-fprofile-arcs'], + COVERAGE_LIBS='gcov', + COVERAGE_STOP_CMD=[ + '$COVERAGE_MCOV --directory "$TARGET_ROOT" --output "$TARGET"', + ('$COVERAGE_GENHTML --output-directory $COVERAGE_HTML_DIR ' + '$COVERAGE_OUTPUT_FILE'), + ], + ) + + env.Append( + HOST_PLATFORMS=['LINUX'], + CPPDEFINES=['OS_LINUX=OS_LINUX'], + + # Settings for debug + CCFLAGS_DEBUG=[ + '-O0', # turn off optimizations + '-g', # turn on debugging info + ], + + # Settings for optimized + CCFLAGS_OPTIMIZED=['-O2'], + + # Settings for component_builders + COMPONENT_LIBRARY_LINK_SUFFIXES=['.so', '.a'], + COMPONENT_LIBRARY_DEBUG_SUFFIXES=[], + ) + + # Restore saved flags. + env.Append(**saved) diff --git a/o3d/site_scons/site_tools/target_platform_mac.py b/o3d/site_scons/site_tools/target_platform_mac.py new file mode 100644 index 0000000..51fd58f --- /dev/null +++ b/o3d/site_scons/site_tools/target_platform_mac.py @@ -0,0 +1,214 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Build tool setup for MacOS. + +This module is a SCons tool which should be include in the topmost mac +environment. +It is used as follows: + env = base_env.Clone(tools = ['component_setup']) + mac_env = base_env.Clone(tools = ['target_platform_mac']) +""" + + +import SCons.Script + + +def ComponentPlatformSetup(env, builder_name): + """Hook to allow platform to modify environment inside a component builder. + + Args: + env: Environment to modify + builder_name: Name of the builder + """ + if env.get('ENABLE_EXCEPTIONS'): + env.FilterOut(CCFLAGS=['-fno-exceptions']) + env.Append(CCFLAGS=['-fexceptions']) + +#------------------------------------------------------------------------------ +# TODO: This bundle builder here needs refactoring to use ComponentPackage(). +# Until that refactoring, consider this code very experimental (i.e., don't use +# it unless you're ok with it changing out from underneath you). + +def BundlePseudoBuilder(env, target, **kwargs): + """MacOS Bundle PseudoBuilder. + + Args: + env: Environment in which to build + target: Name of the bundle to build + kwargs: Additional parameters to set in the environment + + Returns: + The target is returned. + """ + # Don't change the environment passed into the pseudo-builder + env = env.Clone() + + # Bring keywords args into the environment. + for k, v in kwargs.items(): + env[k] = v + # Make sure BUNDLE_RESOURCES is set and not empty; force it to be a list + bundle_resources = env.Flatten(env.get('BUNDLE_RESOURCES', [])) + if not bundle_resources: + raise ValueError('BUNDLE_RESOURCES must be set and non-empty') + + # Make each resource into a directory node. + # TODO: this seems a little too restrictive. + # bundle_resources = [env.Dir(i) for i in bundle_resources] + bundle_resources = [i for i in bundle_resources] + + # Create a PkgInfo file only if BUNDLE_PKGINFO_FILENAME is useful. + # (NPAPI bundles are unhappy with PkgInfo files.) + if env.get('BUNDLE_PKGINFO_FILENAME'): + pkginfo_create_command = ('$BUNDLE_GENERATE_PKGINFO ' + '>$TARGET/$BUNDLE_PKGINFO_FILENAME') + else: + pkginfo_create_command = '/bin/echo no PkgInfo will be created' # noop + + # Add the build step for the bundle. + p = env.Command(env.Dir(target), + [env.File('$BUNDLE_EXE'), + env.File('$BUNDLE_INFO_PLIST')] + + bundle_resources, + [SCons.Script.Delete('$TARGET'), + SCons.Script.Mkdir('$TARGET/Contents'), + SCons.Script.Mkdir('$TARGET/Contents/MacOS'), + SCons.Script.Mkdir('$TARGET/Contents/Resources'), + 'cp -f $SOURCE $TARGET/Contents/MacOS', + 'cp -f ${SOURCES[1]} $TARGET/Contents', + pkginfo_create_command, + 'cp -rf ${SOURCES[2:]} $TARGET/Contents/Resources']) + + # Add an alias for this target. + # This also allows the 'all_bundles' target to build me. + a = env.Alias(target, p) + for group in env['COMPONENT_BUNDLE_GROUPS']: + SCons.Script.Alias(group, a) + + return env.Dir(target) + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Preserve some variables that get blown away by the tools. + saved = dict() + for k in ['CFLAGS', 'CCFLAGS', 'CXXFLAGS', 'LINKFLAGS', 'LIBS']: + saved[k] = env.get(k, []) + env[k] = [] + + # Use g++ + env.Tool('g++') + env.Tool('gcc') + env.Tool('gnulink') + env.Tool('ar') + env.Tool('as') + env.Tool('applelink') + + # Set target platform bits + env.SetBits('mac', 'posix') + + env.Replace( + TARGET_PLATFORM='MAC', + COMPONENT_PLATFORM_SETUP=ComponentPlatformSetup, + CCFLAG_INCLUDE='-include', # Command line option to include a header + + # Code coverage related. + COVERAGE_CCFLAGS=['-ftest-coverage', '-fprofile-arcs'], + COVERAGE_LIBS='gcov', + COVERAGE_STOP_CMD=[ + '$COVERAGE_MCOV --directory "$TARGET_ROOT" --output "$TARGET"', + ('$COVERAGE_GENHTML --output-directory $COVERAGE_HTML_DIR ' + '$COVERAGE_OUTPUT_FILE'), + ], + + # Libraries expect to be in the same directory as their executables. + # This is correct for unit tests, and for libraries which are published + # in Contents/MacOS next to their executables. + DYLIB_INSTALL_NAME_FLAGS=[ + '-install_name', + '@loader_path/${TARGET.file}' + ], + ) + + env.Append( + HOST_PLATFORMS=['MAC'], + CPPDEFINES=['OS_MACOSX=OS_MACOSX'], + + # Mac apps and dylibs have a more strict relationship about where they + # expect to find each other. When an app is linked, it stores the + # relative path from itself to any dylibs it links against. Override + # this so that it will store the relative path from $LIB_DIR instead. + # This is similar to RPATH on Linux. + LINKFLAGS = [ + '-Xlinker', '-executable_path', + '-Xlinker', '$LIB_DIR', + ], + # Similarly, tell the library where it expects to find itself once it's + # installed. + SHLINKFLAGS = ['$DYLIB_INSTALL_NAME_FLAGS'], + + # Settings for debug + CCFLAGS_DEBUG=['-g'], + LINKFLAGS_DEBUG=['-g'], + + # Settings for optimized + # Optimized for space by default, which is what Xcode does + CCFLAGS_OPTIMIZED=['-Os'], + + # Settings for component_builders + COMPONENT_LIBRARY_LINK_SUFFIXES=['.dylib', '.a'], + COMPONENT_LIBRARY_DEBUG_SUFFIXES=[], + + # New 'all' target. Helpful: "hammer -h" now lists it! + COMPONENT_BUNDLE_GROUPS=['all_bundles'], + ) + + + # Set default values used by the Bundle pseudobuilder. + env.Replace( + BUNDLE_TYPE='APPL', + BUNDLE_STRING='${BUNDLE_TYPE}????', + BUNDLE_GENERATE_PKGINFO='echo "${BUNDLE_STRING}"', + BUNDLE_PKGINFO_FILENAME='PkgInfo', + BUNDLE_INFO_PLIST='Info.plist', + ) + + # Add the Bundle pseudobuilder. + env.AddMethod(BundlePseudoBuilder, 'Bundle') + + # Add our target groups + AddTargetGroup('all_bundles', 'bundles can be built') + + # Restore saved flags. + env.Append(**saved) diff --git a/o3d/site_scons/site_tools/target_platform_windows.py b/o3d/site_scons/site_tools/target_platform_windows.py new file mode 100644 index 0000000..eabd7d6 --- /dev/null +++ b/o3d/site_scons/site_tools/target_platform_windows.py @@ -0,0 +1,408 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Build tool setup for Windows. + +This module is a SCons tool which should be include in the topmost windows +environment. +It is used as follows: + env = base_env.Clone(tools = ['component_setup']) + win_env = base_env.Clone(tools = ['target_platform_windows']) +""" + + +import os +import time +import SCons.Script +import command_output + + +def WaitForWritable(target, source, env): + """Waits for the target to become writable. + + Args: + target: List of target nodes. + source: List of source nodes. + env: Environment context. + + Returns: + Zero if success, nonzero if error. + + This is a necessary hack on Windows, where antivirus software can lock exe + files briefly after they're written. This can cause subsequent reads of the + file by env.Install() to fail. To prevent these failures, wait for the file + to be writable. + """ + env = env # suppress lint + source = source # suppress lint + target_path = target[0].abspath + if not os.path.exists(target_path): + return 0 # Nothing to wait for + + for unused_retries in range(10): + try: + f = open(target_path, 'a+b') + f.close() + return 0 # Successfully opened file for write, so we're done + except (IOError, OSError): + print 'Waiting for access to %s...' % target_path + time.sleep(1) + + # If we're still here, fail + print 'Timeout waiting for access to %s.' % target_path + return 1 + + +def RunManifest(target, source, env, cmd): + """Run the Microsoft Visual Studio manifest tool (mt.exe). + + Args: + target: List of target nodes. + source: List of source nodes. + env: Environment context. + cmd: Command to run. + + Returns: + Zero if success, nonzero if error. + + The mt.exe tool seems to experience intermittent failures trying to write to + .exe or .dll files. Antivirus software makes this worse, but the problem + can still occur even if antivirus software is disabled. The failures look + like: + + mt.exe : general error c101008d: Failed to write the updated manifest to + the resource of file "(name of exe)". Access is denied. + + with mt.exe returning an errorlevel (return code) of 31. The workaround is + to retry running mt.exe after a short delay. + """ + cmdline = env.subst(cmd, target=target, source=source) + + for retry in range(5): + # If this is a retry, print a message and delay first + if retry: + # mt.exe failed to write to the target file. Print a warning message, + # delay 3 seconds, and retry. + print 'Warning: mt.exe failed to write to %s; retrying.' % target[0] + time.sleep(3) + + return_code, output = command_output.RunCommand( + cmdline, env=env['ENV'], echo_output=False) + if return_code != 31: # Something other than the intermittent error + break + + # Pass through output (if any) and return code from manifest + if output: + print output + return return_code + + +def RunManifestExe(target, source, env): + """Calls RunManifest for updating an executable (resource_num=1).""" + return RunManifest(target, source, env, cmd='$MANIFEST_COM') + + +def RunManifestDll(target, source, env): + """Calls RunManifest for updating a dll (resource_num=2).""" + return RunManifest(target, source, env, cmd='$SHMANIFEST_COM') + + +def ComponentPlatformSetup(env, builder_name): + """Hook to allow platform to modify environment inside a component builder. + + This is called on a clone of the environment passed into the component + builder, and is the last modification done to that environment before using + it to call the underlying SCons builder (env.Program(), env.Library(), etc.) + + Args: + env: Environment to modify + builder_name: Name of the builder + """ + if env.get('ENABLE_EXCEPTIONS'): + env.FilterOut( + CPPDEFINES=['_HAS_EXCEPTIONS=0'], + # There are problems with LTCG when some files are compiled with + # exceptions and some aren't (the v-tables for STL and BOOST classes + # don't match). Therefore, turn off LTCG when exceptions are enabled. + CCFLAGS=['/GL'], + LINKFLAGS=['/LTCG'], + ARFLAGS=['/LTCG'], + ) + env.Append(CCFLAGS=['/EHsc']) + + if builder_name in ('ComponentObject', 'ComponentLibrary'): + if env.get('COMPONENT_STATIC'): + env.Append(CPPDEFINES=['_LIB']) + else: + env.Append(CPPDEFINES=['_USRDLL', '_WINDLL']) + + if (not env.get('COMPONENT_TEST_SUBSYSTEM_WINDOWS') and + builder_name == 'ComponentTestProgram'): + env.FilterOut( + CPPDEFINES=['_WINDOWS'], + LINKFLAGS=['/SUBSYSTEM:WINDOWS'], + ) + env.Append( + CPPDEFINES=['_CONSOLE'], + LINKFLAGS=['/SUBSYSTEM:CONSOLE'], + ) + + # Make sure link methods are lists, so we can append to them below + env['LINKCOM'] = [env['LINKCOM']] + env['SHLINKCOM'] = [env['SHLINKCOM']] + + # Support manifest file generation and consumption + if env.get('MANIFEST_FILE'): + env.Append( + LINKCOM=[SCons.Script.Action(RunManifestExe, '$MANIFEST_COMSTR')], + SHLINKCOM=[SCons.Script.Action(RunManifestDll, '$SHMANIFEST_COMSTR')], + ) + + # If manifest file should be autogenerated, add the -manifest link line and + # delete the generated manifest after running mt.exe. + if env.get('MANIFEST_FILE_GENERATED_BY_LINK'): + env.Append( + LINKFLAGS=['-manifest'], + LINKCOM=[SCons.Script.Delete('$MANIFEST_FILE_GENERATED_BY_LINK')], + SHLINKCOM=[SCons.Script.Delete('$MANIFEST_FILE_GENERATED_BY_LINK')], + ) + + # Wait for the output file to be writable before releasing control to + # SCons. Windows virus scanners temporarily lock modified executable files + # for scanning, which causes SCons's env.Install() to fail intermittently. + env.Append( + LINKCOM=[SCons.Script.Action(WaitForWritable, None)], + SHLINKCOM=[SCons.Script.Action(WaitForWritable, None)], + ) + +#------------------------------------------------------------------------------ + + +def _CoverageInstall(dest, source, env): + """Version of Install that instruments EXEs and DLLs going to $TESTS_DIR. + + When the destination is under a path in $COVERAGE_INSTRUMENTATION_PATHS and + an EXE/DLL is involved, instrument after copy. Other files are passed through + to the original Install method. PDBs are handled specially. They are ignored + when installed to $COVERAGE_INSTRUMENTATION_PATHS, and are instead copied + explicitly before the corresponding EXE/DLL. Only files from under + $DESTINATION_ROOT are considered. + Arguments: + dest: destination filename for the install + source: source filename for the install + env: the environment context in which this occurs + """ + # Determine if this path is under $COVERAGE_INSTRUMENTATION_PATHS. + in_instrumentation_paths = False + for path in env.get('COVERAGE_INSTRUMENTATION_PATHS'): + if not env.RelativePath(path, dest).startswith('..'): + in_instrumentation_paths = True + + # Determine if source is under $DESTINATION_ROOT. + source_under_dst_root = not env.RelativePath('$DESTINATION_ROOT', + source).startswith('..') + + # get the source extension. + source_ext = os.path.splitext(source)[1] + + if (source_ext == '.pdb' and + source_under_dst_root and in_instrumentation_paths): + # PDBs going into $TESTS_DIR will be copied as part of the EXE/DLL copy. + # TODO: Put in the proper env.Requires steps instead. + return + elif (source_ext in ['.exe', '.dll'] and + source_under_dst_root and in_instrumentation_paths): + # Copy PDBs (assumed for now to match the filenames of EXEs/DLLs) into + # place before instrumenting. The PDB is assumed to match the source PDB + # file name. + source_pdb = env.subst('$PDB', target=env.File(source)) + dest_pdb = env.subst('$PDB', target=env.File(dest)) + dest_pdb = os.path.join(os.path.split(dest_pdb)[0], + os.path.split(source_pdb)[1]) + if os.path.exists(source_pdb): + env.Execute('copy "%s" "%s"' % (source_pdb, dest_pdb)) + WaitForWritable([env.File(dest_pdb)], None, env) + # Copy EXEs/DLLs and then instrument. + env.Execute('copy "%s" "%s"' % (source, dest)) + WaitForWritable([env.File(dest)], None, env) + env.Execute('$COVERAGE_VSINSTR /COVERAGE "%s"' % dest) + else: + env['PRECOVERAGE_INSTALL'](dest, source, env) + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # TODO: Several sections here are gated out on the mac to prevent failure. + # This appears to be SCons issue 1720 manifesting itself on Mac when using + # windows tools like msvs or msvc. + + # Preserve some variables that get blown away by the tools. + saved = dict() + for k in ['CFLAGS', 'CCFLAGS', 'CXXFLAGS', 'LINKFLAGS', 'LIBS']: + saved[k] = env.get(k, []) + env[k] = [] + + # Bring in the outside PATH, INCLUDE, and LIB if not blocked. + if not env.get('MSVC_BLOCK_ENVIRONMENT_CHANGES'): + env.AppendENVPath('PATH', os.environ.get('PATH', '[]')) + env.AppendENVPath('INCLUDE', os.environ.get('INCLUDE', '[]')) + env.AppendENVPath('LIB', os.environ.get('LIB', '[]')) + + # Load various Visual Studio related tools. + if env['PLATFORM'] != 'darwin': + env.Tool('as') + env.Tool('msvs') + env.Tool('windows_hard_link') + + pre_msvc_env = env['ENV'].copy() + + if env['PLATFORM'] != 'darwin': + env.Tool('msvc') + env.Tool('mslib') + env.Tool('mslink') + + # Find VC80_DIR if it isn't already set. + if not env.get('VC80_DIR'): + # Look in each directory in the path for cl.exe. + for p in env['ENV']['PATH'].split(os.pathsep): + # Use the directory two layers up if it exists. + if os.path.exists(os.path.join(p, 'cl.exe')): + env['VC80_DIR'] = os.path.dirname(os.path.dirname(p)) + + # The msvc, mslink, and mslib tools search the registry for installed copies + # of Visual Studio and prepends them to the PATH, INCLUDE, and LIB + # environment variables. Block these changes if necessary. + if env.get('MSVC_BLOCK_ENVIRONMENT_CHANGES'): + env['ENV'] = pre_msvc_env + + # Set target platform bits + env.SetBits('windows') + + env.Replace( + TARGET_PLATFORM='WINDOWS', + COMPONENT_PLATFORM_SETUP=ComponentPlatformSetup, + + # A better rebuild command (actually cleans, then rebuild) + MSVSREBUILDCOM=''.join(['$MSVSSCONSCOM -c "$MSVSBUILDTARGET" && ', + '$MSVSSCONSCOM "$MSVSBUILDTARGET"']), + ) + + env.SetDefault( + # Command line option to include a header + CCFLAG_INCLUDE='/FI', + + # Generate PDBs matching target name by default. + PDB='${TARGET.base}.pdb', + + # Code coverage related. + COVERAGE_LINKFLAGS='/PROFILE', # Requires vc_80 or higher. + COVERAGE_SHLINKFLAGS='$COVERAGE_LINKFLAGS', + # Change install step for coverage to cause instrumentation. + COVERAGE_INSTALL=_CoverageInstall, + # NOTE: need to ignore error in return type here, the tool has issues. + # Thus a - is added. + COVERAGE_START_CMD=[ + # If a previous build was cancelled or crashed, VSPerfCmd may still + # be running, which causes future coverage runs to fail. Make sure + # it's shut down before starting coverage up again. + '-$COVERAGE_VSPERFCMD -shutdown', + '$COVERAGE_VSPERFCMD -start:coverage ' + '-output:${COVERAGE_OUTPUT_FILE}.pre'], + COVERAGE_STOP_CMD=[ + '-$COVERAGE_VSPERFCMD -shutdown', + 'c:\\Windows\\System32\\regsvr32.exe /S ' + '$COVERAGE_ANALYZER_DIR/msdia80.dll', + '$COVERAGE_ANALYZER -sym_path=. ${COVERAGE_OUTPUT_FILE}.pre.coverage', + 'c:\\Windows\\System32\\regsvr32.exe /S /U ' + '$COVERAGE_ANALYZER_DIR/msdia80.dll', + SCons.Script.Copy('$COVERAGE_OUTPUT_FILE', + '${COVERAGE_OUTPUT_FILE}.pre.coverage.lcov'), + ], + COVERAGE_EXTRA_PATHS=['$COVERAGE_ANALYZER_DIR'], + # Directories for which EXEs and DLLs should by instrumented on install. + COVERAGE_INSTRUMENTATION_PATHS=['$TESTS_DIR', '$ARTIFACTS_DIR'], + + # Manifest options + # When link.exe is run with '-manifest', it always generated a manifest + # with this name. + MANIFEST_FILE_GENERATED_BY_LINK='${TARGET}.manifest', + # Manifest file to use as input to mt.exe. Can be overridden to pass in + # a pregenerated manifest file. + MANIFEST_FILE='$MANIFEST_FILE_GENERATED_BY_LINK', + MANIFEST_COM=('mt.exe -nologo -manifest "$MANIFEST_FILE" ' + '-outputresource:"$TARGET";1'), + MANIFEST_COMSTR='$MANIFEST_COM', + SHMANIFEST_COM=('mt.exe -nologo -manifest "$MANIFEST_FILE" ' + '-outputresource:"$TARGET";2'), + SHMANIFEST_COMSTR='$SHMANIFEST_COM', + ) + + env.Append( + HOST_PLATFORMS=['WINDOWS'], + CPPDEFINES=['OS_WINDOWS=OS_WINDOWS'], + + # Turn up the warning level + CCFLAGS=['/W3'], + + # Force x86 platform, generate manifests + LINKFLAGS=['/MACHINE:X86'], + ARFLAGS=['/MACHINE:X86'], + + # Settings for debug + CCFLAGS_DEBUG=[ + '/Od', # disable optimizations + '/RTC1', # enable fast checks + '/MTd', # link with LIBCMTD.LIB debug lib + ], + LINKFLAGS_DEBUG=['/DEBUG'], + + # Settings for optimized + CCFLAGS_OPTIMIZED=[ + '/O1', # optimize for size + '/MT', # link with LIBCMT.LIB (multi-threaded, static linked crt) + '/GS', # enable security checks + ], + LINKFLAGS_OPTIMIZED=['/PDBPATH:none'], + + # Settings for component_builders + COMPONENT_LIBRARY_LINK_SUFFIXES=['.lib'], + COMPONENT_LIBRARY_DEBUG_SUFFIXES=['.pdb'], + ) + + # TODO: mslink.py creates a shlibLinkAction which doesn't specify + # '$SHLINKCOMSTR' as its command string. This breaks --brief. For now, + # hack into the existing action and override its command string. + if env['PLATFORM'] != 'darwin': + env['SHLINKCOM'].list[0].cmdstr = '$SHLINKCOMSTR' + + # Restore saved flags. + env.Append(**saved) diff --git a/o3d/site_scons/site_tools/visual_studio_solution.py b/o3d/site_scons/site_tools/visual_studio_solution.py new file mode 100644 index 0000000..36d0ad9 --- /dev/null +++ b/o3d/site_scons/site_tools/visual_studio_solution.py @@ -0,0 +1,131 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Visual Studio solution file generation tool for SCons.""" + + +import sys +import SCons.Script + + +def Solution(env, solution_name, + environments, + exclude_pattern=None, + extra_build_targets=None): + """Builds an MSVS solution containing all projects underneath build/win. + + Args: + solution_name: Name of the solution. + environments: List of environments for variants. Only the first one + will be used to build the solutions/projects. + exclude_pattern: Files matching this pattern will not be added to the + projects and solution. + extra_build_targets: Dict of extra build targets, indexed by target + name. Each extra build target will be given + its own empty project. + """ + + # Provide an empty dict for the set of extra targets, by default. + if extra_build_targets is None: + extra_build_targets = dict() + + # Fail if not on windows for now. + if sys.platform not in ['win32', 'cygwin']: + print ('*** Solution file generation skipped ' + '(not supported on this platform).') + return + + # Add in the msvs tool. + env.Tool('msvs') + + # Pick out variants + variants = [e['BUILD_TYPE'] for e in environments] + # Pick out build targets + build_targets = [e.subst('$TARGET_ROOT') for e in environments] + # pick out sources, headers, and resources + sources, headers, resources, others = env.GatherInputs( + [env.Dir('$DESTINATION_ROOT')], + ['.+\\.(c|cc|m|mm|cpp)$', # source files + '.+\\.(h|hh|hpp)$', # header files + '.+\\.(rc)$', # resource files + '.*'], # all other files + exclude_pattern=exclude_pattern, + ) + # Build main Visual Studio Project file + project_list = env.MSVSProject(target=solution_name + + env['MSVSPROJECTSUFFIX'], + srcs=sources + headers + others + resources, + incs=[], + misc=[], + resources=[], + auto_build_solution=0, + MSVSCLEANCOM='hammer.bat -c MODE=all', + MSVSBUILDCOM='hammer.bat MODE=all', + MSVSREBUILD='hammer.bat -c MODE=all;' + 'hammer.bat MODE=all', + buildtarget=build_targets, + variant=variants) + # Collect other projects + for e in extra_build_targets: + # Explicitly create a node for target, so SCons will expand env variables. + build_target = env.File(extra_build_targets[e]) + # Create an empty project that only has a build target. + project_list += env.MSVSProject(target='projects/' + e + '/' + e + + env['MSVSPROJECTSUFFIX'], + srcs=[], + incs=[], + resources=[], + misc=[], + auto_build_solution=0, + MSVSCLEANCOM='rem', + MSVSBUILDCOM='rem', + MSVSREBUILD='rem', + buildtarget=build_target, + variant=variants[0]) + + # Build Visual Studio Solution file. + solution = env.MSVSSolution(target=solution_name + env['MSVSSOLUTIONSUFFIX'], + projects=project_list, + variant=variants) + # Explicitly add dependencies. + env.Depends(solution, project_list) + + return solution + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + + # Add in the gather_inputs tool. + env.Tool('gather_inputs') + + # Add a method to generate a combined solution file. + env.AddMethod(Solution, 'Solution') diff --git a/o3d/site_scons/site_tools/windows_hard_link.py b/o3d/site_scons/site_tools/windows_hard_link.py new file mode 100644 index 0000000..139f312 --- /dev/null +++ b/o3d/site_scons/site_tools/windows_hard_link.py @@ -0,0 +1,108 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Hard link support for Windows. + +This module is a SCons tool which should be include in the topmost windows +environment. It is usually included by the target_platform_windows tool. +""" + + +import os +import stat +import sys +import SCons + +if sys.platform == 'win32': + # Only attempt to load pywin32 on Windows systems + try: + import win32file + except ImportError: + print ('Warning: Unable to load win32file module; using copy instead of' + ' hard linking for env.Install(). Is pywin32 present?') + +#------------------------------------------------------------------------------ +# Python 2.4 and 2.5's os module doesn't support os.link on Windows, even +# though Windows does have hard-link capability on NTFS filesystems. So by +# default, SCons will insist on copying files instead of linking them as it +# does on other (linux,mac) OS's. +# +# Use the CreateHardLink() functionality from pywin32 to provide hard link +# capability on Windows also. + + +def _HardLink(fs, src, dst): + """Hard link function for hooking into SCons.Node.FS. + + Args: + fs: Filesystem class to use. + src: Source filename to link to. + dst: Destination link name to create. + + Raises: + OSError: The link could not be created. + """ + # A hard link shares file permissions from the source. On Windows, the write + # access of the file itself determines whether the file can be deleted + # (unlike Linux/Mac, where it's the write access of the containing + # directory). So if we made a link from a read-only file, the only way to + # delete it would be to make the link writable, which would have the + # unintended effect of making the source writable too. + # + # So if the source is read-only, we can't hard link from it. + if not stat.S_IMODE(fs.stat(src)[stat.ST_MODE]) & stat.S_IWRITE: + raise OSError('Unsafe to hard-link read-only file: %s' % src) + + # If the file is writable, only hard-link from it if it was build by SCons. + # Those files shouldn't later become read-only. We don't hard-link from + # writable files which SCons didn't create, because those could become + # read-only (for example, following a 'p4 submit'), which as indicated above + # would make our link read-only too. + if not fs.File(src).has_builder(): + raise OSError('Unsafe to hard-link file not built by SCons: %s' % src) + + try: + win32file.CreateHardLink(dst, src) + except win32file.error, msg: + # Translate errors into standard OSError which SCons expects. + raise OSError(msg) + + +#------------------------------------------------------------------------------ + + +def generate(env): + # NOTE: SCons requires the use of this name, which fails gpylint. + """SCons entry point for this tool.""" + env = env # Silence gpylint + + # Patch in our hard link function, if we were able to load pywin32 + if 'win32file' in globals(): + SCons.Node.FS._hardlink_func = _HardLink diff --git a/o3d/site_scons/sync_tgz.py b/o3d/site_scons/sync_tgz.py new file mode 100644 index 0000000..8af6a96 --- /dev/null +++ b/o3d/site_scons/sync_tgz.py @@ -0,0 +1,72 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Keep a local directory in sync with a website tar file. + +This module downloads a tgz, and expands it as needed. +It supports username and password with basic authentication. +""" + +import os +import shutil +import tarfile +import http_download + + +def SyncTgz(url, target, username=None, password=None, verbose=True): + """Download a file from a remote server. + + Args: + url: A URL to download from. + target: Directory to extract to and prefix to use for tgz file. + username: Optional username for download. + password: Optional password for download (ignored if no username). + verbose: Flag indicating if status shut be printed. + """ + shutil.rmtree(target, True) + tgz_filename = target + '.tgz' + + if verbose: + print 'Downloading %s to %s...' % (url, tgz_filename) + http_download.HttpDownload(url, tgz_filename, + username=username, password=password) + + if verbose: + print 'Extracting from %s...' % tgz_filename + tgz = tarfile.open(tgz_filename, 'r') + for m in tgz: + if verbose: + print m.name + tgz.extract(m, target) + tgz.close() + os.remove(tgz_filename) + + if verbose: + print 'Update complete.' diff --git a/o3d/site_scons/usage_log.py b/o3d/site_scons/usage_log.py new file mode 100644 index 0000000..cab04c8 --- /dev/null +++ b/o3d/site_scons/usage_log.py @@ -0,0 +1,307 @@ +#!/usr/bin/python2.4 +# Copyright 2009, 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. + +"""Optional usage logging for Software Construction Toolkit.""" + +import atexit +import os +import platform +import sys +import time +import xml.dom +import SCons +import SCons.Script + + +chain_build_targets = None # Previous SCons _build_targets function + +#------------------------------------------------------------------------------ +# Wrappers and hooks into SCons + + +class ProgressDisplayWrapper(object): + """Wrapper around SCons.Util.DisplayEngine. + + Needs to be has-a not is-a, since DisplayEngine.set_mode() overrides the + __call__ member. + """ + + def __init__(self, old_display): + """Constructor. + + Args: + old_display: Old display object to chain to. + """ + self.old_display = old_display + + def __call__(self, text, append_newline=1): + """Display progress. + + Args: + text: Text to display. + append_newline: Append newline to text if non-zero. + + Returns: + Passthru from old display object. + """ + log.AddEntry('progress %s' % text) + return self.old_display(text, append_newline) + + def set_mode(self, mode): + """Passthru to DisplayEngine.setmode(). + + Args: + mode: If non-zero, print progress. + + Returns: + Passthru from old display object. + """ + return self.old_display.set_mode(mode) + + +def BuildTargetsWrapper(fs, options, targets, target_top): + """Wrapper around SCons.Script.Main._build_targets(). + + Args: + fs: Filesystem object. + options: SCons options (after modification by SConscripts. + targets: Targets to build. + target_top: Passed through to _build_targets(). + """ + log.AddEntry('build_targets start') + log.SetParam('build_targets.targets', map(str, targets)) + + # Get list of non-default options. SConscript settings override defaults. + build_opts = dict(options.__SConscript_settings__) + # Command line settings are direct attrs, and override SConscript settings. + for key in dir(options): + if key.startswith('__') or key == 'settable': + continue + value = getattr(options, key) + if callable(value): + continue + build_opts[key] = value + + for key, value in build_opts.items(): + log.SetParam('build_targets.option.%s' % key, value) + + try: + returnval = None + if chain_build_targets: + returnval = chain_build_targets(fs, options, targets, target_top) + return returnval + finally: + log.AddEntry('build_targets done') + + +def PrecmdWrapper(self, line): + """Pre-command handler for SCons.Script.Interactive() to support logging. + + Args: + self: cmd object. + line: Command line which will be executed. + + Returns: + Passthru value of line. + """ + log.AddEntry('Interactive start') + log.SetParam('interactive.command', line or self.lastcmd) + return line + + +def PostcmdWrapper(self, stop, line): + """Post-command handler for SCons.Script.Interactive() to support logging. + + Args: + self: cmd object. + stop: Will execution stop after this function exits? + line: Command line which was executed. + + Returns: + Passthru value of stop. + """ + log.AddEntry('Interactive done') + log.Dump() + return stop + + +#------------------------------------------------------------------------------ +# Usage log object + + +class Log(object): + """Usage log object.""" + + def __init__(self): + """Constructor.""" + self.params = {} + self.entries = [] + self.dump_writer = None + self.time = time.time + + def SetParam(self, key, value): + """Sets a parameter. + + Args: + key: Parameter name (string). + value: Value for parameter. + """ + self.params[key] = value + + def AddEntry(self, text): + """Adds a timestamped log entry. + + Args: + text: Text of log entry. + """ + self.entries.append((self.time(), text)) + + def ConvertToXml(self): + """Converts the usage log to XML. + + Returns: + An xml.dom.minidom.Document object with the usage log contents. + """ + xml_impl = xml.dom.getDOMImplementation() + xml_doc = xml_impl.createDocument(None, 'usage_log', None) + + # List build params + xml_param_list = xml_doc.createElement('param_list') + xml_doc.documentElement.appendChild(xml_param_list) + for key in sorted(self.params): + xml_param = xml_doc.createElement('param') + xml_param.setAttribute('name', str(key)) + xml_param_list.appendChild(xml_param) + + value = self.params[key] + if hasattr(value, '__iter__'): + # Iterable value, so list items + for v in value: + xml_item = xml_doc.createElement('item') + xml_item.setAttribute('value', str(v)) + xml_param.appendChild(xml_item) + else: + # Non-iterable, so convert to string + xml_param.setAttribute('value', str(value)) + + # List log entries + xml_entry_list = xml_doc.createElement('entry_list') + xml_doc.documentElement.appendChild(xml_entry_list) + for entry_time, entry_text in self.entries: + xml_entry = xml_doc.createElement('entry') + xml_entry.setAttribute('time', str(entry_time)) + xml_entry.setAttribute('text', str(entry_text)) + xml_entry_list.appendChild(xml_entry) + + return xml_doc + + def Dump(self): + """Dumps the log by calling self.dump_writer(), then clears the log.""" + if self.dump_writer: + self.dump_writer(self) + + # Clear log entries (but not params, since they can be used again if SCons + # is in interactive mode). + self.entries = [] + + + def SetOutputFile(self, filename): + """Sets the output filename for usage log dumps. + + Args: + filename: Name of output file. + """ + self.dump_to_file = filename + self.dump_writer = FileDumpWriter + +#------------------------------------------------------------------------------ +# Usage log methods + +def AddSystemParams(): + """Prints system stats.""" + log.SetParam('sys.argv', sys.argv) + log.SetParam('sys.executable', sys.executable) + log.SetParam('sys.version', sys.version) + log.SetParam('sys.version_info', sys.version_info) + log.SetParam('sys.path', sys.path) + log.SetParam('sys.platform', sys.platform) + log.SetParam('platform.uname', platform.uname()) + log.SetParam('platform.platform', platform.platform()) + + for e in ['PATH', 'INCLUDE', 'LIB', 'HAMMER_OPTS', 'HAMMER_XGE']: + log.SetParam('shell.%s' % e, os.environ.get(e, '')) + + log.SetParam('scons.version', SCons.__version__) + + +def AtExit(): + """Usage log cleanup at exit.""" + log.AddEntry('usage_log exit') + log.Dump() + + +def AtModuleLoad(): + """Code executed at module load time.""" + AddSystemParams() + + # Wrap SCons' progress display wrapper + SCons.Script.Main.progress_display = ProgressDisplayWrapper( + SCons.Script.Main.progress_display) + + # Wrap SCons' _build_targets() + global chain_build_targets + chain_build_targets = SCons.Script.Main._build_targets + SCons.Script.Main._build_targets = BuildTargetsWrapper + + # Hook SCons interactive mode + SCons.Script.Interactive.SConsInteractiveCmd.precmd = PrecmdWrapper + SCons.Script.Interactive.SConsInteractiveCmd.postcmd = PostcmdWrapper + + # Make sure we get called at exit + atexit.register(AtExit) + + +def FileDumpWriter(log): + """Dumps the log to the specified file.""" + print 'Writing usage log to %s...' % log.dump_to_file + f = open(log.dump_to_file, 'wt') + doc = log.ConvertToXml() + doc.writexml(f, encoding='UTF-8', addindent=' ', newl='\n') + doc.unlink() + f.close() + print 'Done writing log.' + + +# Create the initial log (can't do this in AtModuleLoad() without 'global') +log = Log() +log.AddEntry('usage_log loaded') + +# Do other work at module load time +AtModuleLoad() |