Ninja linux ��� ���

Ninja

Оригинал: Ninja
Автор: Evan Martin
Перевод: А.Панин

Архитектура системы Ninja

На высоком уровне любая сборочная система выполняет три основных задачи. Она загружает директивы сборки (1) и анализирует ее цели, (2) устанавливает то, какие шаги необходимы для достижения этих целей и (3) выполняет эти шаги.

Для ускорения шага запуска (1) система Ninja должна выполнять минимальный объем работы в процессе загрузки файлов сборок. Системы сборки проектов обычно используются людьми, поэтому в них должен применяться удобный, высокоуровневый синтаксис для описания целей сборок. Это также означает, что непосредственно в момент начала сборки проекта сборочная система должна дополнительно обработать инструкции: например, в какой-то момент система сборки Visual Studio должна на основании конфигурации системы сборки четко определить место размещения выходных файлов или то, какие файлы должны быть скомпилированы с помощью компилятора языка C++, а какие — с помощью компилятора языка C.

В связи с этим, работа GYP по генерации файлов сборки Visual Studio была четко ограничена преобразованием списков файлов исходного кода в списки файлов с использованием синтаксиса Visual Studio, с последующим выполнением остальной части работы средствами Visual Studio. При работе над Ninja я столкнулся с возможностью переноса максимального объема работы на систему GYP. Я имею в виду однократное выполнение всех описанных выше операций в ходе генерации файлов сборки Ninja средствами GYP. После этого у GYP должна была быть возможность сохранения снимка данных промежуточного состояния в формате, который Ninja может быстро загрузить в ходе каждой из последующих сборок.

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

Такая минималистичная архитектура, как это не парадоксально, повышает гибкость инструмента. Из-за того, что система Ninja не имеет представления о таких стандартных высокоуровневых концепциях сборки, как выходная директория или текущая конфигурация, ее можно без лишних сложностей встроить в более сложные системы сборки (например, CMake, как мы увидим позднее), в которых сборочное окружение организуется по-другому. Например, система Ninja не устанавливает четкого ограничения того, должны ли результирующие файлы сборки (т.е., объектные файлы) размещаться в одной директории с файлами исходного кода (что считается плохим, приводящим к неразберихе методом размещения файлов некоторыми разработчиками) или в отдельной директории для результирующих файлов сборки (что считается затрудняющим понимание методом размещения файлов другими разработчиками). Спустя долгие годы после выпуска первой версии системы Ninja я наконец придумал правильную метафору для ее описания: если считать все другие системы сборки проектов компиляторами, то Ninja будет ассемблером.

Какую работу выполняет Ninja

Если система Ninja оставляет большую часть работы генератору файлов сборки, то какую работу остается выполнить ее силами? Описанная идеология хороша в принципе, но реальный мир всегда гораздо сложнее. Система Ninja развивалась, приобретая (и теряя) возможности в процессе разработки. В каждый момент времени мы задавались важным вопросом: «Можем ли мы выполнить меньший объем работы?» Ниже приведено краткое описание принципа работы системы.

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

Правила ведут себя практически аналогично функциям, а переменные — аргументам. Две этих простых возможности находятся в опасной близости от возможностей языка программирования — что не соответствует цели «избавления от дополнительной работы». При этом они имеют важное достоинство, заключающееся в возможности сокращения повторений строк, что полезно не только для людей, но и для компьютеров, так как сокращается объем текста, который необходимо разобрать.

Файл сборки после разбора представляет граф зависимостей: результирующий выходной двоичный файл создается в результате связывания множества объектных файлов, каждый из которых, в свою очередь, создается в результате компиляции файла исходного кода. В нашем случае мы имеем дело с двудольным графом, в котором «узлы» (входные файлы) связаны с «ребрами» (командами сборки), которые в свою очередь связаны с узлами (результирующими файлами) 6 . Впоследствии в процессе сборки производится обход данного графа.

