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