tsy77 / blog

78 stars 2 forks source link

Node.js源码-node_javascript.cc #8

Open tsy77 opened 6 years ago

tsy77 commented 6 years ago

上一篇讲node运行的文章中,我们提到了LoadEnvironment的LoadersBootstrapperSource方法,其从node_javascript.cc中获取loader文件内容的ascII码,node_javascript.cc在out/Debug/gen中,它是如何产生的呢?

本篇文章就是来介绍node_javascript.cc是如何产生的以及其中的内容。

node_js2c

下面是node.gyp定义的node_js2c目标:

{
      'target_name': 'node_js2c',
      'type': 'none',
      'toolsets': ['host'],
      'actions': [
        {
          'action_name': 'node_js2c',
          'process_outputs_as_sources': 1,
          'inputs': [
            '<@(library_files)',
            './config.gypi',
            'tools/check_macros.py'
          ],
          'outputs': [
            '<(SHARED_INTERMEDIATE_DIR)/node_javascript.cc',
          ],
          'conditions': [
            [ 'node_use_dtrace=="false" and node_use_etw=="false"', {
              'inputs': [ 'src/notrace_macros.py' ]
            }],
            [ 'node_use_perfctr=="false"', {
              'inputs': [ 'src/noperfctr_macros.py' ]
            }],
            [ 'node_debug_lib=="false"', {
              'inputs': [ 'tools/nodcheck_macros.py' ]
            }],
            [ 'node_debug_lib=="true"', {
              'inputs': [ 'tools/dcheck_macros.py' ]
            }]
          ],
          'action': [
            'python',
            'tools/js2c.py',
            '<@(_outputs)',
            '<@(_inputs)',
          ],
        },
      ],
    }

inputs

我们可以看到inputs中主要有三个输入,library_files、./config.gypi、tools/check_macros.py。

library_files

其中library_files包含如下文件:

'library_files': [
      'lib/internal/bootstrap/loaders.js',
      'lib/internal/bootstrap/node.js',
      'lib/async_hooks.js',
      'lib/assert.js',
      'lib/buffer.js',

      ......

      'deps/node-inspect/lib/internal/inspect_client.js',
      'deps/node-inspect/lib/internal/inspect_repl.js',
      'deps/acorn/dist/acorn.js',
      'deps/acorn/dist/walk.js',
    ],

基本上是lib和dep中的一些.js文件。

./config.gypi

./config.gypi主要定义了一些target_defaults(作用域.gyp文件中所有的targets)和一些变量。

tools/check_macros.py

宏定义:

macro CHECK(x) = do { if (!(x)) (process._rawDebug("CHECK: x == true"), process.abort()) } while (0);
macro CHECK_EQ(a, b) = CHECK((a) === (b));
macro CHECK_GE(a, b) = CHECK((a) >= (b));
macro CHECK_GT(a, b) = CHECK((a) > (b));
macro CHECK_LE(a, b) = CHECK((a) <= (b));
macro CHECK_LT(a, b) = CHECK((a) < (b));
macro CHECK_NE(a, b) = CHECK((a) !== (b));

outputs

outputs很简单,在debug模式下就是out/Debug/node_javascript.cc。

action

'action': [
            'python',
            'tools/js2c.py',
            '<@(_outputs)',
            '<@(_inputs)',
          ]

翻译成指令就是:

python tools/js2c.py $(outputs) $(inputs)

js2c.py

下面我们来看下js2c.py里做了什么?

def main():
  natives = sys.argv[1]
  source_files = sys.argv[2:]
  JS2C(source_files, [natives])

调用了JS2C,并将inputs中的所有文件路径作为参数传进去。

我们来看下JS2C:

