diff options
Diffstat (limited to 'native_client_sdk/src/main.scons')
-rw-r--r-- | native_client_sdk/src/main.scons | 1065 |
1 files changed, 1065 insertions, 0 deletions
diff --git a/native_client_sdk/src/main.scons b/native_client_sdk/src/main.scons new file mode 100644 index 0000000..557333d --- /dev/null +++ b/native_client_sdk/src/main.scons @@ -0,0 +1,1065 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Main scons script for Native Client SDK builds. + +Do not invoke this script directly, but instead use the scons or scons.bat +wrapper function. E.g. + +Linux or Mac: + ./scons [Options...] + +Windows: + scons.bat [Options...] +""" + +from __future__ import with_statement + +import os +import platform +import subprocess +import sys +import toolchainbinaries +from build_tools import build_utils + +# ---------------------------------------------------------------------------- +HELP_STRING = """ +=============================================================================== +Help for NaCl SDK +=============================================================================== + +* cleaning: ./scons -c +* build a target: ./scons <target> + +Supported targets: + * bot Runs everything that the build and try bots run. + * debug_server_x64 Build the out-of-process debug_server. + * debug_server_Win32 Build the out-of-process debug_server. + * docs Build all of the Doxygen documentation. + * examples Build the examples. + * experimental Build the experimental projects. + * installer Build the SDK installer. + * nacl-bpad Build the native client crash reporting tool. + * sdk_tools Build nacl_sdk.zip and sdk_tools.tgz + * toolchain Update the toolchain's headers and libraries. + * vsx Build the Visual Studio Plugin. + +Flags: + * USE_EXISTING_INSTALLER=1 Do not rebuild the installer if it exists. + * chrome_browser_path=<full_path> Download Chrome to <full_path>. + * SHOW_BROWSER=1 Don't suppress browser GUI while performing + browser tests in Linux. + +More targets are listed below in the automatically generated help section. + +=============================================================================== +Automatically generated help follows: +=============================================================================== +""" + +# ---------------------------------------------------------------------------- +# Perform some environment checks before running. +# Note that scons should set NACL_SDK_ROOT before this script runs. + +if os.getenv('NACL_SDK_ROOT') is None: + sys.stderr.write('NACL_SDK_ROOT must be defined as the root directory' + ' of NaCl SDK.\n') + sys.exit(1) + +# By default, run with a parallel build (i.e. '-j num_jobs'). +# Use a default value proportional to the number of cpu cores on the system. +# To run a serial build, explicitly type '-j 1' on the command line. +try: + import multiprocessing + CORE_COUNT = multiprocessing.cpu_count() +except (ImportError, NotImplementedError): + CORE_COUNT = 2 # Our buildbots seem to be dual-core typically + +SetOption('num_jobs', CORE_COUNT * 2) +print 'Building with', GetOption('num_jobs'), 'parallel jobs' + +# ---------------------------------------------------------------------------- +# The environment_list contains all the build environments that we want to +# specify. Selecting a particular environment is done using the --mode option. +# Each environment that we support gets appended to this list. +environment_list = [] + +# ---------------------------------------------------------------------------- +# Create the base environment, from which all other environments are derived. +base_env = Environment( + tools = ['component_setup'], + CPPPATH = ['$MAIN_DIR'], + CPPDEFINES = [ + 'BOOST_ALL_NO_LIB', + ], + NACL_TOOLCHAIN_ROOTS = { + ('x86', 'newlib'): + build_utils.NormalizeToolchain(arch='x86', variant='newlib'), + ('x86', 'glibc'): + build_utils.NormalizeToolchain(arch='x86', variant='glibc'), + }, + ROOT_DIR = os.path.abspath(os.getcwd()), + IS_WINDOWS = sys.platform in ['cygwin', 'win32'], + IS_LINUX = sys.platform == 'linux2', + IS_MAC = sys.platform == 'darwin', + JOB_COUNT = GetOption('num_jobs') +) + +# It is possible to override these values on the command line by typing +# something like this: +# PYTHON=/path/to/my/python +base_env.SetDefault( + PYTHON = ARGUMENTS.get('PYTHON', 'python'), + USE_EXISTING_INSTALLER = ARGUMENTS.get('USE_EXISTING_INSTALLER', False), + SHOW_BROWSER = ARGUMENTS.get('SHOW_BROWSER', False), +) + +base_env.Append( + BUILD_SCONSCRIPTS = [ + # Keep in alphabetical order + 'build_tools/build.scons', + 'debugger/build.scons', + 'documentation/build.scons', + 'experimental/visual_studio_plugin/build.scons', + 'experimental/webgtt/tests/nacltest/test.scons', + 'project_templates/test.scons', + ], +) + +base_env.Help(HELP_STRING) + +KNOWN_SUITES = frozenset([ + 'bot', + ]) + + +def HasBotTarget(env): + if 'bot' in COMMAND_LINE_TARGETS: + return True + return False + +base_env.AddMethod(HasBotTarget) + + +def CheckSuiteName(suite, node_name): + '''Check whether a given test suite or alias name is a known name. + + If the suite name is not in the approved list, then this function throws + an exception, with the node_name within the error message. + + Args: + suite: a name of a suite that must be in the KNOWN_SUITES set + node_name: The name of the node. This is used for error messages + ''' + if suite not in KNOWN_SUITES: + raise Exception('Testsuite/Alias "%s" for target "%s" is unknown' % + (suite, node_name)) + + +def AddNodeToTestSuite(env, node, suite_names, node_name, test_size='all'): + '''Adds a test node to a given set of suite names + + These tests are automatically added to the run_all_tests target and are + listed in the help screen. + + This function is loosely based on a function of the same name in the + Native Client repository + + Args: + env - The environment from which this function was called + node - A scons node (e.g., file, command, etc) to be added to set suite + suite_names - A list of test suite names. For none, pass an empty list + node_name - The target name used for running this test + test_size - The relative run-time of this test: small, medium, or large + ''' + + # CommandTest can return an empty list when it silently discards a test + if not node: + return + + AlwaysBuild(node) + + for s in suite_names: + CheckSuiteName(s, node_name) + env.Alias(s, node) + + if test_size not in ['small', 'medium', 'large', 'all']: + raise Exception('Invalid test size for %s' % node_name) + + # Note that COMPONENT_TEST_SIZE is set to 'large' by default, which + # populates a largely redundant list of 'large' tests. Note that all + # tests are added to 'all', so setting test_size='all' is a no-op + env.ComponentTestOutput(node_name, node, COMPONENT_TEST_SIZE=test_size) + +base_env.AddMethod(AddNodeToTestSuite) + + +def ShouldBeCleaned(env, targets, suite_names, node_name): + '''Determines whether a given set of targets require cleaning. + + Args: + env - The calling environment. + targets - Any build artifacts to which a cleaning step might apply. + Any false object indicates that this check is skipped. + suite_names - Any suites that might produce |targets| + node_name - A node that might produce |targets| + ''' + if not env.GetOption('clean'): + return False + + if len(COMMAND_LINE_TARGETS) > 0: + clean_this = False + for cl_target in COMMAND_LINE_TARGETS: + if cl_target in suite_names or cl_target == node_name: + clean_this = True + break + if not clean_this: + return False + + if not targets: + return True + for target in targets: + if os.path.exists(target): + return True + return False + + +def AddCleanAction(env, targets, action, suite_names, node_name): + '''Adds a cleanup action that scons cannot detect automatically. + + Cleaning will only occur if there is a match between the suite or nodes + specified on the command line, and suite_names or node_name or if no + suite or nodes are specified on the command line. Also, at least one of the + targets must exist on the file system. + + Args: + env - The calling environment + targets - Artifacts to be cleaned. + action - The action to be performed. It is up to the caller to ensure + that |action| will actually remove |targets| + suite_names - Any suites to which this cleanup target applies. + node_name - Any nodes to which this cleanup target applies. + ''' + if ShouldBeCleaned(env, targets, suite_names, node_name): + env.Execute(action) + +base_env.AddMethod(AddCleanAction) + + +def AddNodeAliases(env, node, suite_names, node_name): + '''Allow a given node to be built under a different name or as a suite + + Args: + env - The calling environment + node - A target node to add to a known build alias (e.g., 'bot') + suite_names - A list of suite names. For none, pass an empty list. This + node will be run whenever any of these suites are invoked. + Each suite name must match a string in KNOWN_SUITES. + node_name - The name of this node, when run by itself + ''' + + if not node: + return + + for s in suite_names: + CheckSuiteName(s, node_name) + env.Alias(s, node) + + env.Alias(node_name, node) + +base_env.AddMethod(AddNodeAliases) + + +def CreatePythonUnitTest(env, filename, dependencies=None, disabled=False, + params=None, buffered=True, banner=None): + """Returns a new build command that will run a unit test with a given file. + + Args: + env: SCons environment + filename: The python file that contains the unit test + dependencies: An optional list of other files that this unit test uses + disabled: Setting this to True will prevent the test from running + params: Optional additional parameters for python command + buffered: True=stdout is buffered until entirely complete; + False=stdout is immediately displayed as it occurs. + banner: (optional) annotation banner for build/try bots + + Returns: + A SCons command node + """ + dependencies = dependencies or [] + params = params or [] + + basename = os.path.splitext(os.path.basename(filename))[0] + outfilename = "%s_output.txt" % basename + + + def RunPythonUnitTest(env, target, source): + """Runs unit tests using the given target as a command. + + The argument names of this test are not very intuitive but match what is + used conventionally throughout scons. If the string "PASSED" does not + occur in target when this exits, the test has failed; also a scons + convention. + + Args: + env: SCons's current environment. + target: Where to write the result of the test. + source: The command to run as the test. + + Returns: + None for good status + An error string for bad status + """ + bot = build_utils.BotAnnotator() + if banner: + bot.BuildStep(banner) + + if disabled: + sys.stdout.write("Test %s is disabled.\n" % basename) + sys.stdout.flush() + return None # return with good status + + import subprocess + + app = [str(env['PYTHON']), str(source[0].abspath)] + map( + lambda param: param if type(param) is str else str(param.abspath), + params) + bot.Print('Running: %s' % app) + app_env = os.environ.copy() + # We have to do this because scons overrides PYTHONPATH and does + # not preserve what is provided by the OS. + python_path = [env['ROOT_DIR'], app_env['PYMOX'], app_env['PYTHONPATH']] + app_env['PYTHONPATH'] = os.pathsep.join(python_path) + ret_val = 'Error: General Test Failure' # Indicates failure, by default + target_str = str(target[0]) + with open(target_str, 'w') as outfile: + def Write(str): + if buffered: + outfile.write(str) + outfile.flush() + else: + sys.stdout.write(str) + sys.stdout.flush() + Write('\n-----Begin output for Test: %s\n' % basename) + if subprocess.call(app, env=app_env, + stdout=outfile if buffered else None, + stderr=outfile if buffered else None): + Write('-----Error: unit test failed\n') + ret_val = 'Error: Test Failure in %s' % basename + else: + ret_val = None # Indicates success + + Write('-----End output for Test: %s\n' % basename) + if buffered: + with open(target_str, 'r') as resultfile: + sys.stdout.write(resultfile.read()) + sys.stdout.flush() + + if ret_val: + bot.BuildStepFailure() + + return ret_val + + cmd = env.Command(outfilename, filename, RunPythonUnitTest) + env.Depends(cmd, dependencies) + # Create dependencies for all the env.File parameters and other scons nodes + for param in params: + if type(param) is not str: + env.Depends(cmd, param) + + return cmd + +base_env.AddMethod(CreatePythonUnitTest) + + +# ---------------------------------------------------------------------------- +# Support for running Chrome. These functions access the construction +# Environment() to produce a path to Chrome. + +# A Dir object representing the directory where the Chrome binaries are kept. +# You can use chrome_binaries_dir= to set this on the command line. Defaults +# to chrome_binaries. +base_env['CHROME_DOWNLOAD_DIR'] = \ + base_env.Dir(ARGUMENTS.get('chrome_binaries_dir', '#chrome_binaries')) + + +def ChromeArchitectureSpec(env): + '''Determine the architecture spec for the Chrome binary. + + The architecture spec is a string that represents the host architecture. + Possible values are: + x86-32 + x86-64 + On Mac and Windows, the architecture spec is always x86-32, because there are + no 64-bit version available. + + Returns: An architecture spec string for the host CPU. + ''' + arch, _ = platform.architecture(); + # On Mac and Windows, always use a 32-bit version of Chrome (64-bit versions + # are not available). + if env['IS_WINDOWS'] or env['IS_MAC']: + arch = 'x86-32' + else: + arch = 'x86-64' if '64' in arch else 'x86-32' + return arch + +base_env.AddMethod(ChromeArchitectureSpec) + + +def GetDefaultChromeBinary(env): + '''Get a File object that represents a Chrome binary. + + By default, the test infrastructure will download a copy of Chrome that can + be used for testing. This method returns a File object that represents the + downloaded Chrome binary that can be run by tests. Note that the path to the + binary is specific to the host platform, for example the path on Linux + is <chrome_dir>/linux/<arch>/chrome, while on Mac it's + <chrome_dir>/mac/<arch>/Chromium.app/Contents.MacOS/Chromium. + + Returns: A File object representing the Chrome binary. + ''' + if env['IS_LINUX']: + os_name = 'linux' + binary = 'chrome' + elif env['IS_WINDOWS']: + os_name = 'windows' + binary = 'chrome.exe' + elif env['IS_MAC']: + os_name = 'mac' + binary = 'Chromium.app/Contents/MacOS/Chromium' + else: + raise Exception('Unsupported OS') + + return env.File(os.path.join( + '${CHROME_DOWNLOAD_DIR}', + '%s_%s' % (os_name, env.ChromeArchitectureSpec()), + binary)) + +base_env.AddMethod(GetDefaultChromeBinary) + + +def GetChromeBinary(env): + '''Return a File object that represents the downloaded Chrome binary. + + If chrome_browser_path is specified on the command line, then return a File + object that represents that path. Otherwise, return a File object + representing the default downloaded Chrome (see GetDefaultChromeBinary(), + above). + + Returns: A File object representing a Chrome binary. + ''' + return env.File(ARGUMENTS.get('chrome_browser_path', + env.GetDefaultChromeBinary())) + +base_env.AddMethod(GetChromeBinary) + + +def DependsOnChrome(env, dependency): + '''Create a dependency on the download of Chrome. + + Creates a dependency in |env| such that Chrome gets downloaded (if necessary) + whenever |dependency| changes. Uses the Chrome downloader scripts built + into NaCl; this script expects NaCl to be DEPS'ed into + third_party/native_client/native_client. + + The Chrome binary is added as a precious node to the base Environment. If + we added it to the build environment env, then downloading chrome would in + effect be specified for multiple environments. + ''' + if not hasattr(base_env, 'download_chrome_node'): + chrome_binary = env.GetDefaultChromeBinary() + download_chrome_script = build_utils.JoinPathToNaClRepo( + 'native_client', 'build', 'download_chrome.py', + root_dir=env['ROOT_DIR']) + base_env.download_chrome_node = env.Command( + chrome_binary, + [], + '${PYTHON} %s --arch=%s --dst=${CHROME_DOWNLOAD_DIR}' % + (download_chrome_script, env.ChromeArchitectureSpec())) + # This stops Scons from deleting the file before running the step above. + env.NoClean(chrome_binary) + env.Precious(chrome_binary) + env.Depends(dependency, base_env.download_chrome_node) + +base_env.AddMethod(DependsOnChrome) + + +# ---------------------------------------------------------------------------- +# Targets for updating sdk headers and libraries +# NACL_SDK_XXX vars are defined by site_scons/site_tools/naclsdk.py +# NOTE: Our task here is complicated by the fact that there might already be +# some (outdated) headers/libraries at the new location +# One of the hacks we employ here is to make every library depend +# on the installation on ALL headers (sdk_headers) + +# Contains all the headers to be installed +sdk_headers = base_env.Alias('extra_sdk_update_header', []) +# Contains all the libraries and .o files to be installed +libs_platform = base_env.Alias('extra_sdk_libs_platform', []) +libs = base_env.Alias('extra_sdk_libs', []) +base_env.Alias('extra_sdk_update', [libs, libs_platform]) + +AlwaysBuild(sdk_headers) + +# ---------------------------------------------------------------------------- +# The following section contains proxy nodes which can be used to create +# dependencies between targets that are not in the same scope or environment. +toolchain_node = base_env.Alias('toolchain', []) + + +def GetToolchainNode(env): + '''Returns the node associated with the toolchain build target''' + return toolchain_node + +base_env.AddMethod(GetToolchainNode) + + +def GetHeadersNode(env): + return sdk_headers + +base_env.AddMethod(GetHeadersNode) + +installer_prereqs_node = base_env.Alias('installer_prereqs', []) + + +def GetInstallerPrereqsNode(env): + return installer_prereqs_node + +base_env.AddMethod(GetInstallerPrereqsNode) + +installer_test_node = base_env.Alias('installer_test_node', []) + + +def GetInstallerTestNode(env): + return installer_test_node + +base_env.AddMethod(GetInstallerTestNode) + + +def AddHeaderToSdk(env, nodes, subdir = 'nacl/', base_dirs = None): + """Add a header file to the toolchain. By default, Native Client-specific + headers go under nacl/, but there are non-specific headers, such as + the OpenGLES2 headers, that go under their own subdir. + + Args: + env: Environment in which we were called. + nodes: A list of node objects to add to the toolchain + subdir: This is appended to each base_dir + base_dirs: A list of directories to install the node to""" + if not base_dirs: + # TODO(mball): This won't work for PNaCl: + base_dirs = [os.path.join(dir, 'x86_64-nacl', 'include') + for dir in env['NACL_TOOLCHAIN_ROOTS'].values()] + + for base_dir in base_dirs: + node = env.Replicate(os.path.join(base_dir, subdir), nodes) + env.Depends(sdk_headers, node) + env.Depends(toolchain_node, node) + return node + +base_env.AddMethod(AddHeaderToSdk) + + +def AddLibraryToSdkHelper(env, nodes, is_lib, is_platform): + """"Helper function to install libs/objs into the toolchain + and associate the action with the extra_sdk_update. + + Args: + env: Environment in which we were called. + nodes: list of libc/objs + is_lib: treat nodes as libs + is_platform: nodes are truly platform specific + """ + env.Requires(nodes, sdk_headers) + + dir = ARGUMENTS.get('extra_sdk_lib_destination') + if not dir: + dir = '${NACL_SDK_LIB}/' + + if is_lib: + n = env.ReplicatePublished(dir, nodes, 'link') + else: + n = env.Replicate(dir, nodes) + + if is_platform: + env.Alias('extra_sdk_libs_platform', n) + else: + env.Alias('extra_sdk_libs', n) + return n + + +def AddLibraryToSdk(env, nodes, is_platform=False): + return AddLibraryToSdkHelper(env, nodes, True, is_platform) + +base_env.AddMethod(AddLibraryToSdk) + + +# ---------------------------------------------------------------------------- +# This is a simple environment that is primarily for targets that aren't built +# directly by scons, and therefore don't need any special environment setup. +build_env = base_env.Clone( + BUILD_TYPE = 'build', + BUILD_GROUPS = ['default', 'all'], + BUILD_TYPE_DESCRIPTION = 'Default build environment', + HOST_PLATFORMS = '*', + ) + +environment_list.append(build_env) + +# ---------------------------------------------------------------------------- +# Get the appropriate build command depending on the environment. + + +def SconsBuildCommand(env): + '''Return the build command used to run separate scons instances. + Args: + env: The construction Environment() that is building using scons. + Returns: + A string representing the platform-specific build command that will run the + scons instances. + ''' + if env['IS_WINDOWS']: + return 'scons.bat --jobs=%s' % GetOption('num_jobs') + else: + return './scons --jobs=%s' % GetOption('num_jobs') + +# ---------------------------------------------------------------------------- +# Add a builder for examples. This adds an Alias() node named 'examples' that +# is always built. There is some special handling for the clean mode, since +# SCons relies on actual build products for its clean processing and will not +# run Alias() actions during clean unless they actually produce something. + + +def BuildExamples(env, target, source): + '''Build the examples. + + This runs the build command in the 'examples' directory. + + Args: + env: The construction Environment() that is building the examples. + target: The target that triggered this build. Not used. + source: The sources used for this build. Not used. + ''' + os_env = os.environ.copy() + os_env['NACL_TARGET_PLATFORM'] = '.' + subprocess.check_call(SconsBuildCommand(env), + cwd='examples', + env=os_env, + shell=True) + + +def CleanExamples(env, target, source): + '''Clean the examples. + + This runs the clean command in the 'examples' directory. + + Args: + env: The construction Environment() that is building the examples. + ''' + os_env = os.environ.copy() + os_env['NACL_TARGET_PLATFORM'] = '.' + subprocess.check_call('%s --clean' % SconsBuildCommand(env), + cwd='examples', + env=os_env, + shell=True) + + +examples_builder = build_env.Alias('examples', [toolchain_node], BuildExamples) +build_env.AlwaysBuild(examples_builder) +build_env.AddCleanAction(['examples'], CleanExamples, ['bot'], + examples_builder) + + +def DependsOnExamples(env, dependency): + env.Depends(dependency, examples_builder) + +build_env.AddMethod(DependsOnExamples) + + +# ---------------------------------------------------------------------------- +# Add a builder for experimental projects. This adds an Alias() node named +# 'experimental' that is always built. There is some special handling for the +# clean mode, since SCons relies on actual build products for its clean +# processing and will not run Alias() actions during clean unless they actually +# produce something. + + +def BuildExperimental(env, target, source): + '''Build the experimental projects. + + This runs the build command in the 'experimental' directory. + + Args: + env: The construction Environment() that is building the experimental + projects. + target: The target that triggered this build. Not used. + source: The sources used for this build. Not used. + ''' + subprocess.check_call(SconsBuildCommand(env), + cwd='experimental', + shell=True) + + +def CleanExperimental(env, target, source): + '''Clean the experimental projects. + + This runs the clean command in the 'experimental' directory. + + Args: + env: The construction Environment() that is building the experimental + projects. + ''' + subprocess.check_call('%s --clean' % SconsBuildCommand(env), + cwd='experimental', + shell=True) + + +experimental_builder = build_env.Alias('experimental', [toolchain_node], + BuildExperimental) +build_env.AlwaysBuild(experimental_builder) +build_env.AddCleanAction(['experimental'], CleanExperimental, ['bot'], + experimental_builder) + +# ---------------------------------------------------------------------------- +# Add helper functions that build/clean a test by invoking scons under the +# test directory (|cwd|, when specified). These functions are meant to be +# called from corresponding project-specific 'Build<project>Test' and +# 'Clean<project>Test' functions in the local test.scons scripts. Note that the +# CleanNaClTest does not require a |cwd| because its cwd is always '.' + + +def BuildNaClTest(env, cwd): + '''Build the test. + + This runs the build command in the test directory from which it is called. + + Args: + env: The construction Environment() that is building the test. + cwd: The directory under which the test's build.scons rests. + ''' + subprocess.check_call('%s stage' % SconsBuildCommand(env), + cwd=cwd, + shell=True) + +build_env.AddMethod(BuildNaClTest) + + +def CleanNaClTest(env): + '''Clean the test. + + This runs the clean command in the test directory from which it is called. + + Args: + env: The construction Environment() that is building the test. + cwd: The directory under which the test's build.scons rests. + ''' + subprocess.check_call('%s stage --clean' % SconsBuildCommand(env), + shell=True) + # The step above still leaves behind two empty 'opt' directories, so a second + # cleaning pass is necessary. + subprocess.check_call('%s --clean' % SconsBuildCommand(env), + shell=True) + +build_env.AddMethod(CleanNaClTest) + +# ---------------------------------------------------------------------------- +# Enable PPAPIBrowserTester() functionality using nacltest.js +# NOTE: The three main functions in this section: PPAPIBrowserTester(), +# CommandTest(), and AutoDepsCommand() are 'LITE' versions of their counterparts +# provided by Native Client @ third_party/native_client/native_client/SConstruct + + +def SetupBrowserEnv(env): + '''Set up the environment for running the browser. + + This copies environment parameters provided by the OS in order to run the + browser reliably. + + Args: + env: The construction Environment() that runs the browser. + ''' + EXTRA_ENV = ['XAUTHORITY', 'HOME', 'DISPLAY', 'SSH_TTY', 'KRB5CCNAME'] + for var_name in EXTRA_ENV: + if var_name in os.environ: + env['ENV'][var_name] = os.environ[var_name] + + env.Append( + PYTHONPATH = [ + build_utils.JoinPathToNaClRepo( + 'third_party', 'pylib', + root_dir=os.getenv('NACL_SDK_ROOT')), + build_utils.JoinPathToNaClRepo( + 'tools', 'valgrind', + root_dir=os.getenv('NACL_SDK_ROOT')), + ] + ) + + +def PPAPIBrowserTester(env, + target, + url, + files, + timeout=20): + '''The main test wrapper for browser integration tests. + + This constructs the command that invokes the browser_tester.py script on an + existing Chrome binary (to be downloaded if necessary). + + Args: + env: The construction Environment() that runs the browser. + target: The output file to which the output of the test is to be written. + url: The test web page. + files: The files necessary for the web page to be served. + timeout: How long to wait for a response before concluding failure. + + Returns: A command node that executes the browser test. + ''' + + env = env.Clone() + SetupBrowserEnv(env) + + python_tester_script = build_utils.JoinPathToNaClRepo( + 'native_client', 'tools', 'browser_tester', 'browser_tester.py', + root_dir=env['ROOT_DIR']) + + # Check if browser GUI needs to be suppressed (possible only in Linux) + headless_prefix = [] + if not env['SHOW_BROWSER'] and env['IS_LINUX']: + headless_prefix = ['xvfb-run', '--auto-servernum'] + + command = headless_prefix + [ + '${PYTHON}', python_tester_script, + '--browser_path', env.GetChromeBinary(), + '--url', url, + # Fail if there is no response for X seconds. + '--timeout', str(timeout)] + + for dep_file in files: + command.extend(['--file', dep_file]) + + cmd = env.CommandTest(target, + command, + # Set to 'huge' so that the browser tester's timeout + # takes precedence over the default of the test suite. + size='huge', + capture_output=False) + env.DependsOnChrome(cmd) + + return cmd + +build_env.AddMethod(PPAPIBrowserTester) + + +def CommandTest(env, + name, + command, + size='small', + capture_output=True): + '''The wrapper for testing execution of a command and logging details. + + This constructs the command that invokes the command_tester.py script on a + given command. + + Args: + env: The construction Environment() that runs the command. + name: The output file to which the output of the tester is to be written. + command: The command to be tested. + size: This dictates certain timeout thresholds. + capture_output: This specifies whether the command's output needs to be + captured for further processing. When this option is False, + stdout and stderr will be streamed out. For more info, see + <NACL_REPO>/native_client/tools/command_tester.py + + Returns: A command node that executes the command test. + ''' + TEST_TIME_THRESHOLD = { + 'small': 2, + 'medium': 10, + 'large': 60, + 'huge': 1800, + } + + if not name.endswith('out') or name.startswith('$'): + raise Exception('ERROR: bad test filename for test output %r' % name) + + arch_string = env.ChromeArchitectureSpec(); + if env['IS_LINUX']: + platform_string = 'linux' + elif env['IS_MAC']: + platform_string = 'mac' + elif env['IS_WINDOWS']: + platform_string = 'windows' + + name = '${TARGET_ROOT}/test_results/' + name + max_time = TEST_TIME_THRESHOLD[size] + + script_flags = ['--name', name, + '--report', name, + '--time_warning', str(max_time), + '--time_error', str(10 * max_time), + '--perf_env_description', platform_string + '_' + arch_string, + '--arch', 'x86', + '--subarch', arch_string[-2:], + ] + if not capture_output: + script_flags.extend(['--capture_output', '0']) + + test_script = build_utils.JoinPathToNaClRepo( + 'native_client', 'tools', 'command_tester.py', + root_dir=env['ROOT_DIR']) + command = ['${PYTHON}', test_script] + script_flags + command + return AutoDepsCommand(env, name, command) + +build_env.AddMethod(CommandTest) + + +def AutoDepsCommand(env, name, command): + """AutoDepsCommand() takes a command as an array of arguments. Each + argument may either be: + + * a string, or + * a Scons file object, e.g. one created with env.File() or as the + result of another build target. + + In the second case, the file is automatically declared as a + dependency of this command. + + Args: + env: The construction Environment() that runs the command. + name: The target file to which the output is to be written. + command: The command to be executed. + + Returns: A command node in the standard SCons format. + """ + deps = [] + for index, arg in enumerate(command): + if not isinstance(arg, str): + if len(Flatten(arg)) != 1: + # Do not allow this, because it would cause "deps" to get out + # of sync with the indexes in "command". + # See http://code.google.com/p/nativeclient/issues/detail?id=1086 + raise AssertionError('Argument to AutoDepsCommand() actually contains ' + 'multiple (or zero) arguments: %r' % arg) + command[index] = '${SOURCES[%d].abspath}' % len(deps) + deps.append(arg) + + return env.Command(name, deps, ' '.join(command)) + +build_env.AddMethod(AutoDepsCommand) + + +def BuildVSSolution(env, target_name, solution, project_config=None): + """BuildVSSolution() Builds a Visual Studio solution. + + Args: + env: The construction Environment() that runs the command. + target_name: The name of the target. Build output will be written to + [target_name]_build_output.txt. + solution: The solution to build. + project_config: A valid project configuration string to pass into devenv. + This provides support for building specific configurations, i.e. + 'Debug|Win32', 'Debug|x64', 'Release|Win32', 'Release|x64'. Note that the + string must be in quotes to work. devenv will default to Win32 if this + is not provided. + + Returns the Command() used to build the target, so other targets can be made + to depend on it. + """ + vs_build_action = ['vcvarsall', '&&', 'devenv', '${SOURCE}', '/build'] + if project_config: + vs_build_action.extend([project_config]) + + build_command = env.Command(target='%s_build_output.txt' % target_name, + source=solution, + action=' '.join(vs_build_action)) + env.AddNodeAliases(build_command, ['bot'], target_name) + return build_command + +build_env.AddMethod(BuildVSSolution) + + +def CleanVSSolution(env, target_name, solution_dir): + """CleanVSSolution() Cleans up a Visual Studio solution's build results. + + The clean target created by this function is added to the 'bot' target as + well as the target specified. The function will clean any build artifacts + that could possibly be generated under the solution directory. + + Args: + env: The construction Environment() that runs the command. + target_name: The name of the target which builds whatever should be cleaned + up. + solution_dir: The directory under which VS build artifacts are to be + expected. This function will look for Debug, Release, and x64 build + targets. + """ + clean_targets = [os.path.join(solution_dir, 'Debug'), + os.path.join(solution_dir, 'Release'), + os.path.join(solution_dir, 'x64')] + + for target in clean_targets: + clean_action = ['rmdir', '/Q', '/S', target] + env.AddCleanAction([target], + Action(' '.join(clean_action)), + ['bot'], + target_name) + +build_env.AddMethod(CleanVSSolution) + + +def TestVSSolution(env, target_name, test_container, type, size, build_cmd): + """Defines a set of tests to be added to the scons test set. + + This function adds a test solution generated by Visual Studio. It can either + run mstest or, for natively compiled solutions, it can run an executable. + + Args: + env: The construction Environment() that runs the command. + target_name: The name of the target which resulted in the test container. + A name that clearly marks the target as a test is recommended here. + test_container: The fully qualified path to the dll or exe that contains + the tests. + type: The type test package to expect as a string. This can be 'dll' or + 'exe'. + size: Which test harness to add the tests to; small, medium, or large + build_cmd: The command which builds the target being tested. + """ + test_action = test_container + if type is 'dll': + test_action = ' '.join(['vcvarsall', + '&&', + 'mstest', + '/testcontainer:%s' % test_container, + '/resultsfile:${TARGET}']) + + # Can't use the test container as SOURCE because it is generated indirectly + # and using it as source would declare an explicit dependency on a target, + # generated by scons. Files that exist in the environment can be used in that + # way, but only if they exist at the time when scons starts to run, or if + # the are explicit Command targets. As a result, source is empty. + test_command = env.Command( + target='%s_test_results.trx' % target_name, + source='', + action=test_action) + + env.Depends(test_command, build_cmd) + env.AddNodeToTestSuite(test_command, + ['bot'], + target_name, + size) + return test_command + +build_env.AddMethod(TestVSSolution) + + +# ---------------------------------------------------------------------------- +BuildComponents(environment_list) + +# Require specifying an explicit target only when not cleaning +if not GetOption('clean'): + Default(None) |