После задания целевого файла Ninja в первую очередь обходит граф для установления параметров состояния каждого из файлов исходного кода, представленного вершиной: существует ли данный файл и какова метка времени его модификации. После этого Ninja создает план (plan). План является набором ребер, причем каждое представленное ребром правило должно быть исполнено для обновления результирующего файла в соответствии с метками времени модификации промежуточных файлов. Наконец, после выполнения плана происходит обратный обход графа с целью проверки выполнения и корректного завершения представленных ребрами правил.

Читайте также:  Какая windows быстрее 10 home или windows 10 pro

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

Источник

Ninja OS

Ninja OS Dev Team

Ninja OS Dev Team

Ninja OS Dev Team

Ninja OS Dev Team

Ninja OS Dev Team

Ninja OS Dev Team

Ninja OS Dev Team

Ninja OS Dev Team

When we started with what would ultimately become Ninja OS in early 2010, Live OSs where the latest hotness. Insperation came from previous generations of Live CD distributions. Mostly early versions of TAILs that left users wanting as well as AnonymOS, and other live Desktop OSes generally that got burned to Optical disks such as CD and DVD. Initially, we did in fact provide an ISO9660 image for use with what is now legacy technology.

In the modern world, the Live OS has lost its lustre as well. Increasingly people rely on virtual machines, and even distributions like Qubes which is a management interface for virtual machines. The Live USB stick is not completely worthless, and it will be kept.

As a concept, Ninja OS will move forward as a family of operating systems, and tooling. They will all use the same Arch install base and similar and/or complementory packages(such as client and server).

Conceptualized Components:

  • [ninjaos] — Arch Linux Repo
  • Ninja LiveUSB
  • Ninja VM Desktop
  • Ninja remote VDI
  • Ninja Cloud Server Template

i686 support being dropped

Ninja OS Dev Team

Its a long time comming, but we are discontinuing the last of i686 support, effectively immediately. i686 has been discontinued from upstream Arch Linux for some time, and we have been following the Archlinux32 project. The last two releases did not see an i686 release as the quality would be signifigantly less and some major packages like firefox-esr do not build at all. A direct port is no longer possible. a proper fork would require a lot of effort, for very little gain.

In addition there are errors like Archlinx32 not fully repackaging their perl packages for new versions, tripping errors. Many upstream repos and projects do NOT test for 32 bit x86 on linux anymore.

More and more PKGBUILDs start to fail.

Today, we hit OOM errors attempting to build packages, even with enough swap space installed, and the memory limit of the arch kernel with 4GB of ram is not enough to maintain this repo. To rememedy this, we would need to maintain a seperate kernel with PAE enabled, just for the build environment. While configuring a kernel is trivial, maintaining one is not. Especially when you maintain a seperate kernel for production.

All for less and less people actually using this obsolete archecture. We feel that 64-bit PC penetration is sufficient to make the change.

In the future, if Archlinux32 improves and we are able to make a full compile, we MIGHT, repeat MIGHT release a pentium4 fork. Pentium4 has the added benefit of SSE2 instructions, that might make this feasible to run on more modern 32-bit machines.

Adding cross compile tools and updates

Ninja OS Dev Team

We are adding some cross compile tools to the [ninjaos] repository. apple-darwin-osxcross for compiling darwin/OSX binaries, and mingw-w64 gcc toolchain for compiling Windows binaries.

These will definately be available for x86_64, but we are having trouble with i686. Issues like this are the reason we no longer support i686. Archlinux32 is not supported as well as Arch Linux propper. It is also considerably(weeks) behind upstream, as in that its not possible to match patch levels.

We cannot maintain i686 forever. It is starting to get a little long in the tooth

Contents В© 2021 Ninja OS Dev Team — Powered by Nikola Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)

Источник

Build Your C++ Projects Faster Using Ninja

Sep 20, 2019 · 3 min read