def JS2C(source, target):
  modules = []
  consts = {}
  macros = {}
  macro_lines = []

  for s in source:
    if (os.path.split(str(s))[1]).endswith('macros.py'):
      macro_lines.extend(ReadLines(str(s)))
    else:
      modules.append(s)

  # Process input from all *macro.py files
  // 拿到宏定义
  (consts, macros) = ReadMacros(macro_lines)

  # Build source code lines
  definitions = []
  initializers = []

  for name in modules:
    lines = ReadFile(str(name))
    // 替换宏定义
    lines = ExpandConstants(lines, consts)
    lines = ExpandMacros(lines, macros)

    deprecated_deps = None

    # On Windows, "./foo.bar" in the .gyp file is passed as "foo.bar"
    # so don't assume there is always a slash in the file path.
    if '/' in name or '\\' in name:
      split = re.split('/|\\\\', name)
      if split[0] == 'deps':
        if split[1] == 'node-inspect' or split[1] == 'v8':
          deprecated_deps = split[1:]
        split = ['internal'] + split
      else:
        split = split[1:]
      name = '/'.join(split)

    # if its a gypi file we're going to want it as json
    # later on anyway, so get it out of the way now
    if name.endswith(".gypi"):
      lines = re.sub(r'#.*?\n', '', lines)
      lines = re.sub(r'\'', '"', lines)
    name = name.split('.', 1)[0]
    var = name.replace('-', '_').replace('/', '_')
    key = '%s_key' % var
    value = '%s_value' % var

    definitions.append(Render(key, name))
    definitions.append(Render(value, lines))
    initializers.append(INITIALIZER.format(key=key, value=value))

    if deprecated_deps is not None:
      name = '/'.join(deprecated_deps)
      name = name.split('.', 1)[0]
      var = name.replace('-', '_').replace('/', '_')
      key = '%s_key' % var
      value = '%s_value' % var

      definitions.append(Render(key, name))
      definitions.append(Render(value, DEPRECATED_DEPS.format(module=name)))
      initializers.append(INITIALIZER.format(key=key, value=value))

  # Emit result
  output = open(str(target[0]), "w")
  output.write(TEMPLATE.format(definitions=''.join(definitions),
                               initializers=''.join(initializers)))
  output.close()

这里一共做了如下几件事:

1.拿到宏定义
2.循环遍历文件
    ·宏替换
    ·获得所有定义的字符串代码
    ·获得所有初始化的字符串代码
    ·字符串替换

render

def Render(var, data):
  # Treat non-ASCII as UTF-8 and convert it to UTF-16.
  if any(ord(c) > 127 for c in data):
    template = TWO_BYTE_STRING
    data = map(ord, data.decode('utf-8').encode('utf-16be'))
    data = [data[i] * 256 + data[i+1] for i in xrange(0, len(data), 2)]
    data = ToCArray(data)
  else:
    template = ONE_BYTE_STRING
    data = ToCString(data)
  return template.format(var=var, data=data)

判断文件中字符的ascII码是否超过127,超过的字符被转成UTF-16。

node_javascript.cc

node_javascript.cc主要有以下几部分组成:

1.各个模块key、value对应的结构体的定义

static const uint8_t raw_internal_bootstrap_loaders_key[] = { 105,110,116,101,114,110,97,108,47,98,111,111,116,115,116,114,97,112,47,108,
111,97,100,101,114,115 };
static struct : public v8::String::ExternalOneByteStringResource {
  const char* data() const override {
    return reinterpret_cast<const char*>(raw_internal_bootstrap_loaders_key);
  }
  size_t length() const override { return arraysize(raw_internal_bootstrap_loaders_key); }
  void Dispose() override { /* Default calls `delete this`. */ }
  v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {
    return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
  }
} internal_bootstrap_loaders_key;

static const uint8_t raw_internal_bootstrap_loaders_value[] = { 47,47,32,84,104,105,115,32,102,105,108,101,32,99,114,101,97,116,101,115,
32,116,104,101,32,105,110,116,101,114,110,97,108,32,109,111,100,117,108,101,
32,38,32,98,105,110,100,105,110,103,32,108,111,97,100,101,114,115,32,117,
 };
static struct : public v8::String::ExternalOneByteStringResource {
  const char* data() const override {
    return reinterpret_cast<const char*>(raw_internal_bootstrap_loaders_value);
  }
  size_t length() const override { return arraysize(raw_internal_bootstrap_loaders_value); }
  void Dispose() override { /* Default calls `delete this`. */ }
  v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {
    return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
  }
} internal_bootstrap_loaders_value;

我们可以看到数组和两个struct,其中raw_internal_bootstrap_loaders_key和raw_internal_bootstrap_loaders_value分别记录bootstrap_loaders的key和value(文件内容),两个结构体internal_bootstrap_loaders_key和internal_bootstrap_loaders_value均有方法ToStringChecked,而ToStringChecked其实会去找data()方法,也就是说internal_bootstrap_loaders_value.ToStringChecked()便会返回对应的ascII码。

2.初始化函数定义(initializers)

void DefineJavaScript(Environment* env, v8::Local<v8::Object> target) {
  CHECK(target->Set(env->context(),
                  internal_bootstrap_loaders_key.ToStringChecked(env->isolate()),
                  internal_bootstrap_loaders_value.ToStringChecked(env->isolate())).FromJust());

这里主要是将各个模块的key、value挂载在exports对象中,可以在.cpp或者.js中取得文件内容进行执行等操作。

总结

本文主要介绍node_javascript.cc的产生和内容,这其实也是node中获取native模块最关键的地方。到此为止,已经介绍了node中builtin和native模块的由来,大家也可以和上一篇文章的中所提到的getBinding串起来了。