Добро пожаловать во вторую главу нашей захватывающей серии «Воссоздание NodeJS с нуля». В этой серии мы отправимся в путешествие, чтобы понять внутреннюю работу Node.js, популярной серверной среды выполнения JavaScript, создавая ее с нуля. В этой части мы углубимся в один из важнейших компонентов Node.js: JavaScript-движок Google V8.

Node.js во многом обязан своим успехом и производительностью мощному движку V8, который разработан Google и также служит основой Chrome. Понимание V8 и изучение того, как скомпилировать его на вашем локальном компьютере, является важным шагом в создании пользовательской установки Node.js.

В этой главе мы познакомим вас с процессом компиляции движка Google V8 в вашей локальной среде. Мы рассмотрим необходимые инструменты и конфигурации, пошаговые инструкции и ценные советы, чтобы обеспечить успешную компиляцию. К концу этой главы ваш движок V8 будет готов для поддержки вашей пользовательской реализации Node.js.

Независимо от того, являетесь ли вы любознательным разработчиком, желающим изучить внутреннее устройство Node.js, или начинающим участником проекта Node.js, эта глава предоставит вам необходимые знания для начала работы. Итак, давайте засучим рукава и отправимся в это захватывающее приключение по созданию и пониманию Node.js с нуля!

V8 привет мир

Вероятно, некоторые из вас уже знают, что такое V8 и как он работает за кулисами. Однако, когда я начал глубоко погружаться в эту тему, я столкнулся со многими проблемами при запуске «простой» программы hello world. Для меня это было не так просто, но все же невероятно полезно. Итак, чтобы помочь таким, как я, давайте переименуем нашу первую тему и назовем ее:

Почему я должен собирать V8 и как это сделать?

Движок V8 изначально был разработан для работы в Google Chrome (проект Chromium). Как вы, возможно, знаете, он должен быть совместим с различными платформами, такими как Android, iOS, и различными архитектурами процессоров, такими как arm, arm64, RISC, MIPS, X64 и т. д. Поскольку движок постоянно развивается, поддержка отдельных репозиториев с скомпилированными изображениями для каждая архитектура непрактична. Вместо этого проект V8 предоставляет сценарии и инструменты, которые позволяют разработчикам создавать исходный код для каждой конкретной архитектуры.

Мы уже ответили «почему», теперь давайте объясним «как».

Прежде всего, чтобы скомпилировать исходный код, нам нужно его получить. Официальный репозиторий размещен на собственной платформе Google Git, но для общего доступа он дублируется на GitHub прямо здесь, Но эй, подождите секунду! Вы не знаете. не нужно просто клонировать его, как обычно. У Google всегда есть свой уникальный способ делать вещи. Итак, давайте подробнее рассмотрим то, что называется depot_tools.

Как это сделать? — депо_инструменты

depot_tools — это набор инструментов и скриптов, разработанных Google, которые в основном используются в процессе разработки Chromium. Эти инструменты предназначены для помощи разработчикам в работе с большими репозиториями кода, такими как Chromium и V8 (движок JavaScript с открытым исходным кодом от Google, используемый в Chrome).

  • Оболочка Git: Depot_tools включает настраиваемую оболочку Git, упрощающую работу с репозиторием Chromium и его зависимостями.
  • gclient — это скрипт, помогающий управлять исходным кодом и зависимостями для крупных проектов, таких как Chromium. Он используется для синхронизации и управления несколькими репозиториями Git, составляющими полный проект.
  • ninja – это небольшая программа для сборки, ориентированная на скорость.
  • fetch – это удобный инструмент для загрузки полного проекта Chromium и всех его подпроектов.
  • gn — генератор метакомпилятора для Ninja. GN может создавать проекты для IDE и текстовых редакторов.
  • и многое другое, но нам нужно знать об этом сейчас.

Давайте установим его

  • Создайте новую папку (с именем chapter-2), где мы будем хранить все необходимое для этой главы.
  • инструменты хранилища клонов, выполнив следующую команду
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  • добавить инструменты хранилища в переменную окружения PATH (чтобы не передавать полный путь каждый раз при использовании инструментов).
export PATH=`pwd`/depot_tools:$PATH
  • давайте использовать определенную версию инструментов хранилища (это очень важно, потому что постоянные изменения в репозиториях могут нарушить некоторые совместимости)
