• 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
39
40def ReadFile(filename):
41  if is_verbose:
42    print(filename)
43  with codecs.open(filename, "r", "utf-8") as f:
44    lines = f.read()
45    return lines
46
47
48TEMPLATE = """
49#include "env-inl.h"
50#include "node_native_module.h"
51#include "node_internals.h"
52
53namespace node {{
54
55namespace native_module {{
56
57{0}
58
59void NativeModuleLoader::LoadJavaScriptSource() {{
60  {1}
61}}
62
63UnionBytes NativeModuleLoader::GetConfig() {{
64  return UnionBytes(config_raw, {2});  // config.gypi
65}}
66
67}}  // namespace native_module
68
69}}  // namespace node
70"""
71
72ONE_BYTE_STRING = """
73static const uint8_t {0}[] = {{
74{1}
75}};
76"""
77
78TWO_BYTE_STRING = """
79static const uint16_t {0}[] = {{
80{1}
81}};
82"""
83
84INITIALIZER = 'source_.emplace("{0}", UnionBytes{{{1}, {2}}});'
85
86CONFIG_GYPI_ID = 'config_raw'
87
88SLUGGER_RE =re.compile('[.\-/]')
89
90is_verbose = False
91
92def GetDefinition(var, source, step=30):
93  template = ONE_BYTE_STRING
94  code_points = [ord(c) for c in source]
95  if any(c > 127 for c in code_points):
96    template = TWO_BYTE_STRING
97    # Treat non-ASCII as UTF-8 and encode as UTF-16 Little Endian.
98    encoded_source = bytearray(source, 'utf-16le')
99    code_points = [
100      encoded_source[i] + (encoded_source[i + 1] * 256)
101      for i in range(0, len(encoded_source), 2)
102    ]
103
104  # For easier debugging, align to the common 3 char for code-points.
105  elements_s = ['%3s' % x for x in code_points]
106  # Put no more then `step` code-points in a line.
107  slices = [elements_s[i:i + step] for i in range(0, len(elements_s), step)]
108  lines = [','.join(s) for s in slices]
109  array_content = ',\n'.join(lines)
110  definition = template.format(var, array_content)
111
112  return definition, len(code_points)
113
114
115def AddModule(filename, definitions, initializers):
116  code = ReadFile(filename)
117  name = NormalizeFileName(filename)
118  slug = SLUGGER_RE.sub('_', name)
119  var = slug + '_raw'
120  definition, size = GetDefinition(var, code)
121  initializer = INITIALIZER.format(name, var, size)
122  definitions.append(definition)
123  initializers.append(initializer)
124
125def NormalizeFileName(filename):
126  split = filename.split(os.path.sep)
127  if split[0] == 'deps':
128    split = ['internal'] + split
129  else:  # `lib/**/*.js` so drop the 'lib' part
130    split = split[1:]
131  if len(split):
132    filename = '/'.join(split)
133  return os.path.splitext(filename)[0]
134
135
136def JS2C(source_files, target):
137  # Build source code lines
138  definitions = []
139  initializers = []
140
141  for filename in source_files['.js']:
142    AddModule(filename, definitions, initializers)
143
144  config_def, config_size = handle_config_gypi(source_files['config.gypi'])
145  definitions.append(config_def)
146
147  # Emit result
148  definitions = ''.join(definitions)
149  initializers = '\n  '.join(initializers)
150  out = TEMPLATE.format(definitions, initializers, config_size)
151  write_if_chaged(out, target)
152
153
154def handle_config_gypi(config_filename):
155  # if its a gypi file we're going to want it as json
156  # later on anyway, so get it out of the way now
157  config = ReadFile(config_filename)
158  config = jsonify(config)
159  config_def, config_size = GetDefinition(CONFIG_GYPI_ID, config)
160  return config_def, config_size
161
162
163def jsonify(config):
164  # 1. string comments
165  config = re.sub(r'#.*?\n', '', config)
166  # 3. normalize string literals from ' into "
167  config = re.sub('\'', '"', config)
168  # 2. turn pseudo-booleans strings into Booleans
169  config = re.sub('"true"', 'true', config)
170  config = re.sub('"false"', 'false', config)
171  return config
172
173
174def write_if_chaged(content, target):
175  if os.path.exists(target):
176    with open(target, 'rt') as existing:
177      old_content = existing.read()
178  else:
179    old_content = ''
180  if old_content == content:
181    os.utime(target, None)
182    return
183  with open(target, "wt") as output:
184    output.write(content)
185
186
187def SourceFileByExt(files_by_ext, filename):
188  """
189  :type files_by_ext: dict
190  :type filename: str
191  :rtype: dict
192  """
193  ext = os.path.splitext(filename)[-1]
194  files_by_ext.setdefault(ext, []).append(filename)
195  return files_by_ext
196
197def main():
198  parser = argparse.ArgumentParser(
199    description='Convert code files into `uint16_t[]`s',
200    fromfile_prefix_chars='@'
201  )
202  parser.add_argument('--target', help='output file')
203  parser.add_argument('--verbose', action='store_true', help='output file')
204  parser.add_argument('sources', nargs='*', help='input files')
205  options = parser.parse_args()
206  global is_verbose
207  is_verbose = options.verbose
208  source_files = functools.reduce(SourceFileByExt, options.sources, {})
209  # Should have exactly 2 types: `.js`, and `.gypi`
210  assert len(source_files) == 2
211  # Currently config.gypi is the only `.gypi` file allowed
212  assert source_files['.gypi'] == ['config.gypi']
213  source_files['config.gypi'] = source_files.pop('.gypi')[0]
214  JS2C(source_files, options.target)
215
216
217if __name__ == "__main__":
218  main()
219