• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2015 The Chromium 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
6import argparse
7import logging
8import os
9import platform
10import shutil
11import subprocess
12import sys
13import re
14import json
15import tempfile
16
17
18_V8_DIR = os.path.abspath(
19    os.path.join(os.path.dirname(__file__), os.path.pardir, 'third_party',
20                 'v8'))
21
22_JS_PARSER_DIR = os.path.abspath(
23    os.path.join(os.path.dirname(__file__), os.path.pardir, 'third_party',
24                 'parse5', 'parse5.js'))
25
26
27_BOOTSTRAP_JS_DIR = os.path.abspath(
28    os.path.join(os.path.dirname(__file__), 'd8_bootstrap.js'))
29
30_BASE64_COMPAT_DIR = os.path.abspath(
31    os.path.join(os.path.dirname(__file__), 'base64_compat.js'))
32
33_PATH_UTILS_JS_DIR = os.path.abspath(
34    os.path.join(os.path.dirname(__file__), 'path_utils.js'))
35
36_HTML_IMPORTS_LOADER_JS_DIR = os.path.abspath(
37    os.path.join(os.path.dirname(__file__), 'html_imports_loader.js'))
38
39_HTML_TO_JS_GENERATOR_JS_DIR = os.path.abspath(
40    os.path.join(os.path.dirname(__file__), 'html_to_js_generator.js'))
41
42
43_BOOTSTRAP_JS_CONTENT = None
44
45
46def _ValidateSourcePaths(source_paths):
47  if source_paths is None:
48    return
49  for x in source_paths:
50    assert os.path.exists(x)
51    assert os.path.isdir(x)
52    assert os.path.isabs(x)
53
54
55def _EscapeJsString(s):
56  assert isinstance(s, str)
57  return json.dumps(s)
58
59def _RenderTemplateStringForJsSource(source, template, replacement_string):
60  return source.replace(template, _EscapeJsString(replacement_string))
61
62
63def _GetBootStrapJsContent(source_paths):
64  assert isinstance(source_paths, list)
65  global _BOOTSTRAP_JS_CONTENT
66  if not _BOOTSTRAP_JS_CONTENT:
67    with open(_BOOTSTRAP_JS_DIR, 'r') as f:
68      _BOOTSTRAP_JS_CONTENT = f.read()
69
70  bsc = _BOOTSTRAP_JS_CONTENT
71
72
73  # Ensure that source paths are unique.
74  source_paths = list(set(source_paths))
75  source_paths_string = '[%s]' % (
76      ','.join(_EscapeJsString(s) for s in source_paths))
77  bsc = bsc.replace('<%source_paths%>', source_paths_string)
78  bsc = _RenderTemplateStringForJsSource(
79      bsc, '<%current_working_directory%>', os.getcwd())
80  bsc = _RenderTemplateStringForJsSource(
81      bsc, '<%path_utils_js_path%>', _PATH_UTILS_JS_DIR)
82  bsc = _RenderTemplateStringForJsSource(
83    bsc, '<%html_imports_loader_js_path%>', _HTML_IMPORTS_LOADER_JS_DIR)
84  bsc = _RenderTemplateStringForJsSource(
85      bsc, '<%html_to_js_generator_js_path%>', _HTML_TO_JS_GENERATOR_JS_DIR)
86  bsc = _RenderTemplateStringForJsSource(
87      bsc, '<%js_parser_path%>', _JS_PARSER_DIR)
88  bsc = _RenderTemplateStringForJsSource(
89      bsc, '<%base64_compat_path%>', _BASE64_COMPAT_DIR)
90  bsc += '\n//@ sourceURL=%s\n' % _BOOTSTRAP_JS_DIR
91  return bsc
92
93
94def _IsValidJsOrHTMLFile(parser, js_file_arg):
95  if not os.path.exists(js_file_arg):
96    parser.error('The file %s does not exist' % js_file_arg)
97  _, extension = os.path.splitext(js_file_arg)
98  if extension not in ('.js', '.html'):
99    parser.error('Input must be a JavaScript or HTML file')
100  return js_file_arg
101
102
103def _GetD8BinaryPathForPlatform():
104  if platform.system() == 'Linux' and platform.machine() == 'x86_64':
105    return os.path.join(_V8_DIR, 'linux', 'x86_64', 'd8')
106  elif platform.system() == 'Darwin' and platform.machine() == 'x86_64':
107    return os.path.join(_V8_DIR, 'mac', 'x86_64', 'd8')
108  elif platform.system() == 'Windows' and platform.machine() == 'AMD64':
109    return os.path.join(_V8_DIR, 'win', 'AMD64', 'd8.exe')
110  else:
111    raise NotImplementedError(
112        'd8 binary for this platform (%s) and architecture (%s) is not yet'
113        ' supported' % (platform.system(), platform.machine()))
114
115
116class RunResult(object):
117  def __init__(self, returncode, stdout):
118    self.returncode = returncode
119    self.stdout = stdout
120
121
122def ExecuteFile(file_path, source_paths=None, js_args=None, v8_args=None,
123                stdout=subprocess.PIPE, stdin=subprocess.PIPE):
124  """Execute JavaScript program in |file_path|.
125
126  Args:
127    file_path: string file_path that contains path the .js or .html file to be
128      executed.
129    source_paths: the list of absolute paths containing code. All the imports
130    js_args: a list of string arguments to sent to the JS program.
131
132  Args stdout & stdin are the same as _RunFileWithD8.
133
134  Returns:
135     The string output from running the JS program.
136  """
137  res = RunFile(file_path, source_paths, js_args, v8_args, stdout, stdin)
138  return res.stdout
139
140
141def RunFile(file_path, source_paths=None, js_args=None, v8_args=None,
142            stdout=subprocess.PIPE, stdin=subprocess.PIPE):
143  """Runs JavaScript program in |file_path|.
144
145  Args are same as ExecuteFile.
146
147  Returns:
148     A RunResult containing the program's output.
149  """
150  assert os.path.isfile(file_path)
151  _ValidateSourcePaths(source_paths)
152
153  _, extension = os.path.splitext(file_path)
154  if not extension in ('.html', '.js'):
155    raise ValueError('Can only execute .js or .html file. File %s has '
156                     'unsupported file type: %s' % (file_path, extension))
157  if source_paths is None:
158    source_paths = [os.path.dirname(file_path)]
159
160  abs_file_path_str = _EscapeJsString(os.path.abspath(file_path))
161
162  try:
163    temp_dir = tempfile.mkdtemp()
164    temp_boostrap_file = os.path.join(temp_dir, '_tmp_boostrap.js')
165    with open(temp_boostrap_file, 'w') as f:
166      f.write(_GetBootStrapJsContent(source_paths))
167      if extension == '.html':
168        f.write('\nHTMLImportsLoader.loadHTMLFile(%s, %s);' %
169                (abs_file_path_str, abs_file_path_str))
170      else:
171        f.write('\nHTMLImportsLoader.loadFile(%s);' % abs_file_path_str)
172    return _RunFileWithD8(temp_boostrap_file, js_args, v8_args, stdout, stdin)
173  finally:
174    shutil.rmtree(temp_dir)
175
176
177def ExecuteJsString(js_string, source_paths=None, js_args=None, v8_args=None,
178                     original_file_name=None, stdout=subprocess.PIPE,
179                     stdin=subprocess.PIPE):
180  res = RunJsString(js_string, source_paths, js_args, v8_args,
181                    original_file_name, stdout, stdin)
182  return res.stdout
183
184
185def RunJsString(js_string, source_paths=None, js_args=None, v8_args=None,
186                original_file_name=None, stdout=subprocess.PIPE,
187                stdin=subprocess.PIPE):
188  _ValidateSourcePaths(source_paths)
189
190  try:
191    temp_dir = tempfile.mkdtemp()
192    if original_file_name:
193      name = os.path.basename(original_file_name)
194      name, _ = os.path.splitext(name)
195      temp_file = os.path.join(temp_dir, '%s.js' % name)
196    else:
197      temp_file = os.path.join(temp_dir, 'temp_program.js')
198    with open(temp_file, 'w') as f:
199      f.write(js_string)
200    return RunFile(temp_file, source_paths, js_args, v8_args, stdout, stdin)
201  finally:
202    shutil.rmtree(temp_dir)
203
204
205def _RunFileWithD8(js_file_path, js_args, v8_args, stdout, stdin):
206  """ Execute the js_files with v8 engine and return the output of the program.
207
208  Args:
209    js_file_path: the string path of the js file to be run.
210    js_args: a list of arguments to passed to the |js_file_path| program.
211    v8_args: extra arguments to pass into d8. (for the full list of these
212      options, run d8 --help)
213    stdout: where to pipe the stdout of the executed program to. If
214      subprocess.PIPE is used, stdout will be returned in RunResult.out.
215      Otherwise RunResult.out is None
216    stdin: specify the executed program's input.
217  """
218  if v8_args is None:
219    v8_args = []
220  assert isinstance(v8_args, list)
221  args = [_GetD8BinaryPathForPlatform()] + v8_args
222  args.append(os.path.abspath(js_file_path))
223  full_js_args = [args[0]]
224  if js_args:
225    full_js_args += js_args
226
227  args += ['--js_arguments'] + full_js_args
228
229  # Set stderr=None since d8 doesn't write into stderr anyway.
230  sp = subprocess.Popen(args, stdout=stdout, stderr=None, stdin=stdin)
231  out, _ = sp.communicate()
232
233  # On Windows, d8's print() method add the carriage return characters \r to
234  # newline, which make the output different from d8 on posix. We remove the
235  # extra \r's  to make the output consistent with posix platforms.
236  if platform.system() == 'Windows' and out:
237    out = re.sub('\r+\n', '\n', out)
238
239  # d8 uses returncode 1 to indicate an uncaught exception, but
240  # _RunFileWithD8 needs to distingiush between that and quit(1).
241  #
242  # To fix this, d8_bootstrap.js monkeypatches D8's quit function to
243  # adds 1 to an intentioned nonzero quit. So, now, we have to undo this
244  # logic here in order to raise/return the right thing.
245  returncode = sp.returncode
246  if returncode == 0:
247    return RunResult(0, out)
248  elif returncode == 1:
249    if out:
250      raise RuntimeError(
251        'Exception raised when executing %s:\n%s' % (js_file_path, out))
252    else:
253      raise RuntimeError(
254        'Exception raised when executing %s. '
255        '(Error stack is dumped into stdout)' % js_file_path)
256  else:
257    return RunResult(returncode - 1, out)
258
259
260def main():
261  parser = argparse.ArgumentParser(
262      description='Run JavaScript file with v8 engine')
263  parser.add_argument('file_name', help='input file', metavar='FILE',
264                      type=lambda f: _IsValidJsOrHTMLFile(parser, f))
265  parser.add_argument('--js_args', help='arguments for the js program',
266                      nargs='+')
267  parser.add_argument('--source_paths', help='search path for the js program',
268                      nargs='+', type=str)
269
270  args = parser.parse_args()
271  if args.source_paths:
272    args.source_paths = [os.path.abspath(x) for x in args.source_paths]
273  else:
274    args.source_paths = [os.path.abspath(os.path.dirname(args.file_name))]
275    logging.warning(
276      '--source_paths is not specified. Use %s for search path.' %
277      args.source_paths)
278  res = RunFile(args.file_name, source_paths=args.source_paths,
279                js_args=args.js_args, stdout=sys.stdout, stdin=sys.stdin)
280  return res.returncode
281