Home | History | Annotate | Download | only in native_client
      1 #! -*- python -*-
      2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import json
      7 import os
      8 import shutil
      9 import sys
     10 
     11 sys.path.append(Dir('#/tools').abspath)
     12 import command_tester
     13 import test_lib
     14 
     15 Import(['pre_base_env'])
     16 
     17 # Underlay things migrating to ppapi repo.
     18 Dir('#/..').addRepository(Dir('#/../ppapi'))
     19 
     20 # Append a list of files to another, filtering out the files that already exist.
     21 # Filtering helps migrate declarations between repos by preventing redundant
     22 # declarations from causing an error.
     23 def ExtendFileList(existing, additional):
     24   # Avoid quadratic behavior by using a set.
     25   combined = set()
     26   for file_name in existing + additional:
     27     if file_name in combined:
     28       print 'WARNING: two references to file %s in the build.' % file_name
     29     combined.add(file_name)
     30   return sorted(combined)
     31 
     32 
     33 ppapi_scons_files = {}
     34 ppapi_scons_files['trusted_scons_files'] = []
     35 ppapi_scons_files['untrusted_irt_scons_files'] = []
     36 
     37 ppapi_scons_files['nonvariant_test_scons_files'] = [
     38     'tests/breakpad_crash_test/nacl.scons',
     39     'tests/nacl_browser/browser_dynamic_library/nacl.scons',
     40     'tests/nacl_browser/manifest_file/nacl.scons',
     41     'tests/nacl_browser/nameservice/nacl.scons',
     42     'tests/ppapi_browser/bad/nacl.scons',
     43     'tests/ppapi_browser/extension_mime_handler/nacl.scons',
     44     'tests/ppapi_browser/manifest/nacl.scons',
     45     'tests/ppapi_test_lib/nacl.scons',
     46 ]
     47 
     48 ppapi_scons_files['irt_variant_test_scons_files'] = [
     49     # 'inbrowser_test_runner' must be in the irt_variant list
     50     # otherwise it will run no tests.
     51     'tests/nacl_browser/inbrowser_test_runner/nacl.scons',
     52     # Disabled by Brad Chen 4 Sep to try to green Chromium
     53     # nacl_integration tests
     54     #'tests/nacl_browser/fault_injection/nacl.scons',
     55 ]
     56 
     57 ppapi_scons_files['untrusted_scons_files'] = [
     58     'src/shared/ppapi/nacl.scons',
     59     'src/untrusted/irt_stub/nacl.scons',
     60     'src/untrusted/nacl_ppapi_util/nacl.scons',
     61 ]
     62 
     63 
     64 EXTRA_ENV = [
     65     'XAUTHORITY', 'HOME', 'DISPLAY', 'SSH_TTY', 'KRB5CCNAME',
     66     'CHROME_DEVEL_SANDBOX' ]
     67 
     68 def SetupBrowserEnv(env):
     69   for var_name in EXTRA_ENV:
     70     if var_name in os.environ:
     71       env['ENV'][var_name] = os.environ[var_name]
     72 
     73 pre_base_env.AddMethod(SetupBrowserEnv)
     74 
     75 
     76 def GetHeadlessPrefix(env):
     77   if env.Bit('browser_headless') and env.Bit('host_linux'):
     78     return ['xvfb-run', '--auto-servernum']
     79   else:
     80     # Mac and Windows do not seem to have an equivalent.
     81     return []
     82 
     83 pre_base_env.AddMethod(GetHeadlessPrefix)
     84 
     85 
     86 # A fake file to depend on if a path to Chrome is not specified.
     87 no_browser = pre_base_env.File('chrome_browser_path_not_specified')
     88 
     89 
     90 # SCons attempts to run a test that depends on "no_browser", detect this at
     91 # runtime and cause a build error.
     92 def NoBrowserError(target, source, env):
     93   print target, source, env
     94   print ("***\nYou need to specificy chrome_browser_path=... on the " +
     95          "command line to run these tests.\n***\n")
     96   return 1
     97 
     98 pre_base_env.Append(BUILDERS = {
     99     'NoBrowserError': Builder(action=NoBrowserError)
    100 })
    101 
    102 pre_base_env.NoBrowserError([no_browser], [])
    103 
    104 
    105 def ChromeBinary(env):
    106   if 'chrome_browser_path' in ARGUMENTS:
    107     return env.File(env.SConstructAbsPath(ARGUMENTS['chrome_browser_path']))
    108   else:
    109     return no_browser
    110 
    111 pre_base_env.AddMethod(ChromeBinary)
    112 
    113 
    114 def GetPPAPIPluginPath(env, allow_64bit_redirect=True):
    115   if 'force_ppapi_plugin' in ARGUMENTS:
    116     return env.SConstructAbsPath(ARGUMENTS['force_ppapi_plugin'])
    117   if env.Bit('mac'):
    118     fn = env.File('${STAGING_DIR}/ppNaClPlugin')
    119   else:
    120     fn = env.File('${STAGING_DIR}/${SHLIBPREFIX}ppNaClPlugin${SHLIBSUFFIX}')
    121   if allow_64bit_redirect and env.Bit('target_x86_64'):
    122     # On 64-bit Windows and on Mac, we need the 32-bit plugin because
    123     # the browser is 32-bit.
    124     # Unfortunately it is tricky to build the 32-bit plugin (and all the
    125     # libraries it needs) in a 64-bit build... so we'll assume it has already
    126     # been built in a previous invocation.
    127     # TODO(ncbray) better 32/64 builds.
    128     if env.Bit('windows'):
    129       fn = env.subst(fn).abspath.replace('-win-x86-64', '-win-x86-32')
    130     elif env.Bit('mac'):
    131       fn = env.subst(fn).abspath.replace('-mac-x86-64', '-mac-x86-32')
    132   return fn
    133 
    134 pre_base_env.AddMethod(GetPPAPIPluginPath)
    135 
    136 
    137 # runnable-ld.so log has following format:
    138 # lib_name => path_to_lib (0x....address)
    139 def ParseLibInfoInRunnableLdLog(line):
    140   pos = line.find(' => ')
    141   if pos < 0:
    142     return None
    143   lib_name = line[:pos].strip()
    144   lib_path = line[pos+4:]
    145   pos1 = lib_path.rfind(' (')
    146   if pos1 < 0:
    147     return None
    148   lib_path = lib_path[:pos1]
    149   return lib_name, lib_path
    150 
    151 
    152 # Expected name of the temporary .libs file which stores glibc library
    153 # dependencies in "lib_name => lib_info" format
    154 # (see ParseLibInfoInRunnableLdLog)
    155 def GlibcManifestLibsListFilename(manifest_base_name):
    156   return '${STAGING_DIR}/%s.libs' % manifest_base_name
    157 
    158 
    159 # Copy libs and manifest to the target directory.
    160 # source[0] is a manifest file
    161 # source[1] is a .libs file with a list of libs generated by runnable-ld.so
    162 def CopyLibsForExtensionCommand(target, source, env):
    163   source_manifest = str(source[0])
    164   target_manifest = str(target[0])
    165   shutil.copyfile(source_manifest, target_manifest)
    166   target_dir = os.path.dirname(target_manifest)
    167   libs_file = open(str(source[1]), 'r')
    168   for line in libs_file.readlines():
    169     lib_info = ParseLibInfoInRunnableLdLog(line)
    170     if lib_info:
    171       lib_name, lib_path = lib_info
    172       if lib_path == 'NaClMain':
    173         # This is a fake file name, which we cannot copy.
    174         continue
    175       shutil.copyfile(lib_path, os.path.join(target_dir, lib_name))
    176   shutil.copyfile(env.subst('${NACL_SDK_LIB}/runnable-ld.so'),
    177                   os.path.join(target_dir, 'runnable-ld.so'))
    178   libs_file.close()
    179 
    180 
    181 # Extensions are loaded from directory on disk and so all dynamic libraries
    182 # they use must be copied to extension directory. The option --extra_serving_dir
    183 # does not help us in this case.
    184 def CopyLibsForExtension(env, target_dir, manifest):
    185   if not env.Bit('nacl_glibc'):
    186     return env.Install(target_dir, manifest)
    187   manifest_base_name = os.path.basename(str(env.subst(manifest)))
    188   lib_list_node = env.File(GlibcManifestLibsListFilename(manifest_base_name))
    189   nmf_node = env.Command(
    190       target_dir + '/' + manifest_base_name,
    191       [manifest, lib_list_node],
    192       CopyLibsForExtensionCommand)
    193   return nmf_node
    194 
    195 pre_base_env.AddMethod(CopyLibsForExtension)
    196 
    197 
    198 
    199 def WhitelistLibsForExtensionCommand(target, source, env):
    200   # Load existing extension manifest.
    201   src_file = open(source[0].abspath, 'r')
    202   src_json = json.load(src_file)
    203   src_file.close()
    204 
    205   # Load existing 'web_accessible_resources' key.
    206   if 'web_accessible_resources' not in src_json:
    207     src_json['web_accessible_resources'] = []
    208   web_accessible = src_json['web_accessible_resources']
    209 
    210   # Load list of libraries, and add libraries to web_accessible list.
    211   libs_file = open(source[1].abspath, 'r')
    212   for line in libs_file.readlines():
    213     lib_info = ParseLibInfoInRunnableLdLog(line)
    214     if lib_info:
    215       web_accessible.append(lib_info[0])
    216   # Also add the dynamic loader, which won't be in the libs_file.
    217   web_accessible.append('runnable-ld.so')
    218   libs_file.close()
    219 
    220   # Write out the appended-to extension manifest.
    221   target_file = open(target[0].abspath, 'w')
    222   json.dump(src_json, target_file, sort_keys=True, indent=2)
    223   target_file.close()
    224 
    225 
    226 # Whitelist glibc shared libraries (if necessary), so that they are
    227 # 'web_accessible_resources'.  This allows the libraries hosted at the origin
    228 # chrome-extension://[PACKAGE ID]/
    229 # to be made available to webpages that use this NaCl extension,
    230 # which are in a different origin.
    231 # See: http://code.google.com/chrome/extensions/manifest.html
    232 #
    233 # Alternatively, we could try to use the chrome commandline switch
    234 # '--disable-extensions-resource-whitelist', but that would not be what
    235 # users will need to do.
    236 def WhitelistLibsForExtension(env, target_dir, nmf, extension_manifest):
    237   if env.Bit('nacl_static_link'):
    238     # For static linking, assume the nexe and nmf files are already
    239     # whitelisted, so there is no need to add entries to the extension_manifest.
    240     return env.Install(target_dir, extension_manifest)
    241   nmf_base_name = os.path.basename(env.File(nmf).abspath)
    242   lib_list_node = env.File(GlibcManifestLibsListFilename(nmf_base_name))
    243   manifest_base_name = os.path.basename(env.File(extension_manifest).abspath)
    244   extension_manifest_node = env.Command(
    245       target_dir + '/' + manifest_base_name,
    246       [extension_manifest, lib_list_node],
    247       WhitelistLibsForExtensionCommand)
    248   return extension_manifest_node
    249 
    250 pre_base_env.AddMethod(WhitelistLibsForExtension)
    251 
    252 
    253 # Generate manifest from newlib manifest and the list of libs generated by
    254 # runnable-ld.so.
    255 def GenerateManifestFunc(target, source, env):
    256   # Open the original manifest and parse it.
    257   source_file = open(str(source[0]), 'r')
    258   obj = json.load(source_file)
    259   source_file.close()
    260   # Open the file with ldd-format list of NEEDED libs and parse it.
    261   libs_file = open(str(source[1]), 'r')
    262   lib_names = []
    263   arch = env.subst('${TARGET_FULLARCH}')
    264   for line in libs_file.readlines():
    265     lib_info = ParseLibInfoInRunnableLdLog(line)
    266     if lib_info:
    267       lib_name, _ = lib_info
    268       lib_names.append(lib_name)
    269   libs_file.close()
    270   # Inject the NEEDED libs into the manifest.
    271   if 'files' not in obj:
    272     obj['files'] = {}
    273   for lib_name in lib_names:
    274     obj['files'][lib_name] = {}
    275     obj['files'][lib_name][arch] = {}
    276     obj['files'][lib_name][arch]['url'] = lib_name
    277   # Put what used to be specified under 'program' into 'main.nexe'.
    278   obj['files']['main.nexe'] = {}
    279   for k, v in obj['program'].items():
    280     obj['files']['main.nexe'][k] = v.copy()
    281     v['url'] = 'runnable-ld.so'
    282   # Write the new manifest!
    283   target_file = open(str(target[0]), 'w')
    284   json.dump(obj, target_file, sort_keys=True, indent=2)
    285   target_file.close()
    286   return 0
    287 
    288 
    289 def GenerateManifestDynamicLink(env, dest_file, lib_list_file,
    290                                 manifest, exe_file):
    291   # Run sel_ldr on the nexe to trace the NEEDED libraries.
    292   lib_list_node = env.Command(
    293       lib_list_file,
    294       [env.GetSelLdr(),
    295        '${NACL_SDK_LIB}/runnable-ld.so',
    296        exe_file,
    297        '${SCONSTRUCT_DIR}/DEPS'],
    298       # We ignore the return code using '-' in order to build tests
    299       # where binaries do not validate.  This is a Scons feature.
    300       '-${SOURCES[0]} -a -E LD_TRACE_LOADED_OBJECTS=1 ${SOURCES[1]} '
    301       '--library-path ${NACL_SDK_LIB}:${LIB_DIR} ${SOURCES[2].posix} '
    302       '> ${TARGET}')
    303   return env.Command(dest_file,
    304                      [manifest, lib_list_node],
    305                      GenerateManifestFunc)[0]
    306 
    307 
    308 def GenerateSimpleManifestStaticLink(env, dest_file, exe_name):
    309   def Func(target, source, env):
    310     archs = ('x86-32', 'x86-64', 'arm')
    311     nmf_data = {'program': dict((arch, {'url': '%s_%s.nexe' % (exe_name, arch)})
    312                                 for arch in archs)}
    313     fh = open(target[0].abspath, 'w')
    314     json.dump(nmf_data, fh, sort_keys=True, indent=2)
    315     fh.close()
    316   node = env.Command(dest_file, [], Func)[0]
    317   # Scons does not track the dependency of dest_file on exe_name or on
    318   # the Python code above, so we should always recreate dest_file when
    319   # it is used.
    320   env.AlwaysBuild(node)
    321   return node
    322 
    323 
    324 def GenerateSimpleManifest(env, dest_file, exe_name):
    325   if env.Bit('nacl_static_link'):
    326     return GenerateSimpleManifestStaticLink(env, dest_file, exe_name)
    327   else:
    328     static_manifest = GenerateSimpleManifestStaticLink(
    329         env, '%s.static' % dest_file, exe_name)
    330     return GenerateManifestDynamicLink(
    331         env, dest_file, '%s.tmp_lib_list' % dest_file, static_manifest,
    332         '${STAGING_DIR}/%s.nexe' % env.ProgramNameForNmf(exe_name))
    333 
    334 pre_base_env.AddMethod(GenerateSimpleManifest)
    335 
    336 
    337 # Returns a pair (main program, is_portable), based on the program
    338 # specified in manifest file.
    339 def GetMainProgramFromManifest(env, manifest):
    340   obj = json.loads(env.File(manifest).get_contents())
    341   program_dict = obj['program']
    342   return program_dict[env.subst('${TARGET_FULLARCH}')]['url']
    343 
    344 
    345 # Returns scons node for generated manifest.
    346 def GeneratedManifestNode(env, manifest):
    347   manifest = env.subst(manifest)
    348   manifest_base_name = os.path.basename(manifest)
    349   main_program = GetMainProgramFromManifest(env, manifest)
    350   result = env.File('${STAGING_DIR}/' + manifest_base_name)
    351   # Always generate the manifest for nacl_glibc.
    352   # For nacl_glibc, generating the mapping of shared libraries is non-trivial.
    353   if not env.Bit('nacl_glibc'):
    354     env.Install('${STAGING_DIR}', manifest)
    355     return result
    356   return GenerateManifestDynamicLink(
    357       env, '${STAGING_DIR}/' + manifest_base_name,
    358       # Note that CopyLibsForExtension() and WhitelistLibsForExtension()
    359       # assume that it can find the library list file under this filename.
    360       GlibcManifestLibsListFilename(manifest_base_name),
    361       manifest,
    362       env.File('${STAGING_DIR}/' + os.path.basename(main_program)))
    363   return result
    364 
    365 
    366 # Compares output_file and golden_file.
    367 # If they are different, prints the difference and returns 1.
    368 # Otherwise, returns 0.
    369 def CheckGoldenFile(golden_file, output_file,
    370                     filter_regex, filter_inverse, filter_group_only):
    371   golden = open(golden_file).read()
    372   actual = open(output_file).read()
    373   if filter_regex is not None:
    374     actual = test_lib.RegexpFilterLines(
    375         filter_regex,
    376         filter_inverse,
    377         filter_group_only,
    378         actual)
    379   if command_tester.DifferentFromGolden(actual, golden, output_file):
    380     return 1
    381   return 0
    382 
    383 
    384 # Returns action that compares output_file and golden_file.
    385 # This action can be attached to the node with
    386 # env.AddPostAction(target, action)
    387 def GoldenFileCheckAction(env, output_file, golden_file,
    388                           filter_regex=None, filter_inverse=False,
    389                           filter_group_only=False):
    390   def ActionFunc(target, source, env):
    391     return CheckGoldenFile(env.subst(golden_file), env.subst(output_file),
    392                            filter_regex, filter_inverse, filter_group_only)
    393 
    394   return env.Action(ActionFunc)
    395 
    396 
    397 def PPAPIBrowserTester(env,
    398                        target,
    399                        url,
    400                        files,
    401                        nmfs=None,
    402                        # List of executable basenames to generate
    403                        # manifest files for.
    404                        nmf_names=(),
    405                        map_files=(),
    406                        extensions=(),
    407                        mime_types=(),
    408                        timeout=30,
    409                        log_verbosity=2,
    410                        args=[],
    411                        # list of key/value pairs that are passed to the test
    412                        test_args=(),
    413                        # list of "--flag=value" pairs (no spaces!)
    414                        browser_flags=None,
    415                        # redirect streams of NaCl program to files
    416                        nacl_exe_stdin=None,
    417                        nacl_exe_stdout=None,
    418                        nacl_exe_stderr=None,
    419                        python_tester_script=None,
    420                        **extra):
    421   if 'TRUSTED_ENV' not in env:
    422     return []
    423 
    424   # No browser tests run on arm-thumb2
    425   # Bug http://code.google.com/p/nativeclient/issues/detail?id=2224
    426   if env.Bit('target_arm_thumb2'):
    427     return []
    428 
    429   # Handle issues with mutating any python default arg lists.
    430   if browser_flags is None:
    431     browser_flags = []
    432 
    433   # Lint the extra arguments that are being passed to the tester.
    434   special_args = ['--ppapi_plugin', '--sel_ldr', '--irt_library', '--file',
    435                   '--map_file', '--extension', '--mime_type', '--tool',
    436                   '--browser_flag', '--test_arg']
    437   for arg_name in special_args:
    438     if arg_name in args:
    439       raise Exception('%s: %r is a test argument provided by the SCons test'
    440                       ' wrapper, do not specify it as an additional argument' %
    441                       (target, arg_name))
    442 
    443   env = env.Clone()
    444   env.SetupBrowserEnv()
    445 
    446   if 'scale_timeout' in ARGUMENTS:
    447     timeout = timeout * int(ARGUMENTS['scale_timeout'])
    448 
    449   if python_tester_script is None:
    450     python_tester_script = env.File('${SCONSTRUCT_DIR}/tools/browser_tester'
    451                              '/browser_tester.py')
    452   command = env.GetHeadlessPrefix() + [
    453       '${PYTHON}', python_tester_script,
    454       '--browser_path', env.ChromeBinary(),
    455       '--url', url,
    456       # Fail if there is no response for X seconds.
    457       '--timeout', str(timeout)]
    458   for dep_file in files:
    459     command.extend(['--file', dep_file])
    460   for extension in extensions:
    461     command.extend(['--extension', extension])
    462   for dest_path, dep_file in map_files:
    463     command.extend(['--map_file', dest_path, dep_file])
    464   for file_ext, mime_type in mime_types:
    465     command.extend(['--mime_type', file_ext, mime_type])
    466   command.extend(['--serving_dir', '${NACL_SDK_LIB}'])
    467   command.extend(['--serving_dir', '${LIB_DIR}'])
    468   if 'browser_tester_bw' in ARGUMENTS:
    469     command.extend(['-b', ARGUMENTS['browser_tester_bw']])
    470   if not nmfs is None:
    471     for nmf_file in nmfs:
    472       generated_manifest = GeneratedManifestNode(env, nmf_file)
    473       # We need to add generated manifests to the list of default targets.
    474       # The manifests should be generated even if the tests are not run -
    475       # the manifests may be needed for manual testing.
    476       for group in env['COMPONENT_TEST_PROGRAM_GROUPS']:
    477         env.Alias(group, generated_manifest)
    478       # Generated manifests are served in the root of the HTTP server
    479       command.extend(['--file', generated_manifest])
    480   for nmf_name in nmf_names:
    481     tmp_manifest = '%s.tmp/%s.nmf' % (target, nmf_name)
    482     command.extend(['--map_file', '%s.nmf' % nmf_name,
    483                     env.GenerateSimpleManifest(tmp_manifest, nmf_name)])
    484   if 'browser_test_tool' in ARGUMENTS:
    485     command.extend(['--tool', ARGUMENTS['browser_test_tool']])
    486 
    487   # Suppress debugging information on the Chrome waterfall.
    488   if env.Bit('disable_flaky_tests') and '--debug' in args:
    489     args.remove('--debug')
    490 
    491   command.extend(args)
    492   for flag in browser_flags:
    493     if flag.find(' ') != -1:
    494       raise Exception('Spaces not allowed in browser_flags: '
    495                       'use --flag=value instead')
    496     command.extend(['--browser_flag', flag])
    497   for key, value in test_args:
    498     command.extend(['--test_arg', str(key), str(value)])
    499 
    500   # Set a given file to be the nexe's stdin.
    501   if nacl_exe_stdin is not None:
    502     command.extend(['--nacl_exe_stdin', env.subst(nacl_exe_stdin['file'])])
    503 
    504   post_actions = []
    505   side_effects = []
    506   # Set a given file to be the nexe's stdout or stderr.  The tester also
    507   # compares this output against a golden file.
    508   for stream, params in (
    509       ('stdout', nacl_exe_stdout),
    510       ('stderr', nacl_exe_stderr)):
    511     if params is None:
    512       continue
    513     stream_file = env.subst(params['file'])
    514     side_effects.append(stream_file)
    515     command.extend(['--nacl_exe_' + stream, stream_file])
    516     if 'golden' in params:
    517       golden_file = env.subst(params['golden'])
    518       filter_regex = params.get('filter_regex', None)
    519       filter_inverse = params.get('filter_inverse', False)
    520       filter_group_only = params.get('filter_group_only', False)
    521       post_actions.append(
    522           GoldenFileCheckAction(
    523               env, stream_file, golden_file,
    524               filter_regex, filter_inverse, filter_group_only))
    525 
    526   if env.ShouldUseVerboseOptions(extra):
    527     env.MakeVerboseExtraOptions(target, log_verbosity, extra)
    528   # Heuristic for when to capture output...
    529   capture_output = (extra.pop('capture_output', False)
    530                     or 'process_output_single' in extra)
    531   node = env.CommandTest(target,
    532                          command,
    533                          # Set to 'huge' so that the browser tester's timeout
    534                          # takes precedence over the default of the test_suite.
    535                          size='huge',
    536                          capture_output=capture_output,
    537                          **extra)
    538   for side_effect in side_effects:
    539     env.SideEffect(side_effect, node)
    540   # We can't check output if the test is not run.
    541   if not env.Bit('do_not_run_tests'):
    542     for action in post_actions:
    543       env.AddPostAction(node, action)
    544   return node
    545 
    546 pre_base_env.AddMethod(PPAPIBrowserTester)
    547 
    548 
    549 # Disabled for ARM and MIPS because Chrome binaries for ARM and MIPS are not
    550 # available.
    551 def PPAPIBrowserTesterIsBroken(env):
    552   return (env.Bit('target_arm') or env.Bit('target_arm_thumb2')
    553           or env.Bit('target_mips32'))
    554 
    555 pre_base_env.AddMethod(PPAPIBrowserTesterIsBroken)
    556 
    557 # 3D is disabled everywhere
    558 def PPAPIGraphics3DIsBroken(env):
    559   return True
    560 
    561 pre_base_env.AddMethod(PPAPIGraphics3DIsBroken)
    562 
    563 
    564 def AddChromeFilesFromGroup(env, file_group):
    565   env['BUILD_SCONSCRIPTS'] = ExtendFileList(
    566       env.get('BUILD_SCONSCRIPTS', []),
    567       ppapi_scons_files[file_group])
    568 
    569 pre_base_env.AddMethod(AddChromeFilesFromGroup)
    570