• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2006-2008 the V8 project authors. All rights reserved.
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11#       copyright notice, this list of conditions and the following
12#       disclaimer in the documentation and/or other materials provided
13#       with the distribution.
14#     * Neither the name of Google Inc. nor the names of its
15#       contributors may be used to endorse or promote products derived
16#       from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30"""
31This is a utility for converting JavaScript source code into uint16_t[],
32that are used for embedding JavaScript code into the Node.js binary.
33"""
34import argparse
35import os
36import re
37import functools
38import codecs
39import utils
40
41def ReadFile(filename):
42  if is_verbose:
43    print(filename)
44  with codecs.open(filename, "r", "utf-8") as f:
45    lines = f.read()
46    return lines
47
48
49TEMPLATE = """
50#include "env-inl.h"
51#include "node_native_module.h"
52#include "node_internals.h"
53
54namespace node {{
55
56namespace native_module {{
57
58{0}
59
60void NativeModuleLoader::LoadJavaScriptSource() {{
61  {1}
62}}
63
64UnionBytes NativeModuleLoader::GetConfig() {{
65  return UnionBytes(config_raw, {2});  // config.gypi
66}}
67
68}}  // namespace native_module
69
70}}  // namespace node
71"""
72
73ONE_BYTE_STRING = """
74static const uint8_t {0}[] = {{
75{1}
76}};
77"""
78
79TWO_BYTE_STRING = """
80static const uint16_t {0}[] = {{
81{1}
82}};
83"""
84
85INITIALIZER = 'source_.emplace("{0}", UnionBytes{{{1}, {2}}});'
86
87CONFIG_GYPI_ID = 'config_raw'
88
89SLUGGER_RE =re.compile('[.\-/]')
90
91is_verbose = False
92
93def GetDefinition(var, source, step=30):
94  template = ONE_BYTE_STRING
95  code_points = [ord(c) for c in source]
96  if any(c > 127 for c in code_points):
97    template = TWO_BYTE_STRING
98    # Treat non-ASCII as UTF-8 and encode as UTF-16 Little Endian.
99    encoded_source = bytearray(source, 'utf-16le')
100    code_points = [
101      encoded_source[i] + (encoded_source[i + 1] * 256)
102      for i in range(0, len(encoded_source), 2)
103    ]
104
105  # For easier debugging, align to the common 3 char for code-points.
106  elements_s = ['%3s' % x for x in code_points]
107  # Put no more then `step` code-points in a line.
108  slices = [elements_s[i:i + step] for i in range(0, len(elements_s), step)]
109  lines = [','.join(s) for s in slices]
110  array_content = ',\n'.join(lines)
111  definition = template.format(var, array_content)
112
113  return definition, len(code_points)
114
115
116def AddModule(filename, definitions, initializers):
117  code = ReadFile(filename)
118  name = NormalizeFileName(filename)
119  slug = SLUGGER_RE.sub('_', name)
120  var = slug + '_raw'
121  definition, size = GetDefinition(var, code)
122  initializer = INITIALIZER.format(name, var, size)
123  definitions.append(definition)
124  initializers.append(initializer)
125
126def NormalizeFileName(filename):
127  split = filename.split('/')
128  if split[0] == 'deps':
129    split = ['internal'] + split
130  else:  # `lib/**/*.js` so drop the 'lib' part
131    split = split[1:]
132  if len(split):
133    filename = '/'.join(split)
134  return os.path.splitext(filename)[0]
135
136
137def JS2C(source_files, target):
138  # Build source code lines
139  definitions = []
140  initializers = []
141
142  for filename in source_files['.js']:
143    AddModule(filename, definitions, initializers)
144
145  config_def, config_size = handle_config_gypi(source_files['config.gypi'])
146  definitions.append(config_def)
147
148  # Emit result
149  definitions = ''.join(definitions)
150  initializers = '\n  '.join(initializers)
151  out = TEMPLATE.format(definitions, initializers, config_size)
152  write_if_chaged(out, target)
153
154
155def handle_config_gypi(config_filename):
156  # if its a gypi file we're going to want it as json
157  # later on anyway, so get it out of the way now
158  config = ReadFile(config_filename)
159  config = jsonify(config)
160  config_def, config_size = GetDefinition(CONFIG_GYPI_ID, config)
161  return config_def, config_size
162
163
164def jsonify(config):
165  # 1. string comments
166  config = re.sub(r'#.*?\n', '', config)
167  # 2. join multiline strings
168  config = re.sub(r"'$\s+'", '', config, flags=re.M)
169  # 3. normalize string literals from ' into "
170  config = re.sub('\'', '"', config)
171  # 4. turn pseudo-booleans strings into Booleans
172  config = re.sub('"true"', 'true', config)
173  config = re.sub('"false"', 'false', config)
174  return config
175
176
177def write_if_chaged(content, target):
178  if os.path.exists(target):
179    with open(target, 'rt') as existing:
180      old_content = existing.read()
181  else:
182    old_content = ''
183  if old_content == content:
184    os.utime(target, None)
185    return
186  with open(target, "wt") as output:
187    output.write(content)
188
189
190def SourceFileByExt(files_by_ext, filename):
191  """
192  :type files_by_ext: dict
193  :type filename: str
194  :rtype: dict
195  """
196  ext = os.path.splitext(filename)[-1]
197  files_by_ext.setdefault(ext, []).append(filename)
198  return files_by_ext
199
200def main():
201  parser = argparse.ArgumentParser(
202    description='Convert code files into `uint16_t[]`s',
203    fromfile_prefix_chars='@'
204  )
205  parser.add_argument('--target', help='output file')
206  parser.add_argument(
207      '--directory',
208      default=None,
209      help='input file directory')
210  parser.add_argument('--verbose', action='store_true', help='output file')
211  parser.add_argument('sources', nargs='*', help='input files')
212  options = parser.parse_args()
213  global is_verbose
214  is_verbose = options.verbose
215  sources = options.sources
216  if options.directory is not None:
217    js_files = utils.SearchFiles(options.directory, 'js')
218    mjs_files = utils.SearchFiles(options.directory, 'mjs')
219    sources = js_files + mjs_files + options.sources
220
221  source_files = functools.reduce(SourceFileByExt, sources, {})
222
223  # Should have exactly 2 types: `.js`, and `.gypi`
224  assert len(source_files) == 2
225  # Currently config.gypi is the only `.gypi` file allowed
226  assert source_files['.gypi'] == ['config.gypi']
227  source_files['config.gypi'] = source_files.pop('.gypi')[0]
228  JS2C(source_files, options.target)
229
230
231if __name__ == "__main__":
232  main()
233