summaryrefslogtreecommitdiffstats
path: root/site_scons/site_tools/component_builders.py
diff options
context:
space:
mode:
authorbradnelson@chromium.org <bradnelson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-09 22:38:44 +0000
committerbradnelson@chromium.org <bradnelson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-09 22:38:44 +0000
commitfb9a9fa42b71367b1cda5ca444cf93a3ef74cd3b (patch)
treef610f8a9b7c2b4f96d28905c77d9c830ea50f800 /site_scons/site_tools/component_builders.py
parent8b4b861f8ef2456c2c643032d78a17f6261fd234 (diff)
downloadchromium_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.py511
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')