Статьи

Заменить Марку Джейком

Корень всех инноваций — лень. Это особенно актуально для ИТ-сферы, где мы руководствуемся автоматизацией процессов. Процесс, который особенно раздражает, поэтому его нужно автоматизировать, это развертывание. Развертывание также включает в себя критический этап создания программного обеспечения, то есть компиляцию и модификацию источников, чтобы в результате было запущено приложение. Сначала люди использовали набор сценариев для выполнения одного и того же процесса сборки. Как только один и тот же набор сценариев нужно было скопировать и использовать снова, стало очевидно, что необходимо создать общую систему.

Программное обеспечение Make оказалось очень хорошим решением проблемы. Он гибкий и следует четкой концепции, но эта гибкость имеет свою цену. Многие из великих инноваций программного обеспечения, которые мы создаем, не могут работать с Make. У нас нет расширения или пакета, и расширяемая конфигурация невозможна. Чтобы избежать этих проблем, шаблоны генерации Make-файлов, использования внешних скриптов или наличия нескольких Make-файлов довольно распространены.

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

Специализированные исполнители задач и универсальные инструменты сборки

Идея использования среды JavaScript для создания инструмента сборки не нова. Каждый разработчик фронтэнда сегодня знает Grunt или Gulp. И во многих случаях эти инструменты по-прежнему должны быть основным выбором. Итак, вопрос: где мне использовать какой инструмент?

Для задач, связанных с Интернетом, таких как минимизация файлов JavaScript, префикс CSS или оптимизация изображений, предпочтение следует отдавать исполнителям задач. Но даже в таких случаях Джейк может рассматриваться как вариант, потому что это расширенный набор упомянутых инструментов. Он гораздо менее специализирован, и нет ничего против использования его в этом контексте.

Имея это в виду, Jake лучше подходит, если вы хотите заменить другой инструмент сборки, такой как Make, или если у вас есть другой процесс сборки, который следует классическому подходу правила зависимостей, подход, где у нас есть правило, которое указывает один ко многим зависимостей. Прелесть универсального инструмента сборки в том, что его можно использовать во многих контекстах.

Прежде чем подробно обсудить преимущества Jake, стоит взглянуть на Make и его блестящую концепцию.

Взгляд на Марку

Каждой системе сборки нужны три вещи:

  1. Инструменты (или программное обеспечение или функции), чтобы сделать работу
  2. Правила, чтобы указать, какую работу делать
  3. Зависимости, чтобы указать, какие правила применять

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

Джейк

В экосистеме Node.js есть много замечательных модулей, которые улучшают работу терминала. Это особенно удобно для инструмента сборки. Благодаря устаревшим (и простым) операциям DOM, JavaScript является языком, очень ориентированным на строки. Это очень хорошо сочетается с философией командной строки Unix. Но есть и другая причина, по которой Jake лучше своих конкурентов: специальные функции для тестирования и просмотра изменений в файлах уже интегрированы.

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

Как и другие инструменты сборки, Джейк использует специальный тип файла для описания процесса сборки. Этот файл называется Jakefile и использует Jakefile.js в качестве имени по умолчанию. Тем не менее, можно использовать короткий список других имен, таких как Jakefile , и они автоматически распознаются. Также возможно использование пользовательских имен файлов, но в этом случае вы должны указать имя файла, используемого явно.

Jakefile — это файл, который включает в себя необходимые модули, определяет все задачи и устанавливает некоторые правила. Чтобы применить некоторую структуру к нашим задачам, мы также можем использовать специальную конструкцию, называемую пространством имен . Мы не будем вдаваться в пространства имен в этой статье, но сама концепция может быть полезна для уменьшения потенциального хаоса для больших Jake-файлов.

Пример Jakefile для компиляции приложения

Прежде чем мы начнем с примера Jakefile, у нас должен быть установлен Jake. Установка проста, если вы используете npm, поскольку вам нужно только ввести команду:

npm install -g jake 

Пример, который я собираюсь объяснить, немного длинен, но он близок к реальному коду и иллюстрирует несколько важных концепций. Мы пройдемся по всем линиям, мельком увидев каждый блок. Мы притворимся, что скомпилировали какое-то приложение C ++, но пример не требует никаких знаний о C ++.

Первая строка файла:

 var chalk = require('chalk'); 

Здесь мы включаем модуль Node.js под названием «мел». Мел — очень полезный инструмент для окрашивания вывода терминала, и он определенно должен быть частью большинства Jakefiles.

Как уже упоминалось, мы можем в полной мере использовать экосистему Node.js. Итак, в следующем разделе мы указываем некоторые константы, которые важны для большей гибкости. Если мы используем JavaScript, мы должны использовать его правильно.

 var sourceDirectory = 'src'; var outputDirectory = 'bin'; var objectDirectory = 'obj'; var includeDirectory = 'include'; var applicationName = 'example'; var isAsync = { async: true }; 

