blob: d468d9031f0038a0618bc68ecaab7a283aa2d4be [file] [log] [blame]
[email protected]07ffef1a2011-11-21 01:09:051#! -*- python -*-
2#
[email protected]aeaed3b62012-01-12 18:58:523# Copyright (c) 2011 The Chromium Authors. All rights reserved.
[email protected]07ffef1a2011-11-21 01:09:054# 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
9Do not invoke this script directly, but instead use the scons or scons.bat
10wrapper function. E.g.
11
12Linux or Mac:
13 ./scons [Options...]
14
15Windows:
16 scons.bat [Options...]
17"""
18
19from __future__ import with_statement
20
21import os
22import platform
23import subprocess
24import sys
25import toolchainbinaries
26from build_tools import build_utils
27
28# ----------------------------------------------------------------------------
29HELP_STRING = """
30===============================================================================
31Help for NaCl SDK
32===============================================================================
33
34* cleaning: ./scons -c
35* build a target: ./scons <target>
36
37Supported 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]07ffef1a2011-11-21 01:09:0543 * 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
49Flags:
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
55More targets are listed below in the automatically generated help section.
56
57===============================================================================
58Automatically 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
66if 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.
74try:
75 import multiprocessing
76 CORE_COUNT = multiprocessing.cpu_count()
77except (ImportError, NotImplementedError):
78 CORE_COUNT = 2 # Our buildbots seem to be dual-core typically
79
80SetOption('num_jobs', CORE_COUNT * 2)
81print '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.
87environment_list = []
88
89# ----------------------------------------------------------------------------
90# Create the base environment, from which all other environments are derived.
91base_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]5f819e22011-11-29 01:39:23104 SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.getcwd()))),
[email protected]07ffef1a2011-11-21 01:09:05105 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
114base_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
120base_env.Append(
121 BUILD_SCONSCRIPTS = [
122 # Keep in alphabetical order
123 'build_tools/build.scons',
[email protected]07ffef1a2011-11-21 01:09:05124 'documentation/build.scons',
[email protected]07ffef1a2011-11-21 01:09:05125 'project_templates/test.scons',
126 ],
127)
128
129base_env.Help(HELP_STRING)
130
131KNOWN_SUITES = frozenset([
132 'bot',
133 ])
134
135
136def HasBotTarget(env):
137 if 'bot' in COMMAND_LINE_TARGETS:
138 return True
139 return False
140
141base_env.AddMethod(HasBotTarget)
142
143
144def 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
159def 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
194base_env.AddMethod(AddNodeToTestSuite)
195
196
197def 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
227def 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
246base_env.AddMethod(AddCleanAction)
247
248
249def 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
270base_env.AddMethod(AddNodeAliases)
271
272
273def 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
373base_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.
383base_env['CHROME_DOWNLOAD_DIR'] = \
384 base_env.Dir(ARGUMENTS.get('chrome_binaries_dir', '#chrome_binaries'))
385
386
387def 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
408base_env.AddMethod(ChromeArchitectureSpec)
409
410
411def 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
440base_env.AddMethod(GetDefaultChromeBinary)
441
442
443def 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
456base_env.AddMethod(GetChromeBinary)
457
458
459def 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
486base_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
498sdk_headers = base_env.Alias('extra_sdk_update_header', [])
499# Contains all the libraries and .o files to be installed
500libs_platform = base_env.Alias('extra_sdk_libs_platform', [])
501libs = base_env.Alias('extra_sdk_libs', [])
502base_env.Alias('extra_sdk_update', [libs, libs_platform])
503
504AlwaysBuild(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.
509toolchain_node = base_env.Alias('toolchain', [])
510
511
512def GetToolchainNode(env):
513 '''Returns the node associated with the toolchain build target'''
514 return toolchain_node
515
516base_env.AddMethod(GetToolchainNode)
517
518
519def GetHeadersNode(env):
520 return sdk_headers
521
522base_env.AddMethod(GetHeadersNode)
523
524installer_prereqs_node = base_env.Alias('installer_prereqs', [])
525
526
527def GetInstallerPrereqsNode(env):
528 return installer_prereqs_node
529
530base_env.AddMethod(GetInstallerPrereqsNode)
531
532installer_test_node = base_env.Alias('installer_test_node', [])
533
534
535def GetInstallerTestNode(env):
536 return installer_test_node
537
538base_env.AddMethod(GetInstallerTestNode)
539
540
541def 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
562base_env.AddMethod(AddHeaderToSdk)
563
564
565def 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
593def AddLibraryToSdk(env, nodes, is_platform=False):
594 return AddLibraryToSdkHelper(env, nodes, True, is_platform)
595
596base_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.
602build_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
609environment_list.append(build_env)
610
611# ----------------------------------------------------------------------------
612# Get the appropriate build command depending on the environment.
613
614
615def 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
635def 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
653def 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
669examples_builder = build_env.Alias('examples', [toolchain_node], BuildExamples)
670build_env.AlwaysBuild(examples_builder)
671build_env.AddCleanAction(['examples'], CleanExamples, ['bot'],
672 examples_builder)
673
674
675def DependsOnExamples(env, dependency):
676 env.Depends(dependency, examples_builder)
677
678build_env.AddMethod(DependsOnExamples)
679
680
681# ----------------------------------------------------------------------------
[email protected]07ffef1a2011-11-21 01:09:05682# 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
689def 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
702build_env.AddMethod(BuildNaClTest)
703
704
705def 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
721build_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
730def 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
756def 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
808build_env.AddMethod(PPAPIBrowserTester)
809
810
811def 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
871build_env.AddMethod(CommandTest)
872
873
874def 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
906build_env.AddMethod(AutoDepsCommand)
907
908
909def 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
936build_env.AddMethod(BuildVSSolution)
937
938
939def 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
965build_env.AddMethod(CleanVSSolution)
966
967
968def 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
1010build_env.AddMethod(TestVSSolution)
1011
1012
1013# ----------------------------------------------------------------------------
1014BuildComponents(environment_list)
1015
1016# Require specifying an explicit target only when not cleaning
1017if not GetOption('clean'):
1018 Default(None)