In this article, I introduce you to an open-source build system that can help you speed up the compiling time when building C++ projects.

Ninja is a lightweight build system, designed to take advantage of the available CPU cores in your machine, to build executable files and libraries from source code efficiently. The way it works is very similar to GNU Make. That is it takes input files generated by a high-level build system like CMake and turns them into libraries and executables as fast as possible.

The following definition is taken from the official website:

Ninja is a small build system with a focus on speed. It differs from other build systems in two major respects: it is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible.

Install

You c an install Ninja using package managers:

Читайте также:  Монтирование сетевого диск linux

Linux

  • Debian/Ubuntu: apt-get install ninja-build
  • Arch: pacman -S ninja
  • Fedora: dnf install ninja-build
  • HomeBrew: brew install ninja
  • MacPorts: port install ninja

Or, build the source code by downloading one of the releases from the Github repository. The latest release 1.9.0 contains ready-to-use precompiled binaries for 64-bit Linux, Mac OS X, and Windows.

Hello World!

The difference between Ninja and other build systems is noticeable when building large projects. However, let’s see in the following demonstration how Ninja works with CMake. Let’s create this simple HelloWorld program:

After creating the source code, we go now create a CMakeLists.txt file for it. Let’s give our project the name “HelloWorld”, and set the C++ version to 14. The CMakeLists.txt would look like this:

To generate Ninja build files, we need to run CMake with the flag -G Ninja . Run the following command in the terminal:

CMake will generate its own cache/settings files, build.ninja and rules.ninja . This image shows the output files:

After generating the needed configurations, we can start the build of the project by simply typing ninja :

You should see an executable named HelloWorld created in the directory, as shown in this image:

Incremental build

Next time we modify the code, we don’t have to run the command for CMake. All that we need is to type ninja again and a new build is triggered.

Moreover, Ninja uses the incremental build model, that is in projects with multiple source files only the modified files are rebuilt. It doesn’t rebuild things that are already up to date. This optimization greatly decreases the build time.

Liked the story?

Put a clap, follow me on Medium and Twitter, and try Ninja when you have time. Feedback is always welcome!

About me

I am Ilyas Hamadouche, a Senior Software Engineer at Elektrobit Automotive. I am interested in automotive software and embedded systems. Follow me on Twitter and LinkedIn.

Источник

Как сделать из Ninja систему распределённой сборки?

Привет, Хабр!

Недавно я задумался, ковыряя очередную бесплатную систему сборки, «А нельзя ли взять и самому написать такую систему? Ведь это просто — взять ту же Ninja, прикрутить разделение на препроцессинг и компиляцию, да и передавать по сети файлы туда-сюда. Куда уж проще?»

Просто — не просто, как самому сделать подобную систему — расскажу под катом.

Этап 0. Формулировка задачи

Disclaimer: Статья отмечена как tutorial, но это не совсем пошаговое руководство, скопипастив код из которого получится готовый продукт. Это скорее инструкция — как спланировать и куда копать.

Сперва определимся, какой общий алгоритм работы должен получиться:

  • Читаем граф сборки, вычленяем команды компиляции;
  • Разбиваем компиляцию на два этапа, препроцессинг и собственно генерацию кода. Последнюю помечаем как возможную к удаленному выполнению;
  • Выполняем препроцессинг, считываем результат в память;
  • Отправляем препроцессированный файл и команду на генерацию кода на другой хост по сети;
  • Выполняем команду кодогенерации, считываем объектный файл и отдаем в качестве ответа по сети;
  • Полученный объектный файл сохраняем на диск и выводим в консоль сообщения компилятора.

Вроде не так и страшно, верно? Но сходу за вечер написать все это, пожалуй, не выйдет. Сперва напишем несколько прототипов, и статья рассказывает о них:

  1. Прототип 1. Программа имитирует компилятор, разделяя команду на 2, и самостоятельно вызывая компилятор.
  2. Прототип 2. К этому добавим пересылку команды на компиляцию по сети, без самого файла.
  3. Прототип 3. Пройдемся по графу сборки Ninja, выводя потенциально разбиваемые команды.

