| #! -*- 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) |