cd depot_tools 
git checkout 787e71ac
cd .. 
  • обновить инструменты депо, запустив (может занять некоторое время)
gclient

Имея инструменты хранилища на нашей машине и синхронизируя их, мы теперь можем загрузить код V8.

Как это сделать? — Получение исходного кода v8

Давайте создадим новую папку с именем v8 и будем перемещаться внутри нее.

mkdir v8 && cd v8

теперь клонируйте проект v8, выполнив следующую команду (снова может занять некоторое время)

fetch v8

после этого у нас должно быть что-то вроде этого в нашем каталоге

chapter-2/
    ├── depot_tools/
    │   └── ... many depot_tools files
    │
    └── v8/
        └── v8/
            └── ... many v8 files

Теперь воспользуемся версией, соответствующей версии depot_tools.

cd v8
git checkout 4ec5bb4f26

Теперь мы можем начать сборку исходного кода

Как это сделать? — Компиляция v8

Чтобы начать процесс сборки V8, мы должны сгенерировать файлы конфигурации для нашей конкретной архитектуры. Я продемонстрирую это для процессора x64. Сначала подтвердите свой, используя следующую команду:

uname -m 

Вы увидите вывод, который зависит от вашего процессора:

x86 or x86_64 (64-bit):
Output: x86_64

x86 (32-bit):
Output: i686

ARM (32-bit):
Output: armv7l

ARM (64-bit):
Output: aarch64

Кроме того, мы можем вывести список доступных конфигураций сборки, выполнив следующую команду

tools/dev/v8gen.py list      
                                   
// output:
android.arm.debug
android.arm.optdebug
android.arm.release
arm.debug
arm.optdebug
arm.release
arm64.debug
arm64.optdebug
arm64.release
arm64.release.sample
ia32.debug
ia32.optdebug
ia32.release
mips64el.debug
mips64el.optdebug
mips64el.release
ppc64.debug
ppc64.debug.sim
ppc64.optdebug
ppc64.optdebug.sim
ppc64.release
ppc64.release.sim
riscv64.debug
riscv64.debug.sim
riscv64.optdebug
riscv64.optdebug.sim
riscv64.release
riscv64.release.sim
s390x.debug
s390x.debug.sim
s390x.optdebug
s390x.optdebug.sim
s390x.release
s390x.release.sim
x64.debug
x64.optdebug
x64.release
x64.release.sample

Теперь нам нужно сгенерировать файлы конфигурации для его сборки.

tools/dev/v8gen.py x64.release.sample

Конфигурация сборки «x64.release.sample» — это пользовательский или экспериментальный вариант сборки, созданный для конкретных целей тестирования или демонстрации. Он может включать образцы кода, функции или конфигурации, не входящие в стандартную производственную версию. Он используется в качестве демонстрационной сборки для демонстрации определенных возможностей V8 или в качестве тестовой сборки для опробования экспериментальных функций.

После выполнения приведенной ниже команды будет создан новый каталог с именем out.gn/x64.release.sample. Внутри этого каталога вы найдете файл с именем args.gn, который используется для объявления флагов процесса сборки. Мы должны удалить запись флага для v8_enable_sandbox внутри args.gn.

Пожалуйста, запустите команду, чтобы создать каталог, а затем удалите запись v8_enable_sandboxflag из файла args.gn.

Песочница — это механизм безопасности, который ограничивает выполнение кода в контролируемой среде, чтобы предотвратить доступ к конфиденциальным ресурсам или выполнение потенциально опасных действий. В контексте V8 включение функции песочницы добавляет дополнительный уровень безопасности при выполнении кода JavaScript.

мы можем удалить его непосредственно из файла или выполнив следующую команду

sed -ie '/v8_enable_sandbox/d' out.gn/x64.release.sample/args.gn

Теперь мы готовы построить V8. Пожалуйста, выполните следующую команду (процесс может занять около 20 минут)

ninja -C out.gn/x64.release.sample v8_monolith

