diff options
author | bradnelson@chromium.org <bradnelson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-09 22:38:44 +0000 |
---|---|---|
committer | bradnelson@chromium.org <bradnelson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-09 22:38:44 +0000 |
commit | fb9a9fa42b71367b1cda5ca444cf93a3ef74cd3b (patch) | |
tree | f610f8a9b7c2b4f96d28905c77d9c830ea50f800 /site_scons/site_tools/component_builders.py | |
parent | 8b4b861f8ef2456c2c643032d78a17f6261fd234 (diff) | |
download | chromium_src-fb9a9fa42b71367b1cda5ca444cf93a3ef74cd3b.zip chromium_src-fb9a9fa42b71367b1cda5ca444cf93a3ef74cd3b.tar.gz chromium_src-fb9a9fa42b71367b1cda5ca444cf93a3ef74cd3b.tar.bz2 |
Dropping in software construction toolkit.
Review URL: http://codereview.chromium.org/6329
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3145 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'site_scons/site_tools/component_builders.py')
-rw-r--r-- | site_scons/site_tools/component_builders.py | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/site_scons/site_tools/component_builders.py b/site_scons/site_tools/component_builders.py new file mode 100644 index 0000000..ae3e336 --- /dev/null +++ b/site_scons/site_tools/component_builders.py @@ -0,0 +1,511 @@ +#!/usr/bin/python2.4 +# Copyright 2008, 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(self): + """Re-initializes component builders module. + + Args: + self: Parent environment. + """ + self = self # 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 + + # 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(rspangler): 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 = [] + components = _RetrieveComponents(package_name, + env.get('COMPONENT_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. + args: Positional arguments. + 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) + + # 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(rspangler): 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'): + return env.StaticObject(*args, **kwargs) + else: + return env.SharedObject(*args, **kwargs) + +#------------------------------------------------------------------------------ + + +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) + + # 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('$COMPONENT_LIBRARY_DIR', need_for_link) + + # Publish output + 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) + + # 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 an alias for running the test in the test directory, if there's a test + # command line. + if env.get('COMPONENT_TEST_CMDLINE'): + # Test program is the first run resource we replicated. + test_program = env.ReplicatePublished('$TESTS_DIR', prog_name, 'run') + env.Replace( + COMMAND_OUTPUT_CMDLINE=env['COMPONENT_TEST_CMDLINE'], + COMMAND_OUTPUT_RUN_DIR='$TESTS_DIR', + ) + test_out = env.CommandOutput( + '$TEST_OUTPUT_DIR/${PROGRAM_BASENAME}.out.txt', 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) + + +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(). + + TODO(rspangler): Should have some sort of support for S/M/L categorization + """ + # 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) + + # 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) + + # 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) + + # 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) + + # Set up deferred call to replicate resources + env.Defer(ComponentProgramDeferred) + + # Return the output nodes + return out_nodes + +#------------------------------------------------------------------------------ + + +def ComponentTestOutput(self, test_name, nodes): + """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. + + Returns: + Passthrough return code from env.Alias(). + """ + + # Add an alias for the test outputs, and add it to the right groups + a = self.Alias(test_name, nodes) + for group in self['COMPONENT_TEST_OUTPUT_GROUPS']: + SCons.Script.Alias(group, a) + + # 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( + COMPONENT_LIBRARY_DIR='$TARGET_ROOT/lib', + 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(rspangler): Why doesn't the following work: + # COMPONENT_TEST_CMDLINE='${SOURCE.abspath}', + # (it generates a SCons error) + COMPONENT_TEST_CMDLINE='${PROGRAM_NAME}', + COMPONENT_STATIC=True, # Static linking is a sensible default. + # Don't publish libraries to the staging dir by themselves by default. + COMPONENT_LIBRARY_PUBLISH=False, + ) + env.Append( + LIBPATH=['$COMPONENT_LIBRARY_DIR'], + RPATH=['$COMPONENT_LIBRARY_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 our pseudo-builder methods + env.AddMethod(_InitializeComponentBuilders) + 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') |