Рекомендуется разработку прототипа делать под POSIX-совместимой OS, если вы не будете пользоваться библиотеками.

Этап 1. Разбиваем командную строку

Для прототипа остановимся на компиляторе GCC (или Clang, нет большой разницы), т.к. его командную строку проще разбирать.

Пусть у нас программа вызывается через команду «test -c hello.cpp -o hello.o». Будем считать, что после ключа «-c» (компиляция в объектный код) всегда идет имя входного файла, хоть это и не так. Так же пока остановимся только на работе в локальной директории.

Мы будем использовать функцию popen для запуска процесса и получения стандартного вывода. Функция позволяет открыть процесс так же, как мы бы открыли файл.

Что ж, теперь у нас есть маленький эмулятор компилятора, который дергает настоящий компилятор. Едем дальше 🙂

Дальнейшее развитие прототипа:

  • Учитывать и абсолютные имена файлов;
  • Использовать одну из библиотек для работы с процессами: Boost.Process, QProcess, или Ninja Subprocess;
  • Реализовать поддержку разделения команд для MSVC;
  • Сделать API для выполнения команд асинхронным, а выполнение вынести в отдельный поток.

Этап 2. Сетевая подсистема

Прототип сетевого обмена сделаем на BSD Sockets (Сокеты Беркли)

Сокет это дословно «дырка», в которую можно писать данные и считывать из неё. Чтобы подключиться к удаленному серверу, алгоритм следующий:

  • Создать сокет нужного типа (TCP) с помощью функции socket();
  • После создания, выставить нужные флаги, например неблокирующий режим с помощью setsockopt();
  • Получить адрес в нужном формате для BSD сокетов с помощью getaddrinfo();
  • Подключиться к TCP-хосту с помощью функции connect(), передав туда подготовленный адрес;
  • Вызывать функции read/send для чтения и записи;
  • После окончания работы — вызвать close().
Читайте также:  Кали линукс соц сеть

Сервер работает немного сложнее:

  • Создаем сокет с помощью функции socket();
  • Выставляем опции;
  • Вызываем bind() для того, чтобы привязать сокет к определённому адресу (полученному через getaddrinfo)
  • Начинаем прослушку порта с помощью вызова listen();
  • Входящие соединения примаем функцией accept() — он возвращает нам новый сокет;
  • С полученным сокетом выполняем операции read/write;
  • Закрываем сокет соединения и сокет прослушки через close().

Нам понадобятся сокет-клиент и сокет-сервер. Пусть их интерфейс выглядит следующим образом:

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

Допустим, сокет у нас готов, как будет примерно выглядеть клиент и сервер компилятора?

Да, не все исходники показаны, например TcpConnectionParams или ByteArrayHolder, но это достаточно примитивные структуры.

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

Дальнейшее развитие прототипа:

  • Настоятельно рекомендую использовать одну из существующих сетевых библиотек — Boost.Asio, QTcpSocket (QtNetwork), так же подумать над сериализацией с помощью Protobuf или других подобных
  • Реализовать передачу файлов по сети. Скорее всего, придется их разбивать на фрагменты, но будет зависеть от выбранной вами библиотеки.
  • Необходимо задуматься об асинхронном API отправки и приема сообщений. Кроме того, желательно его сделать абстрактным и не привязанным к сокетам вообще.

Этап 3. Интеграция с Ninja

