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