diff options
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() |