[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 1 | #! -*- python -*- |
| 2 | # |
[email protected] | aeaed3b6 | 2012-01-12 18:58:52 | [diff] [blame^] | 3 | # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | |
| 7 | """ Main scons script for Native Client SDK builds. |
| 8 | |
| 9 | Do not invoke this script directly, but instead use the scons or scons.bat |
| 10 | wrapper function. E.g. |
| 11 | |
| 12 | Linux or Mac: |
| 13 | ./scons [Options...] |
| 14 | |
| 15 | Windows: |
| 16 | scons.bat [Options...] |
| 17 | """ |
| 18 | |
| 19 | from __future__ import with_statement |
| 20 | |
| 21 | import os |
| 22 | import platform |
| 23 | import subprocess |
| 24 | import sys |
| 25 | import toolchainbinaries |
| 26 | from build_tools import build_utils |
| 27 | |
| 28 | # ---------------------------------------------------------------------------- |
| 29 | HELP_STRING = """ |
| 30 | =============================================================================== |
| 31 | Help for NaCl SDK |
| 32 | =============================================================================== |
| 33 | |
| 34 | * cleaning: ./scons -c |
| 35 | * build a target: ./scons <target> |
| 36 | |
| 37 | Supported targets: |
| 38 | * bot Runs everything that the build and try bots run. |
| 39 | * debug_server_x64 Build the out-of-process debug_server. |
| 40 | * debug_server_Win32 Build the out-of-process debug_server. |
| 41 | * docs Build all of the Doxygen documentation. |
| 42 | * examples Build the examples. |
[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 43 | * installer Build the SDK installer. |
| 44 | * nacl-bpad Build the native client crash reporting tool. |
| 45 | * sdk_tools Build nacl_sdk.zip and sdk_tools.tgz |
| 46 | * toolchain Update the toolchain's headers and libraries. |
| 47 | * vsx Build the Visual Studio Plugin. |
| 48 | |
| 49 | Flags: |
| 50 | * USE_EXISTING_INSTALLER=1 Do not rebuild the installer if it exists. |
| 51 | * chrome_browser_path=<full_path> Download Chrome to <full_path>. |
| 52 | * SHOW_BROWSER=1 Don't suppress browser GUI while performing |
| 53 | browser tests in Linux. |
| 54 | |
| 55 | More targets are listed below in the automatically generated help section. |
| 56 | |
| 57 | =============================================================================== |
| 58 | Automatically generated help follows: |
| 59 | =============================================================================== |
| 60 | """ |
| 61 | |
| 62 | # ---------------------------------------------------------------------------- |
| 63 | # Perform some environment checks before running. |
| 64 | # Note that scons should set NACL_SDK_ROOT before this script runs. |
| 65 | |
| 66 | if os.getenv('NACL_SDK_ROOT') is None: |
| 67 | sys.stderr.write('NACL_SDK_ROOT must be defined as the root directory' |
| 68 | ' of NaCl SDK.\n') |
| 69 | sys.exit(1) |
| 70 | |
| 71 | # By default, run with a parallel build (i.e. '-j num_jobs'). |
| 72 | # Use a default value proportional to the number of cpu cores on the system. |
| 73 | # To run a serial build, explicitly type '-j 1' on the command line. |
| 74 | try: |
| 75 | import multiprocessing |
| 76 | CORE_COUNT = multiprocessing.cpu_count() |
| 77 | except (ImportError, NotImplementedError): |
| 78 | CORE_COUNT = 2 # Our buildbots seem to be dual-core typically |
| 79 | |
| 80 | SetOption('num_jobs', CORE_COUNT * 2) |
| 81 | print 'Building with', GetOption('num_jobs'), 'parallel jobs' |
| 82 | |
| 83 | # ---------------------------------------------------------------------------- |
| 84 | # The environment_list contains all the build environments that we want to |
| 85 | # specify. Selecting a particular environment is done using the --mode option. |
| 86 | # Each environment that we support gets appended to this list. |
| 87 | environment_list = [] |
| 88 | |
| 89 | # ---------------------------------------------------------------------------- |
| 90 | # Create the base environment, from which all other environments are derived. |
| 91 | base_env = Environment( |
| 92 | tools = ['component_setup'], |
| 93 | CPPPATH = ['$MAIN_DIR'], |
| 94 | CPPDEFINES = [ |
| 95 | 'BOOST_ALL_NO_LIB', |
| 96 | ], |
| 97 | NACL_TOOLCHAIN_ROOTS = { |
| 98 | ('x86', 'newlib'): |
| 99 | build_utils.NormalizeToolchain(arch='x86', variant='newlib'), |
| 100 | ('x86', 'glibc'): |
| 101 | build_utils.NormalizeToolchain(arch='x86', variant='glibc'), |
| 102 | }, |
| 103 | ROOT_DIR = os.path.abspath(os.getcwd()), |
[email protected] | 5f819e2 | 2011-11-29 01:39:23 | [diff] [blame] | 104 | SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.getcwd()))), |
[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 105 | IS_WINDOWS = sys.platform in ['cygwin', 'win32'], |
| 106 | IS_LINUX = sys.platform == 'linux2', |
| 107 | IS_MAC = sys.platform == 'darwin', |
| 108 | JOB_COUNT = GetOption('num_jobs') |
| 109 | ) |
| 110 | |
| 111 | # It is possible to override these values on the command line by typing |
| 112 | # something like this: |
| 113 | # PYTHON=/path/to/my/python |
| 114 | base_env.SetDefault( |
| 115 | PYTHON = ARGUMENTS.get('PYTHON', 'python'), |
| 116 | USE_EXISTING_INSTALLER = ARGUMENTS.get('USE_EXISTING_INSTALLER', False), |
| 117 | SHOW_BROWSER = ARGUMENTS.get('SHOW_BROWSER', False), |
| 118 | ) |
| 119 | |
| 120 | base_env.Append( |
| 121 | BUILD_SCONSCRIPTS = [ |
| 122 | # Keep in alphabetical order |
| 123 | 'build_tools/build.scons', |
[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 124 | 'documentation/build.scons', |
[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 125 | 'project_templates/test.scons', |
| 126 | ], |
| 127 | ) |
| 128 | |
| 129 | base_env.Help(HELP_STRING) |
| 130 | |
| 131 | KNOWN_SUITES = frozenset([ |
| 132 | 'bot', |
| 133 | ]) |
| 134 | |
| 135 | |
| 136 | def HasBotTarget(env): |
| 137 | if 'bot' in COMMAND_LINE_TARGETS: |
| 138 | return True |
| 139 | return False |
| 140 | |
| 141 | base_env.AddMethod(HasBotTarget) |
| 142 | |
| 143 | |
| 144 | def CheckSuiteName(suite, node_name): |
| 145 | '''Check whether a given test suite or alias name is a known name. |
| 146 | |
| 147 | If the suite name is not in the approved list, then this function throws |
| 148 | an exception, with the node_name within the error message. |
| 149 | |
| 150 | Args: |
| 151 | suite: a name of a suite that must be in the KNOWN_SUITES set |
| 152 | node_name: The name of the node. This is used for error messages |
| 153 | ''' |
| 154 | if suite not in KNOWN_SUITES: |
| 155 | raise Exception('Testsuite/Alias "%s" for target "%s" is unknown' % |
| 156 | (suite, node_name)) |
| 157 | |
| 158 | |
| 159 | def AddNodeToTestSuite(env, node, suite_names, node_name, test_size='all'): |
| 160 | '''Adds a test node to a given set of suite names |
| 161 | |
| 162 | These tests are automatically added to the run_all_tests target and are |
| 163 | listed in the help screen. |
| 164 | |
| 165 | This function is loosely based on a function of the same name in the |
| 166 | Native Client repository |
| 167 | |
| 168 | Args: |
| 169 | env - The environment from which this function was called |
| 170 | node - A scons node (e.g., file, command, etc) to be added to set suite |
| 171 | suite_names - A list of test suite names. For none, pass an empty list |
| 172 | node_name - The target name used for running this test |
| 173 | test_size - The relative run-time of this test: small, medium, or large |
| 174 | ''' |
| 175 | |
| 176 | # CommandTest can return an empty list when it silently discards a test |
| 177 | if not node: |
| 178 | return |
| 179 | |
| 180 | AlwaysBuild(node) |
| 181 | |
| 182 | for s in suite_names: |
| 183 | CheckSuiteName(s, node_name) |
| 184 | env.Alias(s, node) |
| 185 | |
| 186 | if test_size not in ['small', 'medium', 'large', 'all']: |
| 187 | raise Exception('Invalid test size for %s' % node_name) |
| 188 | |
| 189 | # Note that COMPONENT_TEST_SIZE is set to 'large' by default, which |
| 190 | # populates a largely redundant list of 'large' tests. Note that all |
| 191 | # tests are added to 'all', so setting test_size='all' is a no-op |
| 192 | env.ComponentTestOutput(node_name, node, COMPONENT_TEST_SIZE=test_size) |
| 193 | |
| 194 | base_env.AddMethod(AddNodeToTestSuite) |
| 195 | |
| 196 | |
| 197 | def ShouldBeCleaned(env, targets, suite_names, node_name): |
| 198 | '''Determines whether a given set of targets require cleaning. |
| 199 | |
| 200 | Args: |
| 201 | env - The calling environment. |
| 202 | targets - Any build artifacts to which a cleaning step might apply. |
| 203 | Any false object indicates that this check is skipped. |
| 204 | suite_names - Any suites that might produce |targets| |
| 205 | node_name - A node that might produce |targets| |
| 206 | ''' |
| 207 | if not env.GetOption('clean'): |
| 208 | return False |
| 209 | |
| 210 | if len(COMMAND_LINE_TARGETS) > 0: |
| 211 | clean_this = False |
| 212 | for cl_target in COMMAND_LINE_TARGETS: |
| 213 | if cl_target in suite_names or cl_target == node_name: |
| 214 | clean_this = True |
| 215 | break |
| 216 | if not clean_this: |
| 217 | return False |
| 218 | |
| 219 | if not targets: |
| 220 | return True |
| 221 | for target in targets: |
| 222 | if os.path.exists(target): |
| 223 | return True |
| 224 | return False |
| 225 | |
| 226 | |
| 227 | def AddCleanAction(env, targets, action, suite_names, node_name): |
| 228 | '''Adds a cleanup action that scons cannot detect automatically. |
| 229 | |
| 230 | Cleaning will only occur if there is a match between the suite or nodes |
| 231 | specified on the command line, and suite_names or node_name or if no |
| 232 | suite or nodes are specified on the command line. Also, at least one of the |
| 233 | targets must exist on the file system. |
| 234 | |
| 235 | Args: |
| 236 | env - The calling environment |
| 237 | targets - Artifacts to be cleaned. |
| 238 | action - The action to be performed. It is up to the caller to ensure |
| 239 | that |action| will actually remove |targets| |
| 240 | suite_names - Any suites to which this cleanup target applies. |
| 241 | node_name - Any nodes to which this cleanup target applies. |
| 242 | ''' |
| 243 | if ShouldBeCleaned(env, targets, suite_names, node_name): |
| 244 | env.Execute(action) |
| 245 | |
| 246 | base_env.AddMethod(AddCleanAction) |
| 247 | |
| 248 | |
| 249 | def AddNodeAliases(env, node, suite_names, node_name): |
| 250 | '''Allow a given node to be built under a different name or as a suite |
| 251 | |
| 252 | Args: |
| 253 | env - The calling environment |
| 254 | node - A target node to add to a known build alias (e.g., 'bot') |
| 255 | suite_names - A list of suite names. For none, pass an empty list. This |
| 256 | node will be run whenever any of these suites are invoked. |
| 257 | Each suite name must match a string in KNOWN_SUITES. |
| 258 | node_name - The name of this node, when run by itself |
| 259 | ''' |
| 260 | |
| 261 | if not node: |
| 262 | return |
| 263 | |
| 264 | for s in suite_names: |
| 265 | CheckSuiteName(s, node_name) |
| 266 | env.Alias(s, node) |
| 267 | |
| 268 | env.Alias(node_name, node) |
| 269 | |
| 270 | base_env.AddMethod(AddNodeAliases) |
| 271 | |
| 272 | |
| 273 | def CreatePythonUnitTest(env, filename, dependencies=None, disabled=False, |
| 274 | params=None, buffered=True, banner=None): |
| 275 | """Returns a new build command that will run a unit test with a given file. |
| 276 | |
| 277 | Args: |
| 278 | env: SCons environment |
| 279 | filename: The python file that contains the unit test |
| 280 | dependencies: An optional list of other files that this unit test uses |
| 281 | disabled: Setting this to True will prevent the test from running |
| 282 | params: Optional additional parameters for python command |
| 283 | buffered: True=stdout is buffered until entirely complete; |
| 284 | False=stdout is immediately displayed as it occurs. |
| 285 | banner: (optional) annotation banner for build/try bots |
| 286 | |
| 287 | Returns: |
| 288 | A SCons command node |
| 289 | """ |
| 290 | dependencies = dependencies or [] |
| 291 | params = params or [] |
| 292 | |
| 293 | basename = os.path.splitext(os.path.basename(filename))[0] |
| 294 | outfilename = "%s_output.txt" % basename |
| 295 | |
| 296 | |
| 297 | def RunPythonUnitTest(env, target, source): |
| 298 | """Runs unit tests using the given target as a command. |
| 299 | |
| 300 | The argument names of this test are not very intuitive but match what is |
| 301 | used conventionally throughout scons. If the string "PASSED" does not |
| 302 | occur in target when this exits, the test has failed; also a scons |
| 303 | convention. |
| 304 | |
| 305 | Args: |
| 306 | env: SCons's current environment. |
| 307 | target: Where to write the result of the test. |
| 308 | source: The command to run as the test. |
| 309 | |
| 310 | Returns: |
| 311 | None for good status |
| 312 | An error string for bad status |
| 313 | """ |
| 314 | bot = build_utils.BotAnnotator() |
| 315 | if banner: |
| 316 | bot.BuildStep(banner) |
| 317 | |
| 318 | if disabled: |
| 319 | sys.stdout.write("Test %s is disabled.\n" % basename) |
| 320 | sys.stdout.flush() |
| 321 | return None # return with good status |
| 322 | |
| 323 | import subprocess |
| 324 | |
| 325 | app = [str(env['PYTHON']), str(source[0].abspath)] + map( |
| 326 | lambda param: param if type(param) is str else str(param.abspath), |
| 327 | params) |
| 328 | bot.Print('Running: %s' % app) |
| 329 | app_env = os.environ.copy() |
| 330 | # We have to do this because scons overrides PYTHONPATH and does |
| 331 | # not preserve what is provided by the OS. |
| 332 | python_path = [env['ROOT_DIR'], app_env['PYMOX'], app_env['PYTHONPATH']] |
| 333 | app_env['PYTHONPATH'] = os.pathsep.join(python_path) |
| 334 | ret_val = 'Error: General Test Failure' # Indicates failure, by default |
| 335 | target_str = str(target[0]) |
| 336 | with open(target_str, 'w') as outfile: |
| 337 | def Write(str): |
| 338 | if buffered: |
| 339 | outfile.write(str) |
| 340 | outfile.flush() |
| 341 | else: |
| 342 | sys.stdout.write(str) |
| 343 | sys.stdout.flush() |
| 344 | Write('\n-----Begin output for Test: %s\n' % basename) |
| 345 | if subprocess.call(app, env=app_env, |
| 346 | stdout=outfile if buffered else None, |
| 347 | stderr=outfile if buffered else None): |
| 348 | Write('-----Error: unit test failed\n') |
| 349 | ret_val = 'Error: Test Failure in %s' % basename |
| 350 | else: |
| 351 | ret_val = None # Indicates success |
| 352 | |
| 353 | Write('-----End output for Test: %s\n' % basename) |
| 354 | if buffered: |
| 355 | with open(target_str, 'r') as resultfile: |
| 356 | sys.stdout.write(resultfile.read()) |
| 357 | sys.stdout.flush() |
| 358 | |
| 359 | if ret_val: |
| 360 | bot.BuildStepFailure() |
| 361 | |
| 362 | return ret_val |
| 363 | |
| 364 | cmd = env.Command(outfilename, filename, RunPythonUnitTest) |
| 365 | env.Depends(cmd, dependencies) |
| 366 | # Create dependencies for all the env.File parameters and other scons nodes |
| 367 | for param in params: |
| 368 | if type(param) is not str: |
| 369 | env.Depends(cmd, param) |
| 370 | |
| 371 | return cmd |
| 372 | |
| 373 | base_env.AddMethod(CreatePythonUnitTest) |
| 374 | |
| 375 | |
| 376 | # ---------------------------------------------------------------------------- |
| 377 | # Support for running Chrome. These functions access the construction |
| 378 | # Environment() to produce a path to Chrome. |
| 379 | |
| 380 | # A Dir object representing the directory where the Chrome binaries are kept. |
| 381 | # You can use chrome_binaries_dir= to set this on the command line. Defaults |
| 382 | # to chrome_binaries. |
| 383 | base_env['CHROME_DOWNLOAD_DIR'] = \ |
| 384 | base_env.Dir(ARGUMENTS.get('chrome_binaries_dir', '#chrome_binaries')) |
| 385 | |
| 386 | |
| 387 | def ChromeArchitectureSpec(env): |
| 388 | '''Determine the architecture spec for the Chrome binary. |
| 389 | |
| 390 | The architecture spec is a string that represents the host architecture. |
| 391 | Possible values are: |
| 392 | x86-32 |
| 393 | x86-64 |
| 394 | On Mac and Windows, the architecture spec is always x86-32, because there are |
| 395 | no 64-bit version available. |
| 396 | |
| 397 | Returns: An architecture spec string for the host CPU. |
| 398 | ''' |
| 399 | arch, _ = platform.architecture(); |
| 400 | # On Mac and Windows, always use a 32-bit version of Chrome (64-bit versions |
| 401 | # are not available). |
| 402 | if env['IS_WINDOWS'] or env['IS_MAC']: |
| 403 | arch = 'x86-32' |
| 404 | else: |
| 405 | arch = 'x86-64' if '64' in arch else 'x86-32' |
| 406 | return arch |
| 407 | |
| 408 | base_env.AddMethod(ChromeArchitectureSpec) |
| 409 | |
| 410 | |
| 411 | def GetDefaultChromeBinary(env): |
| 412 | '''Get a File object that represents a Chrome binary. |
| 413 | |
| 414 | By default, the test infrastructure will download a copy of Chrome that can |
| 415 | be used for testing. This method returns a File object that represents the |
| 416 | downloaded Chrome binary that can be run by tests. Note that the path to the |
| 417 | binary is specific to the host platform, for example the path on Linux |
| 418 | is <chrome_dir>/linux/<arch>/chrome, while on Mac it's |
| 419 | <chrome_dir>/mac/<arch>/Chromium.app/Contents.MacOS/Chromium. |
| 420 | |
| 421 | Returns: A File object representing the Chrome binary. |
| 422 | ''' |
| 423 | if env['IS_LINUX']: |
| 424 | os_name = 'linux' |
| 425 | binary = 'chrome' |
| 426 | elif env['IS_WINDOWS']: |
| 427 | os_name = 'windows' |
| 428 | binary = 'chrome.exe' |
| 429 | elif env['IS_MAC']: |
| 430 | os_name = 'mac' |
| 431 | binary = 'Chromium.app/Contents/MacOS/Chromium' |
| 432 | else: |
| 433 | raise Exception('Unsupported OS') |
| 434 | |
| 435 | return env.File(os.path.join( |
| 436 | '${CHROME_DOWNLOAD_DIR}', |
| 437 | '%s_%s' % (os_name, env.ChromeArchitectureSpec()), |
| 438 | binary)) |
| 439 | |
| 440 | base_env.AddMethod(GetDefaultChromeBinary) |
| 441 | |
| 442 | |
| 443 | def GetChromeBinary(env): |
| 444 | '''Return a File object that represents the downloaded Chrome binary. |
| 445 | |
| 446 | If chrome_browser_path is specified on the command line, then return a File |
| 447 | object that represents that path. Otherwise, return a File object |
| 448 | representing the default downloaded Chrome (see GetDefaultChromeBinary(), |
| 449 | above). |
| 450 | |
| 451 | Returns: A File object representing a Chrome binary. |
| 452 | ''' |
| 453 | return env.File(ARGUMENTS.get('chrome_browser_path', |
| 454 | env.GetDefaultChromeBinary())) |
| 455 | |
| 456 | base_env.AddMethod(GetChromeBinary) |
| 457 | |
| 458 | |
| 459 | def DependsOnChrome(env, dependency): |
| 460 | '''Create a dependency on the download of Chrome. |
| 461 | |
| 462 | Creates a dependency in |env| such that Chrome gets downloaded (if necessary) |
| 463 | whenever |dependency| changes. Uses the Chrome downloader scripts built |
| 464 | into NaCl; this script expects NaCl to be DEPS'ed into |
| 465 | third_party/native_client/native_client. |
| 466 | |
| 467 | The Chrome binary is added as a precious node to the base Environment. If |
| 468 | we added it to the build environment env, then downloading chrome would in |
| 469 | effect be specified for multiple environments. |
| 470 | ''' |
| 471 | if not hasattr(base_env, 'download_chrome_node'): |
| 472 | chrome_binary = env.GetDefaultChromeBinary() |
| 473 | download_chrome_script = build_utils.JoinPathToNaClRepo( |
| 474 | 'native_client', 'build', 'download_chrome.py', |
| 475 | root_dir=env['ROOT_DIR']) |
| 476 | base_env.download_chrome_node = env.Command( |
| 477 | chrome_binary, |
| 478 | [], |
| 479 | '${PYTHON} %s --arch=%s --dst=${CHROME_DOWNLOAD_DIR}' % |
| 480 | (download_chrome_script, env.ChromeArchitectureSpec())) |
| 481 | # This stops Scons from deleting the file before running the step above. |
| 482 | env.NoClean(chrome_binary) |
| 483 | env.Precious(chrome_binary) |
| 484 | env.Depends(dependency, base_env.download_chrome_node) |
| 485 | |
| 486 | base_env.AddMethod(DependsOnChrome) |
| 487 | |
| 488 | |
| 489 | # ---------------------------------------------------------------------------- |
| 490 | # Targets for updating sdk headers and libraries |
| 491 | # NACL_SDK_XXX vars are defined by site_scons/site_tools/naclsdk.py |
| 492 | # NOTE: Our task here is complicated by the fact that there might already be |
| 493 | # some (outdated) headers/libraries at the new location |
| 494 | # One of the hacks we employ here is to make every library depend |
| 495 | # on the installation on ALL headers (sdk_headers) |
| 496 | |
| 497 | # Contains all the headers to be installed |
| 498 | sdk_headers = base_env.Alias('extra_sdk_update_header', []) |
| 499 | # Contains all the libraries and .o files to be installed |
| 500 | libs_platform = base_env.Alias('extra_sdk_libs_platform', []) |
| 501 | libs = base_env.Alias('extra_sdk_libs', []) |
| 502 | base_env.Alias('extra_sdk_update', [libs, libs_platform]) |
| 503 | |
| 504 | AlwaysBuild(sdk_headers) |
| 505 | |
| 506 | # ---------------------------------------------------------------------------- |
| 507 | # The following section contains proxy nodes which can be used to create |
| 508 | # dependencies between targets that are not in the same scope or environment. |
| 509 | toolchain_node = base_env.Alias('toolchain', []) |
| 510 | |
| 511 | |
| 512 | def GetToolchainNode(env): |
| 513 | '''Returns the node associated with the toolchain build target''' |
| 514 | return toolchain_node |
| 515 | |
| 516 | base_env.AddMethod(GetToolchainNode) |
| 517 | |
| 518 | |
| 519 | def GetHeadersNode(env): |
| 520 | return sdk_headers |
| 521 | |
| 522 | base_env.AddMethod(GetHeadersNode) |
| 523 | |
| 524 | installer_prereqs_node = base_env.Alias('installer_prereqs', []) |
| 525 | |
| 526 | |
| 527 | def GetInstallerPrereqsNode(env): |
| 528 | return installer_prereqs_node |
| 529 | |
| 530 | base_env.AddMethod(GetInstallerPrereqsNode) |
| 531 | |
| 532 | installer_test_node = base_env.Alias('installer_test_node', []) |
| 533 | |
| 534 | |
| 535 | def GetInstallerTestNode(env): |
| 536 | return installer_test_node |
| 537 | |
| 538 | base_env.AddMethod(GetInstallerTestNode) |
| 539 | |
| 540 | |
| 541 | def AddHeaderToSdk(env, nodes, subdir = 'nacl/', base_dirs = None): |
| 542 | """Add a header file to the toolchain. By default, Native Client-specific |
| 543 | headers go under nacl/, but there are non-specific headers, such as |
| 544 | the OpenGLES2 headers, that go under their own subdir. |
| 545 | |
| 546 | Args: |
| 547 | env: Environment in which we were called. |
| 548 | nodes: A list of node objects to add to the toolchain |
| 549 | subdir: This is appended to each base_dir |
| 550 | base_dirs: A list of directories to install the node to""" |
| 551 | if not base_dirs: |
| 552 | # TODO(mball): This won't work for PNaCl: |
| 553 | base_dirs = [os.path.join(dir, 'x86_64-nacl', 'include') |
| 554 | for dir in env['NACL_TOOLCHAIN_ROOTS'].values()] |
| 555 | |
| 556 | for base_dir in base_dirs: |
| 557 | node = env.Replicate(os.path.join(base_dir, subdir), nodes) |
| 558 | env.Depends(sdk_headers, node) |
| 559 | env.Depends(toolchain_node, node) |
| 560 | return node |
| 561 | |
| 562 | base_env.AddMethod(AddHeaderToSdk) |
| 563 | |
| 564 | |
| 565 | def AddLibraryToSdkHelper(env, nodes, is_lib, is_platform): |
| 566 | """"Helper function to install libs/objs into the toolchain |
| 567 | and associate the action with the extra_sdk_update. |
| 568 | |
| 569 | Args: |
| 570 | env: Environment in which we were called. |
| 571 | nodes: list of libc/objs |
| 572 | is_lib: treat nodes as libs |
| 573 | is_platform: nodes are truly platform specific |
| 574 | """ |
| 575 | env.Requires(nodes, sdk_headers) |
| 576 | |
| 577 | dir = ARGUMENTS.get('extra_sdk_lib_destination') |
| 578 | if not dir: |
| 579 | dir = '${NACL_SDK_LIB}/' |
| 580 | |
| 581 | if is_lib: |
| 582 | n = env.ReplicatePublished(dir, nodes, 'link') |
| 583 | else: |
| 584 | n = env.Replicate(dir, nodes) |
| 585 | |
| 586 | if is_platform: |
| 587 | env.Alias('extra_sdk_libs_platform', n) |
| 588 | else: |
| 589 | env.Alias('extra_sdk_libs', n) |
| 590 | return n |
| 591 | |
| 592 | |
| 593 | def AddLibraryToSdk(env, nodes, is_platform=False): |
| 594 | return AddLibraryToSdkHelper(env, nodes, True, is_platform) |
| 595 | |
| 596 | base_env.AddMethod(AddLibraryToSdk) |
| 597 | |
| 598 | |
| 599 | # ---------------------------------------------------------------------------- |
| 600 | # This is a simple environment that is primarily for targets that aren't built |
| 601 | # directly by scons, and therefore don't need any special environment setup. |
| 602 | build_env = base_env.Clone( |
| 603 | BUILD_TYPE = 'build', |
| 604 | BUILD_GROUPS = ['default', 'all'], |
| 605 | BUILD_TYPE_DESCRIPTION = 'Default build environment', |
| 606 | HOST_PLATFORMS = '*', |
| 607 | ) |
| 608 | |
| 609 | environment_list.append(build_env) |
| 610 | |
| 611 | # ---------------------------------------------------------------------------- |
| 612 | # Get the appropriate build command depending on the environment. |
| 613 | |
| 614 | |
| 615 | def SconsBuildCommand(env): |
| 616 | '''Return the build command used to run separate scons instances. |
| 617 | Args: |
| 618 | env: The construction Environment() that is building using scons. |
| 619 | Returns: |
| 620 | A string representing the platform-specific build command that will run the |
| 621 | scons instances. |
| 622 | ''' |
| 623 | if env['IS_WINDOWS']: |
| 624 | return 'scons.bat --jobs=%s' % GetOption('num_jobs') |
| 625 | else: |
| 626 | return './scons --jobs=%s' % GetOption('num_jobs') |
| 627 | |
| 628 | # ---------------------------------------------------------------------------- |
| 629 | # Add a builder for examples. This adds an Alias() node named 'examples' that |
| 630 | # is always built. There is some special handling for the clean mode, since |
| 631 | # SCons relies on actual build products for its clean processing and will not |
| 632 | # run Alias() actions during clean unless they actually produce something. |
| 633 | |
| 634 | |
| 635 | def BuildExamples(env, target, source): |
| 636 | '''Build the examples. |
| 637 | |
| 638 | This runs the build command in the 'examples' directory. |
| 639 | |
| 640 | Args: |
| 641 | env: The construction Environment() that is building the examples. |
| 642 | target: The target that triggered this build. Not used. |
| 643 | source: The sources used for this build. Not used. |
| 644 | ''' |
| 645 | os_env = os.environ.copy() |
| 646 | os_env['NACL_TARGET_PLATFORM'] = '.' |
| 647 | subprocess.check_call(SconsBuildCommand(env), |
| 648 | cwd='examples', |
| 649 | env=os_env, |
| 650 | shell=True) |
| 651 | |
| 652 | |
| 653 | def CleanExamples(env, target, source): |
| 654 | '''Clean the examples. |
| 655 | |
| 656 | This runs the clean command in the 'examples' directory. |
| 657 | |
| 658 | Args: |
| 659 | env: The construction Environment() that is building the examples. |
| 660 | ''' |
| 661 | os_env = os.environ.copy() |
| 662 | os_env['NACL_TARGET_PLATFORM'] = '.' |
| 663 | subprocess.check_call('%s --clean' % SconsBuildCommand(env), |
| 664 | cwd='examples', |
| 665 | env=os_env, |
| 666 | shell=True) |
| 667 | |
| 668 | |
| 669 | examples_builder = build_env.Alias('examples', [toolchain_node], BuildExamples) |
| 670 | build_env.AlwaysBuild(examples_builder) |
| 671 | build_env.AddCleanAction(['examples'], CleanExamples, ['bot'], |
| 672 | examples_builder) |
| 673 | |
| 674 | |
| 675 | def DependsOnExamples(env, dependency): |
| 676 | env.Depends(dependency, examples_builder) |
| 677 | |
| 678 | build_env.AddMethod(DependsOnExamples) |
| 679 | |
| 680 | |
| 681 | # ---------------------------------------------------------------------------- |
[email protected] | 07ffef1a | 2011-11-21 01:09:05 | [diff] [blame] | 682 | # Add helper functions that build/clean a test by invoking scons under the |
| 683 | # test directory (|cwd|, when specified). These functions are meant to be |
| 684 | # called from corresponding project-specific 'Build<project>Test' and |
| 685 | # 'Clean<project>Test' functions in the local test.scons scripts. Note that the |
| 686 | # CleanNaClTest does not require a |cwd| because its cwd is always '.' |
| 687 | |
| 688 | |
| 689 | def BuildNaClTest(env, cwd): |
| 690 | '''Build the test. |
| 691 | |
| 692 | This runs the build command in the test directory from which it is called. |
| 693 | |
| 694 | Args: |
| 695 | env: The construction Environment() that is building the test. |
| 696 | cwd: The directory under which the test's build.scons rests. |
| 697 | ''' |
| 698 | subprocess.check_call('%s stage' % SconsBuildCommand(env), |
| 699 | cwd=cwd, |
| 700 | shell=True) |
| 701 | |
| 702 | build_env.AddMethod(BuildNaClTest) |
| 703 | |
| 704 | |
| 705 | def CleanNaClTest(env): |
| 706 | '''Clean the test. |
| 707 | |
| 708 | This runs the clean command in the test directory from which it is called. |
| 709 | |
| 710 | Args: |
| 711 | env: The construction Environment() that is building the test. |
| 712 | cwd: The directory under which the test's build.scons rests. |
| 713 | ''' |
| 714 | subprocess.check_call('%s stage --clean' % SconsBuildCommand(env), |
| 715 | shell=True) |
| 716 | # The step above still leaves behind two empty 'opt' directories, so a second |
| 717 | # cleaning pass is necessary. |
| 718 | subprocess.check_call('%s --clean' % SconsBuildCommand(env), |
| 719 | shell=True) |
| 720 | |
| 721 | build_env.AddMethod(CleanNaClTest) |
| 722 | |
| 723 | # ---------------------------------------------------------------------------- |
| 724 | # Enable PPAPIBrowserTester() functionality using nacltest.js |
| 725 | # NOTE: The three main functions in this section: PPAPIBrowserTester(), |
| 726 | # CommandTest(), and AutoDepsCommand() are 'LITE' versions of their counterparts |
| 727 | # provided by Native Client @ third_party/native_client/native_client/SConstruct |
| 728 | |
| 729 | |
| 730 | def SetupBrowserEnv(env): |
| 731 | '''Set up the environment for running the browser. |
| 732 | |
| 733 | This copies environment parameters provided by the OS in order to run the |
| 734 | browser reliably. |
| 735 | |
| 736 | Args: |
| 737 | env: The construction Environment() that runs the browser. |
| 738 | ''' |
| 739 | EXTRA_ENV = ['XAUTHORITY', 'HOME', 'DISPLAY', 'SSH_TTY', 'KRB5CCNAME'] |
| 740 | for var_name in EXTRA_ENV: |
| 741 | if var_name in os.environ: |
| 742 | env['ENV'][var_name] = os.environ[var_name] |
| 743 | |
| 744 | env.Append( |
| 745 | PYTHONPATH = [ |
| 746 | build_utils.JoinPathToNaClRepo( |
| 747 | 'third_party', 'pylib', |
| 748 | root_dir=os.getenv('NACL_SDK_ROOT')), |
| 749 | build_utils.JoinPathToNaClRepo( |
| 750 | 'tools', 'valgrind', |
| 751 | root_dir=os.getenv('NACL_SDK_ROOT')), |
| 752 | ] |
| 753 | ) |
| 754 | |
| 755 | |
| 756 | def PPAPIBrowserTester(env, |
| 757 | target, |
| 758 | url, |
| 759 | files, |
| 760 | timeout=20): |
| 761 | '''The main test wrapper for browser integration tests. |
| 762 | |
| 763 | This constructs the command that invokes the browser_tester.py script on an |
| 764 | existing Chrome binary (to be downloaded if necessary). |
| 765 | |
| 766 | Args: |
| 767 | env: The construction Environment() that runs the browser. |
| 768 | target: The output file to which the output of the test is to be written. |
| 769 | url: The test web page. |
| 770 | files: The files necessary for the web page to be served. |
| 771 | timeout: How long to wait for a response before concluding failure. |
| 772 | |
| 773 | Returns: A command node that executes the browser test. |
| 774 | ''' |
| 775 | |
| 776 | env = env.Clone() |
| 777 | SetupBrowserEnv(env) |
| 778 | |
| 779 | python_tester_script = build_utils.JoinPathToNaClRepo( |
| 780 | 'native_client', 'tools', 'browser_tester', 'browser_tester.py', |
| 781 | root_dir=env['ROOT_DIR']) |
| 782 | |
| 783 | # Check if browser GUI needs to be suppressed (possible only in Linux) |
| 784 | headless_prefix = [] |
| 785 | if not env['SHOW_BROWSER'] and env['IS_LINUX']: |
| 786 | headless_prefix = ['xvfb-run', '--auto-servernum'] |
| 787 | |
| 788 | command = headless_prefix + [ |
| 789 | '${PYTHON}', python_tester_script, |
| 790 | '--browser_path', env.GetChromeBinary(), |
| 791 | '--url', url, |
| 792 | # Fail if there is no response for X seconds. |
| 793 | '--timeout', str(timeout)] |
| 794 | |
| 795 | for dep_file in files: |
| 796 | command.extend(['--file', dep_file]) |
| 797 | |
| 798 | cmd = env.CommandTest(target, |
| 799 | command, |
| 800 | # Set to 'huge' so that the browser tester's timeout |
| 801 | # takes precedence over the default of the test suite. |
| 802 | size='huge', |
| 803 | capture_output=False) |
| 804 | env.DependsOnChrome(cmd) |
| 805 | |
| 806 | return cmd |
| 807 | |
| 808 | build_env.AddMethod(PPAPIBrowserTester) |
| 809 | |
| 810 | |
| 811 | def CommandTest(env, |
| 812 | name, |
| 813 | command, |
| 814 | size='small', |
| 815 | capture_output=True): |
| 816 | '''The wrapper for testing execution of a command and logging details. |
| 817 | |
| 818 | This constructs the command that invokes the command_tester.py script on a |
| 819 | given command. |
| 820 | |
| 821 | Args: |
| 822 | env: The construction Environment() that runs the command. |
| 823 | name: The output file to which the output of the tester is to be written. |
| 824 | command: The command to be tested. |
| 825 | size: This dictates certain timeout thresholds. |
| 826 | capture_output: This specifies whether the command's output needs to be |
| 827 | captured for further processing. When this option is False, |
| 828 | stdout and stderr will be streamed out. For more info, see |
| 829 | <NACL_REPO>/native_client/tools/command_tester.py |
| 830 | |
| 831 | Returns: A command node that executes the command test. |
| 832 | ''' |
| 833 | TEST_TIME_THRESHOLD = { |
| 834 | 'small': 2, |
| 835 | 'medium': 10, |
| 836 | 'large': 60, |
| 837 | 'huge': 1800, |
| 838 | } |
| 839 | |
| 840 | if not name.endswith('out') or name.startswith('$'): |
| 841 | raise Exception('ERROR: bad test filename for test output %r' % name) |
| 842 | |
| 843 | arch_string = env.ChromeArchitectureSpec(); |
| 844 | if env['IS_LINUX']: |
| 845 | platform_string = 'linux' |
| 846 | elif env['IS_MAC']: |
| 847 | platform_string = 'mac' |
| 848 | elif env['IS_WINDOWS']: |
| 849 | platform_string = 'windows' |
| 850 | |
| 851 | name = '${TARGET_ROOT}/test_results/' + name |
| 852 | max_time = TEST_TIME_THRESHOLD[size] |
| 853 | |
| 854 | script_flags = ['--name', name, |
| 855 | '--report', name, |
| 856 | '--time_warning', str(max_time), |
| 857 | '--time_error', str(10 * max_time), |
| 858 | '--perf_env_description', platform_string + '_' + arch_string, |
| 859 | '--arch', 'x86', |
| 860 | '--subarch', arch_string[-2:], |
| 861 | ] |
| 862 | if not capture_output: |
| 863 | script_flags.extend(['--capture_output', '0']) |
| 864 | |
| 865 | test_script = build_utils.JoinPathToNaClRepo( |
| 866 | 'native_client', 'tools', 'command_tester.py', |
| 867 | root_dir=env['ROOT_DIR']) |
| 868 | command = ['${PYTHON}', test_script] + script_flags + command |
| 869 | return AutoDepsCommand(env, name, command) |
| 870 | |
| 871 | build_env.AddMethod(CommandTest) |
| 872 | |
| 873 | |
| 874 | def AutoDepsCommand(env, name, command): |
| 875 | """AutoDepsCommand() takes a command as an array of arguments. Each |
| 876 | argument may either be: |
| 877 | |
| 878 | * a string, or |
| 879 | * a Scons file object, e.g. one created with env.File() or as the |
| 880 | result of another build target. |
| 881 | |
| 882 | In the second case, the file is automatically declared as a |
| 883 | dependency of this command. |
| 884 | |
| 885 | Args: |
| 886 | env: The construction Environment() that runs the command. |
| 887 | name: The target file to which the output is to be written. |
| 888 | command: The command to be executed. |
| 889 | |
| 890 | Returns: A command node in the standard SCons format. |
| 891 | """ |
| 892 | deps = [] |
| 893 | for index, arg in enumerate(command): |
| 894 | if not isinstance(arg, str): |
| 895 | if len(Flatten(arg)) != 1: |
| 896 | # Do not allow this, because it would cause "deps" to get out |
| 897 | # of sync with the indexes in "command". |
| 898 | # See http://code.google.com/p/nativeclient/issues/detail?id=1086 |
| 899 | raise AssertionError('Argument to AutoDepsCommand() actually contains ' |
| 900 | 'multiple (or zero) arguments: %r' % arg) |
| 901 | command[index] = '${SOURCES[%d].abspath}' % len(deps) |
| 902 | deps.append(arg) |
| 903 | |
| 904 | return env.Command(name, deps, ' '.join(command)) |
| 905 | |
| 906 | build_env.AddMethod(AutoDepsCommand) |
| 907 | |
| 908 | |
| 909 | def BuildVSSolution(env, target_name, solution, project_config=None): |
| 910 | """BuildVSSolution() Builds a Visual Studio solution. |
| 911 | |
| 912 | Args: |
| 913 | env: The construction Environment() that runs the command. |
| 914 | target_name: The name of the target. Build output will be written to |
| 915 | [target_name]_build_output.txt. |
| 916 | solution: The solution to build. |
| 917 | project_config: A valid project configuration string to pass into devenv. |
| 918 | This provides support for building specific configurations, i.e. |
| 919 | 'Debug|Win32', 'Debug|x64', 'Release|Win32', 'Release|x64'. Note that the |
| 920 | string must be in quotes to work. devenv will default to Win32 if this |
| 921 | is not provided. |
| 922 | |
| 923 | Returns the Command() used to build the target, so other targets can be made |
| 924 | to depend on it. |
| 925 | """ |
| 926 | vs_build_action = ['vcvarsall', '&&', 'devenv', '${SOURCE}', '/build'] |
| 927 | if project_config: |
| 928 | vs_build_action.extend([project_config]) |
| 929 | |
| 930 | build_command = env.Command(target='%s_build_output.txt' % target_name, |
| 931 | source=solution, |
| 932 | action=' '.join(vs_build_action)) |
| 933 | env.AddNodeAliases(build_command, ['bot'], target_name) |
| 934 | return build_command |
| 935 | |
| 936 | build_env.AddMethod(BuildVSSolution) |
| 937 | |
| 938 | |
| 939 | def CleanVSSolution(env, target_name, solution_dir): |
| 940 | """CleanVSSolution() Cleans up a Visual Studio solution's build results. |
| 941 | |
| 942 | The clean target created by this function is added to the 'bot' target as |
| 943 | well as the target specified. The function will clean any build artifacts |
| 944 | that could possibly be generated under the solution directory. |
| 945 | |
| 946 | Args: |
| 947 | env: The construction Environment() that runs the command. |
| 948 | target_name: The name of the target which builds whatever should be cleaned |
| 949 | up. |
| 950 | solution_dir: The directory under which VS build artifacts are to be |
| 951 | expected. This function will look for Debug, Release, and x64 build |
| 952 | targets. |
| 953 | """ |
| 954 | clean_targets = [os.path.join(solution_dir, 'Debug'), |
| 955 | os.path.join(solution_dir, 'Release'), |
| 956 | os.path.join(solution_dir, 'x64')] |
| 957 | |
| 958 | for target in clean_targets: |
| 959 | clean_action = ['rmdir', '/Q', '/S', target] |
| 960 | env.AddCleanAction([target], |
| 961 | Action(' '.join(clean_action)), |
| 962 | ['bot'], |
| 963 | target_name) |
| 964 | |
| 965 | build_env.AddMethod(CleanVSSolution) |
| 966 | |
| 967 | |
| 968 | def TestVSSolution(env, target_name, test_container, type, size, build_cmd): |
| 969 | """Defines a set of tests to be added to the scons test set. |
| 970 | |
| 971 | This function adds a test solution generated by Visual Studio. It can either |
| 972 | run mstest or, for natively compiled solutions, it can run an executable. |
| 973 | |
| 974 | Args: |
| 975 | env: The construction Environment() that runs the command. |
| 976 | target_name: The name of the target which resulted in the test container. |
| 977 | A name that clearly marks the target as a test is recommended here. |
| 978 | test_container: The fully qualified path to the dll or exe that contains |
| 979 | the tests. |
| 980 | type: The type test package to expect as a string. This can be 'dll' or |
| 981 | 'exe'. |
| 982 | size: Which test harness to add the tests to; small, medium, or large |
| 983 | build_cmd: The command which builds the target being tested. |
| 984 | """ |
| 985 | test_action = test_container |
| 986 | if type is 'dll': |
| 987 | test_action = ' '.join(['vcvarsall', |
| 988 | '&&', |
| 989 | 'mstest', |
| 990 | '/testcontainer:%s' % test_container, |
| 991 | '/resultsfile:${TARGET}']) |
| 992 | |
| 993 | # Can't use the test container as SOURCE because it is generated indirectly |
| 994 | # and using it as source would declare an explicit dependency on a target, |
| 995 | # generated by scons. Files that exist in the environment can be used in that |
| 996 | # way, but only if they exist at the time when scons starts to run, or if |
| 997 | # the are explicit Command targets. As a result, source is empty. |
| 998 | test_command = env.Command( |
| 999 | target='%s_test_results.trx' % target_name, |
| 1000 | source='', |
| 1001 | action=test_action) |
| 1002 | |
| 1003 | env.Depends(test_command, build_cmd) |
| 1004 | env.AddNodeToTestSuite(test_command, |
| 1005 | ['bot'], |
| 1006 | target_name, |
| 1007 | size) |
| 1008 | return test_command |
| 1009 | |
| 1010 | build_env.AddMethod(TestVSSolution) |
| 1011 | |
| 1012 | |
| 1013 | # ---------------------------------------------------------------------------- |
| 1014 | BuildComponents(environment_list) |
| 1015 | |
| 1016 | # Require specifying an explicit target only when not cleaning |
| 1017 | if not GetOption('clean'): |
| 1018 | Default(None) |