Для начала, необходимо ознакомиться с принципами работы Ninja. Предполагается, что вы уже собирали с её помощью какие-либо проекты и примерно представляете, как выглядит build.ninja.
Используемые понятия:

  • Узел (Node) — это просто файл. Входной (исходники), выходной (объектные файлы) — это все узлы или вершины графа.
  • Правило (Rule) — по сути это просто команда с шаблоном аргументов. Например, вызов gcc — правило, а его аргументы — $FLAGS $INCLUDES $DEFINES и еще какие-то общие аргументы.
  • Ребро (Edge). Для меня было немного удивительно, но ребро соединяет не два узла, а несколько входных узлов и один выходной, посредством Правила. Вся система сборки основана на том, что последовательно обходит граф, выполняя команды для ребер. Как только все ребра обработаны, проект собран.
  • Состояние (State) — это контейнер со всем вышеперечисленным, который система сборки и использует.

Как это примерно выглядит, если нарисовать зависимости:

Здесь показан граф сборки для двух единиц трансляции, которые компонуются в приложение.

Как мы видим, для того, чтобы внести свои изменения в систему сборки, нам нужно переписать State, разбив Edges на два в нужных местах и добавив новые узлы (препроцессированные файлы).
Предположим, у нас уже есть исходники ninja, мы их собираем, и все в собранном виде работает.
Добавим в ninja.cc следующий фрагмент кода:

Саму функцию RewriteStateRules можно унести в отдельный файл, либо объявить здесь же, в ninja.cc как:

Некоторые нудные фрагменты вырезаны, полный код можно посмотреть здесь.

  • Скорее всего, первый вариант InvocationRewriter не заработает, нужно будет учитывать много вещей — например, то что аргумент компиляции «-c» может быть задан » -c «, ну и я уже молчу про то что он не обязательно предваряет исходный файл.
  • Может быть много дополнительных флагов, которые отмечают какие-то файлы, так что не всё то, что «не флаг» — это файл.
  • После создания разделённого графа, если он успешно собирается в две фазы «препроцессинг и компиляция» — нужно будет проинтегрировать удаленное выполнение по сети с нашим сетевым слоем. Собственно цикл сборки в Ninja находится в build.cc в функции Builder::Build. В нее можно добавить по аналогии с
    «if (failures_allowed && command_runner_->CanRunMore())» и «if (pending_commands)» свои этапы для распределённой сборки.

Этап X. Что дальше?

После успешного создания прототипа, нужно двигаться маленькими шажками к созданию продукта:

  • Конфигурирование всех модулей — как сетевой подсистемы, так и InvocationRewriter-а;
  • Поддержка любых комбинаций опций под разными компиляторами;
  • Поддержка сжатия при передаче файлов;
  • Разнообразная диагностика в виде логов;
  • Написание координатора, который сможет обслуживать подключение к нескольким серверам сборки;
  • Написание балансировщика, который будет учитывать то, что серверами пользуется сразу несколько клиентов (и не перегружать их сверх меры);
  • Написать интеграцию с другими системами сборки, не только Ninja.

В общем, ребята, я остановился где-то на этом этапе; сделал OpenSource-проект Wuild (исходники тут), лицензия Apache, который все эти штуки реализует. На написание ушло примерно 150 часов свободного времени (ежели кто решится повторить мой путь). Я настоятельно рекомендую по максимуму использовать существующие свободные библиотеки, чтобы сконцентрироваться на бизнес-логике и не отлаживать работу сети или запуск процессов.

Что умеет Wuild:

  • Распределённая сборка с возможностью кросс-компиляции (Clang) под Win, Mac, Linux;
  • Интеграция с Ninja и Make.

Да в общем и всё; проект в состоянии между альфой и бетой (стабильность — есть, фич — нет 😀 ). Бенчмарков не выкладываю (рекламировать не хочу), но, в сравнении с одним из продуктов-аналогом, скорость меня более чем устроила.

Статья носит скорее образовательный характер, а проект — предостерегательный (как делать не надо, в смысле NIH-синдрома — делайте меньше велосипедов).

Кто хочет — форкайте, делайте пулл-реквесты, используйте в любых страшных целях!

Источник

Оцените статью