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.
11 sys.path.append(Dir('#/tools').abspath)
15 Import(['pre_base_env'])
17 # Underlay things migrating to ppapi repo.
18 Dir('#/..').addRepository(Dir('#/../ppapi'))
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.
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)
33 ppapi_scons_files = {}
34 ppapi_scons_files['trusted_scons_files'] = []
35 ppapi_scons_files['untrusted_irt_scons_files'] = []
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/nacl_browser/postmessage_redir/nacl.scons',
43 'tests/ppapi_browser/bad/nacl.scons',
44 'tests/ppapi_browser/crash/nacl.scons',
45 'tests/ppapi_browser/extension_mime_handler/nacl.scons',
46 'tests/ppapi_browser/manifest/nacl.scons',
47 'tests/ppapi_browser/ppb_instance/nacl.scons',
48 'tests/ppapi_browser/ppp_instance/nacl.scons',
49 'tests/ppapi_test_lib/nacl.scons',
52 ppapi_scons_files['irt_variant_test_scons_files'] = [
53 # 'inbrowser_test_runner' must be in the irt_variant list
54 # otherwise it will run no tests.
55 'tests/nacl_browser/inbrowser_test_runner/nacl.scons',
56 # Disabled by Brad Chen 4 Sep to try to green Chromium
57 # nacl_integration tests
58 #'tests/nacl_browser/fault_injection/nacl.scons',
61 ppapi_scons_files['untrusted_scons_files'] = [
62 'src/shared/ppapi/nacl.scons',
63 'src/untrusted/irt_stub/nacl.scons',
64 'src/untrusted/nacl_ppapi_util/nacl.scons',
65 'src/untrusted/pnacl_irt_shim/nacl.scons',
66 'src/untrusted/pnacl_support_extension/nacl.scons',
70 EXTRA_ENV = ['XAUTHORITY', 'HOME', 'DISPLAY', 'SSH_TTY', 'KRB5CCNAME']
72 def SetupBrowserEnv(env):
73 for var_name in EXTRA_ENV:
74 if var_name in os.environ:
75 env['ENV'][var_name] = os.environ[var_name]
77 pre_base_env.AddMethod(SetupBrowserEnv)
80 def GetHeadlessPrefix(env):
81 if env.Bit('browser_headless') and env.Bit('host_linux'):
82 return ['xvfb-run', '--auto-servernum']
84 # Mac and Windows do not seem to have an equivalent.
87 pre_base_env.AddMethod(GetHeadlessPrefix)
90 # A fake file to depend on if a path to Chrome is not specified.
91 no_browser = pre_base_env.File('chrome_browser_path_not_specified')
94 # SCons attempts to run a test that depends on "no_browser", detect this at
95 # runtime and cause a build error.
96 def NoBrowserError(target, source, env):
97 print target, source, env
98 print ("***\nYou need to specificy chrome_browser_path=... on the " +
99 "command line to run these tests.\n***\n")
102 pre_base_env.Append(BUILDERS = {
103 'NoBrowserError': Builder(action=NoBrowserError)
106 pre_base_env.NoBrowserError([no_browser], [])
109 def ChromeBinary(env):
110 if 'chrome_browser_path' in ARGUMENTS:
111 return env.File(env.SConstructAbsPath(ARGUMENTS['chrome_browser_path']))
115 pre_base_env.AddMethod(ChromeBinary)
118 def GetPPAPIPluginPath(env, allow_64bit_redirect=True):
119 if 'force_ppapi_plugin' in ARGUMENTS:
120 return env.SConstructAbsPath(ARGUMENTS['force_ppapi_plugin'])
122 fn = env.File('${STAGING_DIR}/ppNaClPlugin')
124 fn = env.File('${STAGING_DIR}/${SHLIBPREFIX}ppNaClPlugin${SHLIBSUFFIX}')
125 if allow_64bit_redirect and env.Bit('target_x86_64'):
126 # On 64-bit Windows and on Mac, we need the 32-bit plugin because
127 # the browser is 32-bit.
128 # Unfortunately it is tricky to build the 32-bit plugin (and all the
129 # libraries it needs) in a 64-bit build... so we'll assume it has already
130 # been built in a previous invocation.
131 # TODO(ncbray) better 32/64 builds.
132 if env.Bit('windows'):
133 fn = env.subst(fn).abspath.replace('-win-x86-64', '-win-x86-32')
135 fn = env.subst(fn).abspath.replace('-mac-x86-64', '-mac-x86-32')
138 pre_base_env.AddMethod(GetPPAPIPluginPath)
141 # runnable-ld.so log has following format:
142 # lib_name => path_to_lib (0x....address)
143 def ParseLibInfoInRunnableLdLog(line):
144 pos = line.find(' => ')
147 lib_name = line[:pos].strip()
148 lib_path = line[pos+4:]
149 pos1 = lib_path.rfind(' (')
152 lib_path = lib_path[:pos1]
153 return lib_name, lib_path
156 # Expected name of the temporary .libs file which stores glibc library
157 # dependencies in "lib_name => lib_info" format
158 # (see ParseLibInfoInRunnableLdLog)
159 def GlibcManifestLibsListFilename(manifest_base_name):
160 return '${STAGING_DIR}/%s.libs' % manifest_base_name
163 # Copy libs and manifest to the target directory.
164 # source[0] is a manifest file
165 # source[1] is a .libs file with a list of libs generated by runnable-ld.so
166 def CopyLibsForExtensionCommand(target, source, env):
167 source_manifest = str(source[0])
168 target_manifest = str(target[0])
169 shutil.copyfile(source_manifest, target_manifest)
170 target_dir = os.path.dirname(target_manifest)
171 libs_file = open(str(source[1]), 'r')
172 for line in libs_file.readlines():
173 lib_info = ParseLibInfoInRunnableLdLog(line)
175 lib_name, lib_path = lib_info
176 # Note: This probably does NOT work with pnacl _pexes_ right now, because
177 # the NEEDED metadata in the bitcode doesn't have the original file paths.
178 # This should probably be done without such knowledge.
179 if lib_path == 'NaClMain':
180 # This is a fake file name, which we cannot copy.
182 shutil.copyfile(lib_path, os.path.join(target_dir, lib_name))
183 shutil.copyfile(env.subst('${NACL_SDK_LIB}/runnable-ld.so'),
184 os.path.join(target_dir, 'runnable-ld.so'))
188 # Extensions are loaded from directory on disk and so all dynamic libraries
189 # they use must be copied to extension directory. The option --extra_serving_dir
190 # does not help us in this case.
191 def CopyLibsForExtension(env, target_dir, manifest):
192 if not env.Bit('nacl_glibc'):
193 return env.Install(target_dir, manifest)
194 manifest_base_name = os.path.basename(str(env.subst(manifest)))
195 lib_list_node = env.File(GlibcManifestLibsListFilename(manifest_base_name))
196 nmf_node = env.Command(
197 target_dir + '/' + manifest_base_name,
198 [manifest, lib_list_node],
199 CopyLibsForExtensionCommand)
202 pre_base_env.AddMethod(CopyLibsForExtension)
206 def WhitelistLibsForExtensionCommand(target, source, env):
207 # Load existing extension manifest.
208 src_file = open(source[0].abspath, 'r')
209 src_json = json.load(src_file)
212 # Load existing 'web_accessible_resources' key.
213 if 'web_accessible_resources' not in src_json:
214 src_json['web_accessible_resources'] = []
215 web_accessible = src_json['web_accessible_resources']
217 # Load list of libraries, and add libraries to web_accessible list.
218 libs_file = open(source[1].abspath, 'r')
219 for line in libs_file.readlines():
220 lib_info = ParseLibInfoInRunnableLdLog(line)
222 web_accessible.append(lib_info[0])
223 # Also add the dynamic loader, which won't be in the libs_file.
224 web_accessible.append('runnable-ld.so')
227 # Write out the appended-to extension manifest.
228 target_file = open(target[0].abspath, 'w')
229 json.dump(src_json, target_file, sort_keys=True, indent=2)
233 # Whitelist glibc shared libraries (if necessary), so that they are
234 # 'web_accessible_resources'. This allows the libraries hosted at the origin
235 # chrome-extension://[PACKAGE ID]/
236 # to be made available to webpages that use this NaCl extension,
237 # which are in a different origin.
238 # See: http://code.google.com/chrome/extensions/manifest.html
240 # Alternatively, we could try to use the chrome commandline switch
241 # '--disable-extensions-resource-whitelist', but that would not be what
242 # users will need to do.
243 def WhitelistLibsForExtension(env, target_dir, nmf, extension_manifest):
244 if env.Bit('nacl_static_link'):
245 # For static linking, assume the nexe and nmf files are already
246 # whitelisted, so there is no need to add entries to the extension_manifest.
247 return env.Install(target_dir, extension_manifest)
248 nmf_base_name = os.path.basename(env.File(nmf).abspath)
249 lib_list_node = env.File(GlibcManifestLibsListFilename(nmf_base_name))
250 manifest_base_name = os.path.basename(env.File(extension_manifest).abspath)
251 extension_manifest_node = env.Command(
252 target_dir + '/' + manifest_base_name,
253 [extension_manifest, lib_list_node],
254 WhitelistLibsForExtensionCommand)
255 return extension_manifest_node
257 pre_base_env.AddMethod(WhitelistLibsForExtension)
260 # Generate manifest from newlib manifest and the list of libs generated by
262 def GenerateManifestFunc(target, source, env):
263 # Open the original manifest and parse it.
264 source_file = open(str(source[0]), 'r')
265 obj = json.load(source_file)
267 # Open the file with ldd-format list of NEEDED libs and parse it.
268 libs_file = open(str(source[1]), 'r')
270 arch = env.subst('${TARGET_FULLARCH}')
271 for line in libs_file.readlines():
272 lib_info = ParseLibInfoInRunnableLdLog(line)
274 lib_name, _ = lib_info
275 lib_names.append(lib_name)
277 # Inject the NEEDED libs into the manifest.
278 if 'files' not in obj:
280 for lib_name in lib_names:
281 obj['files'][lib_name] = {}
282 obj['files'][lib_name][arch] = {}
283 obj['files'][lib_name][arch]['url'] = lib_name
284 # Put what used to be specified under 'program' into 'main.nexe'.
285 obj['files']['main.nexe'] = {}
286 for k, v in obj['program'].items():
287 obj['files']['main.nexe'][k] = v.copy()
288 v['url'] = 'runnable-ld.so'
289 # Write the new manifest!
290 target_file = open(str(target[0]), 'w')
291 json.dump(obj, target_file, sort_keys=True, indent=2)
295 def GenerateManifestPnacl(env, dest_file, manifest, exe_file):
298 ['${GENNMF}', exe_file, manifest],
299 # Generate a flat url scheme to simplify file-staging.
300 ('${SOURCES[0]} ${SOURCES[1]} -L${NACL_SDK_LIB} -L${LIB_DIR} '
301 ' --flat-url-scheme --base-nmf ${SOURCES[2]} -o ${TARGET}'))
303 def GenerateManifestDynamicLink(env, dest_file, lib_list_file,
305 # Run sel_ldr on the nexe to trace the NEEDED libraries.
306 lib_list_node = env.Command(
309 '${NACL_SDK_LIB}/runnable-ld.so',
311 '${SCONSTRUCT_DIR}/DEPS'],
312 # We ignore the return code using '-' in order to build tests
313 # where binaries do not validate. This is a Scons feature.
314 '-${SOURCES[0]} -a -E LD_TRACE_LOADED_OBJECTS=1 ${SOURCES[1]} '
315 '--library-path ${NACL_SDK_LIB}:${LIB_DIR} ${SOURCES[2].posix} '
317 return env.Command(dest_file,
318 [manifest, lib_list_node],
319 GenerateManifestFunc)[0]
322 def GenerateSimpleManifestStaticLink(env, dest_file, exe_name):
323 def Func(target, source, env):
324 archs = ('x86-32', 'x86-64', 'arm')
325 nmf_data = {'program': dict((arch, {'url': '%s_%s.nexe' % (exe_name, arch)})
327 fh = open(target[0].abspath, 'w')
328 json.dump(nmf_data, fh, sort_keys=True, indent=2)
330 node = env.Command(dest_file, [], Func)[0]
331 # Scons does not track the dependency of dest_file on exe_name or on
332 # the Python code above, so we should always recreate dest_file when
334 env.AlwaysBuild(node)
338 def GenerateSimpleManifest(env, dest_file, exe_name):
339 if env.Bit('pnacl_generate_pexe'):
340 static_manifest = GenerateSimpleManifestStaticLink(
341 env, '%s.static' % dest_file, exe_name)
342 return GenerateManifestPnacl(env, dest_file, static_manifest,
343 '${STAGING_DIR}/%s.pexe' %
344 env.ProgramNameForNmf(exe_name))
345 elif env.Bit('nacl_static_link'):
346 return GenerateSimpleManifestStaticLink(env, dest_file, exe_name)
348 static_manifest = GenerateSimpleManifestStaticLink(
349 env, '%s.static' % dest_file, exe_name)
350 return GenerateManifestDynamicLink(
351 env, dest_file, '%s.tmp_lib_list' % dest_file, static_manifest,
352 '${STAGING_DIR}/%s.nexe' % env.ProgramNameForNmf(exe_name))
354 pre_base_env.AddMethod(GenerateSimpleManifest)
357 # Returns a pair (main program, is_portable), based on the program
358 # specified in manifest file.
359 def GetMainProgramFromManifest(env, manifest):
360 obj = json.loads(env.File(manifest).get_contents())
361 program_dict = obj['program']
362 if env.Bit('pnacl_generate_pexe') and 'portable' in program_dict:
363 return program_dict['portable']['pnacl-translate']['url']
365 return program_dict[env.subst('${TARGET_FULLARCH}')]['url']
368 # Returns scons node for generated manifest.
369 def GeneratedManifestNode(env, manifest):
370 manifest = env.subst(manifest)
371 manifest_base_name = os.path.basename(manifest)
372 main_program = GetMainProgramFromManifest(env, manifest)
373 result = env.File('${STAGING_DIR}/' + manifest_base_name)
374 # Always generate the manifest for nacl_glibc and pnacl pexes.
375 # For nacl_glibc, generating the mapping of shared libraries is non-trivial.
376 # For pnacl, the manifest currently hosts a sha for the pexe.
377 if not env.Bit('nacl_glibc') and not env.Bit('pnacl_generate_pexe'):
378 env.Install('${STAGING_DIR}', manifest)
380 if env.Bit('pnacl_generate_pexe'):
381 return GenerateManifestPnacl(
383 '${STAGING_DIR}/' + manifest_base_name,
385 env.File('${STAGING_DIR}/' + os.path.basename(main_program)))
387 return GenerateManifestDynamicLink(
388 env, '${STAGING_DIR}/' + manifest_base_name,
389 # Note that CopyLibsForExtension() and WhitelistLibsForExtension()
390 # assume that it can find the library list file under this filename.
391 GlibcManifestLibsListFilename(manifest_base_name),
393 env.File('${STAGING_DIR}/' + os.path.basename(main_program)))
397 def GetPnaclExtensionRootNode(env):
398 """Get the scons node representing the root directory of pnacl support files.
400 # This is "built" by src/untrusted/pnacl_support_extension/nacl.scons.
401 return env.Dir('${DESTINATION_ROOT}/pnacl_support')
403 pre_base_env.AddMethod(GetPnaclExtensionRootNode)
405 def GetPnaclExtensionDummyVersion(env):
406 """ We supply a dummy version number when packaging the test-extension
407 that is probably newer than all other versions. """
410 pre_base_env.AddMethod(GetPnaclExtensionDummyVersion)
412 def GetPnaclExtensionNode(env):
413 """Get the scons node representing a specific version of pnacl support files.
415 # This is "built" by src/untrusted/pnacl_support_extension/nacl.scons.
416 return env.Dir('${DESTINATION_ROOT}/pnacl_support/' +
417 env.GetPnaclExtensionDummyVersion())
419 pre_base_env.AddMethod(GetPnaclExtensionNode)
422 # Compares output_file and golden_file.
423 # If they are different, prints the difference and returns 1.
424 # Otherwise, returns 0.
425 def CheckGoldenFile(golden_file, output_file,
426 filter_regex, filter_inverse, filter_group_only):
427 golden = open(golden_file).read()
428 actual = open(output_file).read()
429 if filter_regex is not None:
430 actual = test_lib.RegexpFilterLines(
435 if command_tester.DifferentFromGolden(actual, golden, output_file):
440 # Returns action that compares output_file and golden_file.
441 # This action can be attached to the node with
442 # env.AddPostAction(target, action)
443 def GoldenFileCheckAction(env, output_file, golden_file,
444 filter_regex=None, filter_inverse=False,
445 filter_group_only=False):
446 def ActionFunc(target, source, env):
447 return CheckGoldenFile(env.subst(golden_file), env.subst(output_file),
448 filter_regex, filter_inverse, filter_group_only)
450 return env.Action(ActionFunc)
453 def PPAPIBrowserTester(env,
458 # List of executable basenames to generate
459 # manifest files for.
467 # list of key/value pairs that are passed to the test
469 # list of "--flag=value" pairs (no spaces!)
471 # redirect streams of NaCl program to files
473 nacl_exe_stdout=None,
474 nacl_exe_stderr=None,
475 python_tester_script=None,
477 if 'TRUSTED_ENV' not in env:
480 # No browser tests run on arm-thumb2
481 # Bug http://code.google.com/p/nativeclient/issues/detail?id=2224
482 if env.Bit('target_arm_thumb2'):
485 # Handle issues with mutating any python default arg lists.
486 if browser_flags is None:
489 if env.Bit('pnacl_generate_pexe'):
490 # We likely prefer to choose the 'portable' field in nmfs in this mode.
491 args = args + ['--prefer_portable_in_manifest']
493 # Lint the extra arguments that are being passed to the tester.
494 special_args = ['--ppapi_plugin', '--sel_ldr', '--irt_library', '--file',
495 '--map_file', '--extension', '--mime_type', '--tool',
496 '--browser_flag', '--test_arg']
497 for arg_name in special_args:
499 raise Exception('%s: %r is a test argument provided by the SCons test'
500 ' wrapper, do not specify it as an additional argument' %
504 env.SetupBrowserEnv()
506 if 'scale_timeout' in ARGUMENTS:
507 timeout = timeout * int(ARGUMENTS['scale_timeout'])
509 if python_tester_script is None:
510 python_tester_script = env.File('${SCONSTRUCT_DIR}/tools/browser_tester'
511 '/browser_tester.py')
512 command = env.GetHeadlessPrefix() + [
513 '${PYTHON}', python_tester_script,
514 '--browser_path', env.ChromeBinary(),
516 # Fail if there is no response for X seconds.
517 '--timeout', str(timeout)]
518 for dep_file in files:
519 command.extend(['--file', dep_file])
520 for extension in extensions:
521 command.extend(['--extension', extension])
522 if env.Bit('bitcode'):
523 # Enable the installed version of pnacl, and point to a custom install
524 # directory for testing purposes.
525 browser_flags.append('--enable-pnacl')
526 for dest_path, dep_file in map_files:
527 command.extend(['--map_file', dest_path, dep_file])
528 for file_ext, mime_type in mime_types:
529 command.extend(['--mime_type', file_ext, mime_type])
530 command.extend(['--serving_dir', '${NACL_SDK_LIB}'])
531 command.extend(['--serving_dir', '${LIB_DIR}'])
532 if 'browser_tester_bw' in ARGUMENTS:
533 command.extend(['-b', ARGUMENTS['browser_tester_bw']])
535 for nmf_file in nmfs:
536 generated_manifest = GeneratedManifestNode(env, nmf_file)
537 # We need to add generated manifests to the list of default targets.
538 # The manifests should be generated even if the tests are not run -
539 # the manifests may be needed for manual testing.
540 for group in env['COMPONENT_TEST_PROGRAM_GROUPS']:
541 env.Alias(group, generated_manifest)
542 # Generated manifests are served in the root of the HTTP server
543 command.extend(['--file', generated_manifest])
544 for nmf_name in nmf_names:
545 tmp_manifest = '%s.tmp/%s.nmf' % (target, nmf_name)
546 command.extend(['--map_file', '%s.nmf' % nmf_name,
547 env.GenerateSimpleManifest(tmp_manifest, nmf_name)])
548 if 'browser_test_tool' in ARGUMENTS:
549 command.extend(['--tool', ARGUMENTS['browser_test_tool']])
551 # Suppress debugging information on the Chrome waterfall.
552 if env.Bit('disable_flaky_tests') and '--debug' in args:
553 args.remove('--debug')
556 for flag in browser_flags:
557 if flag.find(' ') != -1:
558 raise Exception('Spaces not allowed in browser_flags: '
559 'use --flag=value instead')
560 command.extend(['--browser_flag', flag])
561 for key, value in test_args:
562 command.extend(['--test_arg', str(key), str(value)])
564 # Set a given file to be the nexe's stdin.
565 if nacl_exe_stdin is not None:
566 command.extend(['--nacl_exe_stdin', env.subst(nacl_exe_stdin['file'])])
570 # Set a given file to be the nexe's stdout or stderr. The tester also
571 # compares this output against a golden file.
572 for stream, params in (
573 ('stdout', nacl_exe_stdout),
574 ('stderr', nacl_exe_stderr)):
577 stream_file = env.subst(params['file'])
578 side_effects.append(stream_file)
579 command.extend(['--nacl_exe_' + stream, stream_file])
580 if 'golden' in params:
581 golden_file = env.subst(params['golden'])
582 filter_regex = params.get('filter_regex', None)
583 filter_inverse = params.get('filter_inverse', False)
584 filter_group_only = params.get('filter_group_only', False)
586 GoldenFileCheckAction(
587 env, stream_file, golden_file,
588 filter_regex, filter_inverse, filter_group_only))
590 if env.ShouldUseVerboseOptions(extra):
591 env.MakeVerboseExtraOptions(target, log_verbosity, extra)
592 # Heuristic for when to capture output...
593 capture_output = (extra.pop('capture_output', False)
594 or 'process_output_single' in extra)
595 node = env.CommandTest(target,
597 # Set to 'huge' so that the browser tester's timeout
598 # takes precedence over the default of the test_suite.
600 capture_output=capture_output,
602 # Also indicate that we depend on the layed-out pnacl translator.
603 if env.Bit('bitcode'):
604 env.Depends(node, env.GetPnaclExtensionNode())
605 for side_effect in side_effects:
606 env.SideEffect(side_effect, node)
607 # We can't check output if the test is not run.
608 if not env.Bit('do_not_run_tests'):
609 for action in post_actions:
610 env.AddPostAction(node, action)
613 pre_base_env.AddMethod(PPAPIBrowserTester)
616 # Disabled for ARM and MIPS because Chrome binaries for ARM and MIPS are not
618 def PPAPIBrowserTesterIsBroken(env):
619 return (env.Bit('target_arm') or env.Bit('target_arm_thumb2')
620 or env.Bit('target_mips32'))
622 pre_base_env.AddMethod(PPAPIBrowserTesterIsBroken)
624 # 3D is disabled everywhere
625 def PPAPIGraphics3DIsBroken(env):
628 pre_base_env.AddMethod(PPAPIGraphics3DIsBroken)
631 def AddChromeFilesFromGroup(env, file_group):
632 env['BUILD_SCONSCRIPTS'] = ExtendFileList(
633 env.get('BUILD_SCONSCRIPTS', []),
634 ppapi_scons_files[file_group])
636 pre_base_env.AddMethod(AddChromeFilesFromGroup)