Следующие строки также определяют некоторые константы, но на этот раз мы также разрешаем внешним аргументам переопределять наши собственные определения. Мы не хотим переписывать процесс сборки, чтобы просто попробовать другой компилятор или указать другие флаги. Использование этих аргументов возможно через объект process.env как показано ниже:

 var cc = process.env.cc || 'g++'; var cflags = process.env.cflags || '-std=c++11'; var options = process.env.options || '-Wall'; var libs = process.env.libs || '-lm'; var defines = process.env.defines || ''; 

Теперь реальная сделка начинается. Мы используем jake.FileList конструктора jake.FileList для создания нового списка файлов, который включает все файлы с расширением .cpp в каталоге всех исходных файлов. Этот список затем используется для создания аналогичного списка файлов со всеми объектными файлами. Эти файлы могут не существовать на данный момент, но это не большая проблема. На самом деле, мы не используем поиск файлов для определения списка объектных файлов, а используем некоторое сопоставление JavaScript из существующего списка файлов, представленного в виде массива. Код, реализующий это описание, показан ниже:

 var files = new jake.FileList(); files.include(sourceDirectory + '/*.cpp'); var target = outputDirectory + '/' + applicationName; var objects = files.toArray().map(function(fileName) { return fileName .replace(sourceDirectory, objectDirectory) .replace('.cpp', '.o'); }); 

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

 var info = function(sender, message) { jake.logger.log(['[', chalk.green(sender), '] ', chalk.gray(message)].toMessage()); }; var warn = function(sender, message) { jake.logger.log(['[', chalk.red(sender), '] ', chalk.gray(message)].toMessage()); }; 

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

 var condition = new RegExp('/' + objectDirectory + '/.+' + '\\.o$'); var sourceFileName = function(fileName) { var index = fileName.lastIndexOf('/'); return sourceDirectory + fileName.substr(index).replace('.o', '.cpp'); }; 

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

  • Связывание существующих объектных файлов. Они формируют исполняемый файл в данном сценарии
  • Компиляция исходного файла в объектный файл

Эти две функции используют предоставленный обратный вызов. Обратный вызов будет jake.exec функции jake.exec которая отвечает за запуск системных команд:

 var link = function(target, objs, callback) { var cmd = [cc, cflags, '-o', target, objs, options, libs].join(' '); jake.exec(cmd, callback); }; var compile = function(name, source, callback) { var cmd = [cc, cflags, '-c', '-I', includeDirectory, '-o', name, source, options, '-O2', defines].join(' '); jake.exec(cmd, callback); }; 

В следующем фрагменте раскрываются две важные части Jakefile:

  1. Мы устанавливаем правило преобразования для создания объектных файлов из исходных файлов. Мы используем ранее определенные регулярное выражение и функцию, чтобы получить все запрошенные объектные файлы с соответствующими исходными файлами. Кроме того, мы аннотируем это, чтобы иметь возможность работать асинхронно. Таким образом, мы можем параллельно запускать несколько созданий файлов исходного кода. В обратном вызове мы закрываем правило, вызывая встроенный метод complete
  2. Мы определяем правило файла, которое создает одну цель из нескольких зависимостей. Еще раз, функция помечена как способная работать асинхронно. Используя метод jake.mkdirP мы jake.mkdirP , что каталог для хранения вывода существует, в противном случае он создается.

С помощью этих двух видов правил мы можем установить некоторые задачи. Задачи — это правила, к которым можно получить доступ из инструмента сборки через командную строку.

 rule(condition, sourceFileName, isAsync, function() { jake.mkdirP(objectDirectory); var name = this.name; var source = this.source; compile(name, source, function() { info(cc, 'Compiled ' + chalk.magenta(source) + ' to ' + chalk.magenta(name) + '.'); complete(); }); }); file(target, objects, isAsync, function() { jake.mkdirP(outputDirectory); link(target, objects, function() { info(cc, 'Linked ' + chalk.magenta(target) + '.'); complete(); }); }); 

Наконец, мы поставили три задачи. Один для создания документации, другой для компиляции приложения и задача по умолчанию, которая выполняется, когда jake вызывается из командной строки без каких-либо аргументов. Задача по умолчанию имеет специальное имя default и зависит от двух других определенных задач. Задание на документацию специально пусто. Он существует только для иллюстрации концепции нескольких задач.

 desc('Creates the documentation'); task('doc', [], isAsync, function() { info('doc', 'Finished with nothing'); }); desc('Compiles the application'); task('compile', [target], isAsync, function() { info('compile', 'Finished with compilation'); }); desc('Compiles the application and creates the documentation'); task('default', ['compile', 'doc'], function() { info('default', 'Everything done!'); }); 

Запуск специальной задачи, такой как compile , возможен при запуске jake compile на терминале. Все определенные задачи и их соответствующие описания показываются с помощью команды jake -ls .

Вывод

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