Вот что делает каждая часть команды:

  • ninja: Это вызывает систему сборки Ninja, которая отвечает за эффективную сборку исходного кода V8. Ninja — популярная система сборки, предназначенная для быстрой и масштабируемой.
  • -C out.gn/x64.release.sample: этот флаг указывает каталог сборки, в котором будут сгенерированы выходные данные сборки. В данном случае установлено значение out.gn/x64.release.sample.
  • v8_monolith: Это цель, которую построит Ниндзя. Термин «монолит» указывает на то, что V8 будет собираться как один большой двоичный файл (монолитная сборка) вместо множества более мелких компонентов. Монолитная сборка включает все функции и компоненты V8 в одном исполняемом файле, что может быть полезно в определенных сценариях.

При выполнении этой команды движок V8 будет скомпилирован и связан для создания монолитной сборки в указанном выходном каталоге. Как только процесс сборки будет завершен, у вас будет движок V8, готовый к использованию в качестве одного двоичного файла в каталоге out.gn/x64.release.sample.

После долгого ожидания мы, наконец, собрали нашу библиотеку V8. Теперь пришло время использовать и протестировать его самым простым способом. После этого пришло время:

V8 привет мир

Наконец, для запуска примера hello world (находится в v8/samples/hello-world.cc) нам нужно скомпилировать hello world и скомпоновать библиотеки v8, это возможно, выполнив следующую команду

g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith -lv8_libbase -lv8_libplatform -ldl -Lout.gn/x64.release.sample/obj/ -pthread -std=c++17 -DV8_COMPRESS_POINTERS

Вот что делает каждая часть команды:

  • g++: Это коллекция компиляторов GNU для C++.
  • -I.: этот флаг указывает на включение текущего каталога (.) в путь поиска файлов заголовков.
  • -Iinclude: Этот флаг включает каталог 'include' в путь поиска файлов заголовков.
  • samples/hello-world.cc: Это файл исходного кода, который нужно скомпилировать и связать.
  • -o hello_world: Этот флаг указывает имя выходного файла. В этом случае выходной исполняемый файл будет называться «hello_world».
  • -fno-rtti: этот флаг отключает информацию о типе времени выполнения (RTTI) в компиляции. RTTI используется для динамической идентификации типов, и его отключение может уменьшить размер двоичного файла и повысить производительность.
  • -lv8_monolith: Это связывает монолитную библиотеку движка V8, которая включает все функции и компоненты V8 в одном бинарном файле.
  • -lv8_libbase: Это ссылка на базовую библиотеку V8, которая предоставляет основные утилиты и структуры данных.
  • -lv8_libplatform: Это связывает библиотеку платформы V8, которая занимается потоковой передачей, синхронизацией и файловым вводом-выводом.
  • -ldl: Связывает библиотеку динамической компоновки, необходимую для работы с динамически загружаемыми библиотеками.
  • -Lout.gn/x64.release.sample/obj/: указывает путь, по которому компоновщик должен искать файлы библиотеки V8.
  • -pthread: этот флаг включает поддержку многопоточности с использованием библиотеки потоков POSIX.
  • -std=c++17: этот флаг устанавливает стандарт языка C++ на C++17.
  • -DV8_COMPRESS_POINTERS: определяет символ препроцессора V8_COMPRESS_POINTERS, который позволяет движку V8 использовать сжатие указателя для уменьшения использования памяти. Это необязательный флаг, который может присутствовать не во всех конфигурациях сборки.

При выполнении этой команды файл «hello-world.cc» будет скомпилирован и связан с библиотекой V8, в результате чего будет создан исполняемый файл «hello_world» с указанными конфигурациями.

Теперь ЗАПУСКАТЬ

./hello_world

Результат должен быть

Hello, World!
3 + 4 = 7

Заключение

Вы можете просмотреть код «hello-world.cc», чтобы понять, что там происходит. Я пытался несколько раз, но мне нужно принимать это медленно и спокойно. Я планирую подробно объяснить это в следующей главе. На данный момент я рад, что мы успешно построили V8 (это, несомненно, сложная задача) и выполнили с его помощью новое приложение C++.

Отличная работа!

Кроме того

При написании этой статьи я провел интригующее исследование, и одним из самых ценных источников, который я нашел, была коллекция ресурсов, предоставленная Эриком Венделем.