| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/python2.4 |
| 2 # Copyright 2008, Google Inc. |
| 3 # All rights reserved. |
| 4 # |
| 5 # Redistribution and use in source and binary forms, with or without |
| 6 # modification, are permitted provided that the following conditions are |
| 7 # met: |
| 8 # |
| 9 # * Redistributions of source code must retain the above copyright |
| 10 # notice, this list of conditions and the following disclaimer. |
| 11 # * Redistributions in binary form must reproduce the above |
| 12 # copyright notice, this list of conditions and the following disclaimer |
| 13 # in the documentation and/or other materials provided with the |
| 14 # distribution. |
| 15 # * Neither the name of Google Inc. nor the names of its |
| 16 # contributors may be used to endorse or promote products derived from |
| 17 # this software without specific prior written permission. |
| 18 # |
| 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 |
| 31 """Software construction toolkit builders for SCons.""" |
| 32 |
| 33 |
| 34 import SCons |
| 35 |
| 36 |
| 37 __component_list = {} |
| 38 |
| 39 |
| 40 def _InitializeComponentBuilders(self): |
| 41 """Re-initializes component builders module. |
| 42 |
| 43 Args: |
| 44 self: Parent environment. |
| 45 """ |
| 46 self = self # Silence gpylint |
| 47 |
| 48 __component_list.clear() |
| 49 |
| 50 |
| 51 def _RetrieveComponents(component_name, filter_components=None): |
| 52 """Get the list of all components required by the specified component. |
| 53 |
| 54 Args: |
| 55 component_name: Name of the base component. |
| 56 filter_components: List of components NOT to include. |
| 57 |
| 58 Returns: |
| 59 A list of the transitive closure of all components required by the base |
| 60 component. That is, if A requires B and B requires C, this returns [B, C]. |
| 61 |
| 62 """ |
| 63 if filter_components: |
| 64 filter_components = set(filter_components) |
| 65 else: |
| 66 filter_components = set() |
| 67 |
| 68 components = set([component_name]) # Components always require themselves |
| 69 new_components = set(components) |
| 70 while new_components: |
| 71 # Take next new component and add it to the list we've already scanned. |
| 72 c = new_components.pop() |
| 73 components.add(c) |
| 74 # Add to the list of new components any of c's components that we haven't |
| 75 # seen before. |
| 76 new_components.update(__component_list.get(c, set()) |
| 77 - components - filter_components) |
| 78 |
| 79 return list(components) |
| 80 |
| 81 |
| 82 def _StoreComponents(self, component_name): |
| 83 """Stores the list of child components for the specified component. |
| 84 |
| 85 Args: |
| 86 self: Environment containing component. |
| 87 component_name: Name of the component. |
| 88 |
| 89 Adds component references based on the LIBS and COMPONENTS variables in the |
| 90 current environment. Should be called at primary SConscript execution time; |
| 91 use _RetrieveComponents() to get the final components lists in a Defer()'d |
| 92 function. |
| 93 """ |
| 94 |
| 95 components = set() |
| 96 for clist in ('LIBS', 'COMPONENTS'): |
| 97 components.update(map(self.subst, self.Flatten(self[clist]))) |
| 98 |
| 99 if component_name not in __component_list: |
| 100 __component_list[component_name] = set() |
| 101 __component_list[component_name].update(components) |
| 102 |
| 103 |
| 104 def _ComponentPlatformSetup(env, builder_name, **kwargs): |
| 105 """Modify an environment to work with a component builder. |
| 106 |
| 107 Args: |
| 108 env: Environment to clone. |
| 109 builder_name: Name of the builder. |
| 110 kwargs: Keyword arguments. |
| 111 |
| 112 Returns: |
| 113 A modified clone of the environment. |
| 114 """ |
| 115 # Clone environment so we can modify it |
| 116 env = env.Clone() |
| 117 |
| 118 # Add all keyword arguments to the environment |
| 119 for k, v in kwargs.items(): |
| 120 env[k] = v |
| 121 |
| 122 # Call platform-specific component setup function, if any |
| 123 if env.get('COMPONENT_PLATFORM_SETUP'): |
| 124 env['COMPONENT_PLATFORM_SETUP'](env, builder_name) |
| 125 |
| 126 # Return the modified environment |
| 127 return env |
| 128 |
| 129 #------------------------------------------------------------------------------ |
| 130 |
| 131 # TODO(rspangler): Should be possible to refactor programs, test programs, |
| 132 # libs to all publish as packages, for simplicity and code reuse. |
| 133 |
| 134 |
| 135 def ComponentPackageDeferred(env): |
| 136 """Deferred build steps for component package. |
| 137 |
| 138 Args: |
| 139 env: Environment from ComponentPackage(). |
| 140 |
| 141 Sets up the aliases to build the package. |
| 142 """ |
| 143 package_name = env['PACKAGE_NAME'] |
| 144 |
| 145 # Install program and resources |
| 146 all_outputs = [] |
| 147 components = _RetrieveComponents(package_name, |
| 148 env.get('COMPONENT_PACKAGE_FILTER')) |
| 149 for resource, dest_dir in env.get('COMPONENT_PACKAGE_RESOURCES').items(): |
| 150 all_outputs += env.ReplicatePublished(dest_dir, components, resource) |
| 151 |
| 152 # Add installed program and resources to the alias |
| 153 env.Alias(package_name, all_outputs) |
| 154 |
| 155 |
| 156 def ComponentPackage(self, package_name, dest_dir, **kwargs): |
| 157 """Pseudo-builder for package containing other components. |
| 158 |
| 159 Args: |
| 160 self: Environment in which we were called. |
| 161 package_name: Name of package. |
| 162 dest_dir: Destination directory for package. |
| 163 args: Positional arguments. |
| 164 kwargs: Keyword arguments. |
| 165 |
| 166 Returns: |
| 167 The alias node for the package. |
| 168 """ |
| 169 # Clone and modify environment |
| 170 env = _ComponentPlatformSetup(self, 'ComponentPackage', **kwargs) |
| 171 |
| 172 env.Replace( |
| 173 PACKAGE_NAME=package_name, |
| 174 PACKAGE_DIR=dest_dir, |
| 175 ) |
| 176 |
| 177 # Add an empty alias for the package and add it to the right groups |
| 178 a = env.Alias(package_name, []) |
| 179 for group in env['COMPONENT_PACKAGE_GROUPS']: |
| 180 SCons.Script.Alias(group, a) |
| 181 |
| 182 # Store list of components for this program |
| 183 env._StoreComponents(package_name) |
| 184 |
| 185 # Set up deferred call to replicate resources |
| 186 env.Defer(ComponentPackageDeferred) |
| 187 |
| 188 # Return the alias, since it's the only node we have |
| 189 return a |
| 190 |
| 191 #------------------------------------------------------------------------------ |
| 192 |
| 193 |
| 194 def ComponentObject(self, *args, **kwargs): |
| 195 """Pseudo-builder for object to handle platform-dependent type. |
| 196 |
| 197 Args: |
| 198 self: Environment in which we were called. |
| 199 args: Positional arguments. |
| 200 kwargs: Keyword arguments. |
| 201 |
| 202 Returns: |
| 203 Passthrough return code from env.StaticLibrary() or env.SharedLibrary(). |
| 204 |
| 205 TODO(rspangler): Perhaps this should be a generator builder, so it can take |
| 206 a list of inputs and return a list of outputs? |
| 207 """ |
| 208 # Clone and modify environment |
| 209 env = _ComponentPlatformSetup(self, 'ComponentObject', **kwargs) |
| 210 |
| 211 # Make appropriate object type |
| 212 if env.get('COMPONENT_STATIC'): |
| 213 return env.StaticObject(*args, **kwargs) |
| 214 else: |
| 215 return env.SharedObject(*args, **kwargs) |
| 216 |
| 217 #------------------------------------------------------------------------------ |
| 218 |
| 219 |
| 220 def ComponentLibrary(self, lib_name, *args, **kwargs): |
| 221 """Pseudo-builder for library to handle platform-dependent type. |
| 222 |
| 223 Args: |
| 224 self: Environment in which we were called. |
| 225 lib_name: Library name. |
| 226 args: Positional arguments. |
| 227 kwargs: Keyword arguments. |
| 228 |
| 229 Returns: |
| 230 Passthrough return code from env.StaticLibrary() or env.SharedLibrary(). |
| 231 """ |
| 232 # Clone and modify environment |
| 233 env = _ComponentPlatformSetup(self, 'ComponentLibrary', **kwargs) |
| 234 |
| 235 # Make appropriate library type |
| 236 if env.get('COMPONENT_STATIC'): |
| 237 lib_outputs = env.StaticLibrary(lib_name, *args, **kwargs) |
| 238 else: |
| 239 lib_outputs = env.SharedLibrary(lib_name, *args, **kwargs) |
| 240 |
| 241 # Scan library outputs for files we need to link against this library, and |
| 242 # files we need to run executables linked against this library. |
| 243 need_for_link = [] |
| 244 need_for_debug = [] |
| 245 need_for_run = [] |
| 246 for o in lib_outputs: |
| 247 if o.suffix in env['COMPONENT_LIBRARY_LINK_SUFFIXES']: |
| 248 need_for_link.append(o) |
| 249 if o.suffix in env['COMPONENT_LIBRARY_DEBUG_SUFFIXES']: |
| 250 need_for_debug.append(o) |
| 251 if o.suffix == env['SHLIBSUFFIX']: |
| 252 need_for_run.append(o) |
| 253 all_outputs = lib_outputs |
| 254 |
| 255 # Install library in intermediate directory, so other libs and programs can |
| 256 # link against it |
| 257 all_outputs += env.Replicate('$COMPONENT_LIBRARY_DIR', need_for_link) |
| 258 |
| 259 # Publish output |
| 260 env.Publish(lib_name, 'run', need_for_run) |
| 261 env.Publish(lib_name, 'debug', need_for_debug) |
| 262 |
| 263 # Add an alias to build and copy the library, and add it to the right groups |
| 264 a = self.Alias(lib_name, all_outputs) |
| 265 for group in env['COMPONENT_LIBRARY_GROUPS']: |
| 266 SCons.Script.Alias(group, a) |
| 267 |
| 268 # Store list of components for this library |
| 269 env._StoreComponents(lib_name) |
| 270 |
| 271 # If library should publish itself, publish as if it was a program |
| 272 if env.get('COMPONENT_LIBRARY_PUBLISH'): |
| 273 env['PROGRAM_BASENAME'] = lib_name |
| 274 env.Defer(ComponentProgramDeferred) |
| 275 |
| 276 # Return the library outputs |
| 277 return lib_outputs |
| 278 |
| 279 #------------------------------------------------------------------------------ |
| 280 |
| 281 |
| 282 def ComponentTestProgramDeferred(env): |
| 283 """Deferred build steps for test program. |
| 284 |
| 285 Args: |
| 286 env: Environment from ComponentTestProgram(). |
| 287 |
| 288 Sets up the aliases to compile and run the test program. |
| 289 """ |
| 290 prog_name = env['PROGRAM_BASENAME'] |
| 291 |
| 292 # Install program and resources |
| 293 all_outputs = [] |
| 294 components = _RetrieveComponents(prog_name) |
| 295 for resource, dest_dir in env.get('COMPONENT_TEST_RESOURCES').items(): |
| 296 all_outputs += env.ReplicatePublished(dest_dir, components, resource) |
| 297 |
| 298 # Add installed program and resources to the alias |
| 299 env.Alias(prog_name, all_outputs) |
| 300 |
| 301 # Add an alias for running the test in the test directory, if there's a test |
| 302 # command line. |
| 303 if env.get('COMPONENT_TEST_CMDLINE'): |
| 304 # Test program is the first run resource we replicated. |
| 305 test_program = env.ReplicatePublished('$TESTS_DIR', prog_name, 'run') |
| 306 env.Replace( |
| 307 COMMAND_OUTPUT_CMDLINE=env['COMPONENT_TEST_CMDLINE'], |
| 308 COMMAND_OUTPUT_RUN_DIR='$TESTS_DIR', |
| 309 ) |
| 310 test_out = env.CommandOutput( |
| 311 '$TEST_OUTPUT_DIR/${PROGRAM_BASENAME}.out.txt', test_program) |
| 312 # Running the test requires the test and its libs copied to the tests dir |
| 313 env.Depends(test_out, all_outputs) |
| 314 env.ComponentTestOutput('run_' + prog_name, test_out) |
| 315 |
| 316 |
| 317 def ComponentTestProgram(self, prog_name, *args, **kwargs): |
| 318 """Pseudo-builder for test program to handle platform-dependent type. |
| 319 |
| 320 Args: |
| 321 self: Environment in which we were called. |
| 322 prog_name: Test program name. |
| 323 args: Positional arguments. |
| 324 kwargs: Keyword arguments. |
| 325 |
| 326 Returns: |
| 327 Output node list from env.Program(). |
| 328 |
| 329 TODO(rspangler): Should have some sort of support for S/M/L categorization |
| 330 """ |
| 331 # Clone and modify environment |
| 332 env = _ComponentPlatformSetup(self, 'ComponentTestProgram', **kwargs) |
| 333 |
| 334 env['PROGRAM_BASENAME'] = prog_name |
| 335 env['PROGRAM_NAME'] = '$PROGPREFIX$PROGRAM_BASENAME$PROGSUFFIX' |
| 336 |
| 337 # Call env.Program() |
| 338 out_nodes = env.Program(prog_name, *args, **kwargs) |
| 339 |
| 340 # Publish output |
| 341 env.Publish(prog_name, 'run', out_nodes[0]) |
| 342 env.Publish(prog_name, 'debug', out_nodes[1:]) |
| 343 |
| 344 # Add an alias to build the program to the right groups |
| 345 a = env.Alias(prog_name, out_nodes) |
| 346 for group in env['COMPONENT_TEST_PROGRAM_GROUPS']: |
| 347 SCons.Script.Alias(group, a) |
| 348 |
| 349 # Store list of components for this program |
| 350 env._StoreComponents(prog_name) |
| 351 |
| 352 # Set up deferred call to replicate resources and run test |
| 353 env.Defer(ComponentTestProgramDeferred) |
| 354 |
| 355 # Return the output node |
| 356 return out_nodes |
| 357 |
| 358 #------------------------------------------------------------------------------ |
| 359 |
| 360 |
| 361 def ComponentProgramDeferred(env): |
| 362 """Deferred build steps for program. |
| 363 |
| 364 Args: |
| 365 env: Environment from ComponentProgram(). |
| 366 |
| 367 Sets up the aliases to compile the program. |
| 368 """ |
| 369 prog_name = env['PROGRAM_BASENAME'] |
| 370 |
| 371 # Install program and resources |
| 372 all_outputs = [] |
| 373 components = _RetrieveComponents(prog_name) |
| 374 for resource, dest_dir in env.get('COMPONENT_PROGRAM_RESOURCES').items(): |
| 375 all_outputs += env.ReplicatePublished(dest_dir, components, resource) |
| 376 |
| 377 # Add installed program and resources to the alias |
| 378 env.Alias(prog_name, all_outputs) |
| 379 |
| 380 |
| 381 def ComponentProgram(self, prog_name, *args, **kwargs): |
| 382 """Pseudo-builder for program to handle platform-dependent type. |
| 383 |
| 384 Args: |
| 385 self: Environment in which we were called. |
| 386 prog_name: Test program name. |
| 387 args: Positional arguments. |
| 388 kwargs: Keyword arguments. |
| 389 |
| 390 Returns: |
| 391 Output node list from env.Program(). |
| 392 """ |
| 393 # Clone and modify environment |
| 394 env = _ComponentPlatformSetup(self, 'ComponentProgram', **kwargs) |
| 395 |
| 396 env['PROGRAM_BASENAME'] = prog_name |
| 397 |
| 398 # Call env.Program() |
| 399 out_nodes = env.Program(prog_name, *args, **kwargs) |
| 400 |
| 401 # Publish output |
| 402 env.Publish(prog_name, 'run', out_nodes[0]) |
| 403 env.Publish(prog_name, 'debug', out_nodes[1:]) |
| 404 |
| 405 # Add an alias to build the program to the right groups |
| 406 a = env.Alias(prog_name, out_nodes) |
| 407 for group in env['COMPONENT_PROGRAM_GROUPS']: |
| 408 SCons.Script.Alias(group, a) |
| 409 |
| 410 # Store list of components for this program |
| 411 env._StoreComponents(prog_name) |
| 412 |
| 413 # Set up deferred call to replicate resources |
| 414 env.Defer(ComponentProgramDeferred) |
| 415 |
| 416 # Return the output nodes |
| 417 return out_nodes |
| 418 |
| 419 #------------------------------------------------------------------------------ |
| 420 |
| 421 |
| 422 def ComponentTestOutput(self, test_name, nodes): |
| 423 """Pseudo-builder for test output. |
| 424 |
| 425 Args: |
| 426 self: Environment in which we were called. |
| 427 test_name: Test name. |
| 428 nodes: List of files/Nodes output by the test. |
| 429 |
| 430 Returns: |
| 431 Passthrough return code from env.Alias(). |
| 432 """ |
| 433 |
| 434 # Add an alias for the test outputs, and add it to the right groups |
| 435 a = self.Alias(test_name, nodes) |
| 436 for group in self['COMPONENT_TEST_OUTPUT_GROUPS']: |
| 437 SCons.Script.Alias(group, a) |
| 438 |
| 439 # Return the output node |
| 440 return a |
| 441 |
| 442 #------------------------------------------------------------------------------ |
| 443 |
| 444 |
| 445 def generate(env): |
| 446 # NOTE: SCons requires the use of this name, which fails gpylint. |
| 447 """SCons entry point for this tool.""" |
| 448 |
| 449 env.Replace( |
| 450 COMPONENT_LIBRARY_DIR='$TARGET_ROOT/lib', |
| 451 STAGING_DIR='$TARGET_ROOT/staging', |
| 452 TESTS_DIR='$TARGET_ROOT/tests', |
| 453 TEST_OUTPUT_DIR='$TARGET_ROOT/test_output', |
| 454 # Default command line for a test is just the name of the file. |
| 455 # TODO(rspangler): Why doesn't the following work: |
| 456 # COMPONENT_TEST_CMDLINE='${SOURCE.abspath}', |
| 457 # (it generates a SCons error) |
| 458 COMPONENT_TEST_CMDLINE='${PROGRAM_NAME}', |
| 459 COMPONENT_STATIC=True, # Static linking is a sensible default. |
| 460 # Don't publish libraries to the staging dir by themselves by default. |
| 461 COMPONENT_LIBRARY_PUBLISH=False, |
| 462 ) |
| 463 env.Append( |
| 464 LIBPATH=['$COMPONENT_LIBRARY_DIR'], |
| 465 RPATH=['$COMPONENT_LIBRARY_DIR'], |
| 466 |
| 467 # Default alias groups for component builders |
| 468 COMPONENT_PACKAGE_GROUPS=['all_packages'], |
| 469 COMPONENT_LIBRARY_GROUPS=['all_libraries'], |
| 470 COMPONENT_PROGRAM_GROUPS=['all_programs'], |
| 471 COMPONENT_TEST_PROGRAM_GROUPS=['all_test_programs'], |
| 472 COMPONENT_TEST_OUTPUT_GROUPS=['run_all_tests'], |
| 473 |
| 474 # Additional components whose resources should be copied into program |
| 475 # directories, in addition to those from LIBS and the program itself. |
| 476 LIBS=[], |
| 477 COMPONENTS=[], |
| 478 |
| 479 # Dicts of what resources should go in each destination directory for |
| 480 # programs and test programs. |
| 481 COMPONENT_PACKAGE_RESOURCES={ |
| 482 'run': '$PACKAGE_DIR', |
| 483 'debug': '$PACKAGE_DIR', |
| 484 }, |
| 485 COMPONENT_PROGRAM_RESOURCES={ |
| 486 'run': '$STAGING_DIR', |
| 487 'debug': '$STAGING_DIR', |
| 488 }, |
| 489 COMPONENT_TEST_RESOURCES={ |
| 490 'run': '$TESTS_DIR', |
| 491 'debug': '$TESTS_DIR', |
| 492 'test_input': '$TESTS_DIR', |
| 493 }, |
| 494 ) |
| 495 |
| 496 # Add our pseudo-builder methods |
| 497 env.AddMethod(_InitializeComponentBuilders) |
| 498 env.AddMethod(_StoreComponents) |
| 499 env.AddMethod(ComponentPackage) |
| 500 env.AddMethod(ComponentObject) |
| 501 env.AddMethod(ComponentLibrary) |
| 502 env.AddMethod(ComponentProgram) |
| 503 env.AddMethod(ComponentTestProgram) |
| 504 env.AddMethod(ComponentTestOutput) |
| 505 |
| 506 # Add our target groups |
| 507 AddTargetGroup('all_libraries', 'libraries can be built') |
| 508 AddTargetGroup('all_programs', 'programs can be built') |
| 509 AddTargetGroup('all_test_programs', 'tests can be built') |
| 510 AddTargetGroup('all_packages', 'packages can be built') |
| 511 AddTargetGroup('run_all_tests', 'tests can be run') |
| OLD | NEW |