Статьи

Грабли 201

Во второй статье о Rake мы немного углубимся и расскажем о более сложных темах, таких как файловые задачи, правила, многозадачность и т. Д., Которые значительно улучшат ваш выбор Rake.

  • отказ
  • Задание по умолчанию
  • Объект задачи
  • Файловые задачи
  • Метод справочника
  • Файловые утилиты
  • правила
  • Флаг трассировки
  • Параллельные задачи

Я хочу подойти к этой теме с более общей точки зрения. Это не статья, в которой представлен список задач Rake с продуманными решениями, которые можно скопировать и вставить без особой запоздалости. Он больше предназначен для того, чтобы заглянуть под капот, будучи дружественным для новичков, а также интересным для людей, которые просто еще мало играли с Rake, кроме очевидных задач Rake в Rails.

Я думаю, что понимание инструмента и того, что он предлагает, дает более высокую прибыль. Надеюсь, ты не против. Для меня освещение принципов более ценно — и более доступно для начинающих, — и вам решать, что вы будете делать с ним в своих собственных приложениях.

Я уверен, что вы по крайней мере где-то слышали этот термин ранее. Но что на самом деле является задачей по умолчанию? В этом нет ничего волшебного, но давайте быстро избавимся от этого. Когда вы запускаете rake без какого-либо дополнительного имени для задачи rake, выполняется задание по умолчанию.

1
rake
1
2
3
4
desc ‘This is the default task.
task :default do
  puts ‘Some task that you might wanna run on a regular basis’
end

В Rails задача по умолчанию должна запускать ваши тесты. Ваше предположение такое же хорошее, как и мое, но я полагаю, что это было результатом испытаний, которые нужно было выполнять чаще, чем какие-либо другие задачи. Когда вы переопределяете задачу Rake по default в Rails, она просто добавляется к задаче, определенной Rails, — она ​​не переопределяет ее. Это на самом деле, как работает Rake. Когда вы переопределяете задачу Rake, вы добавляете к предыдущим определениям.

Как следует из названия, это задачи, которые вы выполняете над файлами (и каталогами). У них есть несколько хитростей в рукавах, хотя. Грабли, конечно, будут работать много времени с файлами. Не удивительно, что кто-то узнал этот шаблон и создал для вас специальные файловые задачи, особенно по простой причине, чтобы избежать дублирования или потери возможностей обработки.

Преобразование файлов из одного типа в другой — очень распространенная задача. Источники — ваши зависимости, а имена задач — то, что следует за ключевым словом file . Преобразование Markdown в HTML-файлы, преобразование HTML-файлов в форматы электронных книг, JPG-изображения в PNG-изображения, компиляция исходного кода, создание статических страниц или просто изменение расширений файлов и многие другие варианты в вашем распоряжении. Мы могли бы сделать все это вручную, но это, конечно, утомительно и неэффективно. Написание кода для этого гораздо более элегантно и масштабируемо.

Использование файловых задач мало чем отличается от «обычных» задач. Они также появятся, если вы запросите список задач Rake через rake -T . На самом деле, Rake обрабатывает все задачи одинаково, за исключением multitask . Добавление описаний и предварительных условий также не является проблемой для файловых задач.

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

1
2
3
file ‘mi6/q/gadgets/secret_list.md’ => ‘mi6/research/secret_list.md’ do
  cp ‘mi6/research/secret_list.md’, ‘mi6/q/gadgets/secret_list.md’
end

Имя вашей файловой задачи — это в основном ваш целевой файл, файл, который вы хотите создать. Обязательным условием является исходный файл, необходимый для выполнения задачи. Внутри блока вы говорите Rake, как создать желаемый результат — как его построить с использованием уже существующих файлов предварительных требований. Ввод, вывод. Например, это может быть команда оболочки, использующая инструмент pandoc , который преобразует файлы Markdown в файлы HTML. Приложений для файловых задач более чем достаточно. Поначалу синтаксис может показаться немного странным. Я понял

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

1
2
3
4
desc ‘Change some file extension’
file ‘some_file.new_extension’ => ‘some_file.old_extension’ do
  mv ‘some_file.old_extension’, ‘some_file.new_extension’
end
1
2
3
$rake some_file.new_extension
 
=> mv some_file.old_extension some_file.new_extension

Если вам интересно узнать о методе cp в предыдущем примере или приведенной выше команде mv , давайте поговорим о файловых утилитах. Мы могли бы использовать sh mv ... для выполнения команды Shell из задачи Rake. К счастью для нас, мы можем использовать модуль, который делает такие командные команды Shell менее детальными и независимыми от платформы. FileUtils — это модуль Ruby с множеством unixy-команд для файловых операций:

  • rm
  • cp
  • mv
  • mkdir
  • и так далее…

Если изобретать велосипед не для вас, FileUtils будет полезным компаньоном для работы с файлами. Часто Rake — это все, что вам нужно, но время от времени вы будете очень рады, что этот удобный модуль получил вашу поддержку. RakeUtils расширил этот модуль для вашего удобства.

Давайте рассмотрим список того, что находится в вашем распоряжении, а затем рассмотрим несколько конкретных, которые могут вас заинтересовать:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cd(dir, options)
cd(dir, options) {|dir|
pwd()
mkdir(dir, options)
mkdir(list, options)
mkdir_p(dir, options)
mkdir_p(list, options)
rmdir(dir, options)
rmdir(list, options)
ln(old, new, options)
ln(list, destdir, options)
ln_s(old, new, options)
ln_s(list, destdir, options)
ln_sf(src, dest, options)
cp(src, dest, options)
cp(list, dir, options)
cp_r(src, dest, options)
cp_r(list, dir, options)
mv(src, dest, options)
mv(list, dir, options)
rm(list, options)
rm_r(list, options)
rm_rf(list, options)
install(src, dest, mode = <src’s>, options)
chmod(mode, list, options)
chmod_R(mode, list, options)
chown(user, group, list, options)
chown_R(user, group, list, options)
touch(list, options)

Хотя я предполагаю, что вы новичок, я также предполагаю, что вы уже играли с Rails и знаете базовые утилиты Unix — такие как mv , cd , pwd , mkdir и другие. Если нет, делай домашнее задание и возвращайся.

В ваших Rake-файлах вы можете использовать эти методы прямо из коробки. И чтобы избежать недоразумений, это слой Ruby, который «подражает» этим командам Unix и который вы можете использовать в своих файлах Rake без префиксов, таких как sh для выполнения команды Shell. Кстати, options вы видите в списке выше, означают хэш {} параметров. Давайте рассмотрим несколько интересных команд, которые могут пригодиться при написании файловых задач:

  • sh

Это позволяет вам выполнять команды оболочки из ваших файлов Ruby.

  • cd

Это очень простой, но в этой команде есть что-то классное. Если вы предоставляете cd с блоком, он меняет текущий каталог на его назначение, выполняет свою работу, как определено в блоке, а затем возвращается в предыдущий рабочий каталог для продолжения. Опрятно, на самом деле!

  • cp_r

Позволяет копировать файлы и каталоги рекурсивно навалом.

  • mkdir_p

Создает целевой каталог и все его указанные родители. К счастью для нас, у нас есть метод directory в Rake, который еще более удобен, и поэтому он нам не нужен.

  • touch

Это обновляет временную метку файла, если он существует — если нет, он создается.

  • identical?

Позволяет проверить, совпадают ли два файла.

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

1
directory ‘mi6/q/special_gadgets’

Это создает каталоги, о которых идет речь, без особой суеты. Что может быть неочевидным сразу же, так это тот факт, что вы можете зависеть от него, как от любой другой грабли, — в качестве предварительного условия. Просто убедитесь, что имя файловой задачи, ее имя включает каталог, от которого вы зависите. Если от этого зависит несколько задач, они все равно будут созданы только один раз.

1
2
3
4
5
6
directory ‘mi6/q/gadgets’
 
desc ‘Transfer secret research gadgets’
file ‘mi6/q/gadgets/gadget_list.md’ => ‘mi6/q/gadgets’ do
  cp ‘gadget_list.md’, ‘mi6/q/special_gadgets/secret_gadget_list.md’
end

Как вы можете видеть здесь, Rake очень последовательный и думает обо всех вещах, которые нужно построить как задачи. Спасибо, Джим, это облегчает жизнь!

Правила могут помочь нам уменьшить дублирование, когда мы имеем дело с задачами — фактически, с файловыми задачами. Вместо того, чтобы инструктировать Rake выполнять задачи с определенными файлами, такими как somefile.markdown , мы можем научить Rake выполнять эти задачи с определенным типом файлов — например, с шаблоном или планом. Преобразование набора файлов вместо отдельных является гораздо более универсальным и СУХИМЫМ подходом. Задачи, подобные этим, масштабируются намного лучше, когда мы определяем шаблон для файлов, которые имеют сходные характеристики.

1
2
3
file «quartermaster_gadgets.html» => «quartermaster_gadgets.markdown» do
  sh «pandoc -s quartermaster_gadgets.markdown -o quartermaster_gadgets.html»
end

Как вы можете видеть, иметь такую ​​кучу файлов было бы утомительно. Да, мы можем написать собственный скрипт, в котором мы храним список файлов в массиве и перебираем его, но мы можем добиться большего — намного лучше.

Другим нежелательным побочным эффектом будет то, что каждый раз, когда мы запускаем такой скрипт, все HTML-файлы перестраиваются, даже если они не меняются вообще. Большой список файлов заставил бы вас ждать намного дольше или занимать гораздо больше ресурсов, чем необходимо. Нам не нужен дополнительный код для работы с несколькими файлами. Rake лучше и эффективнее работает в этом отделе, поскольку выполняет свои файловые задачи или правила только тогда, когда к файлу прикасались.

1
2
3
rule «.html» => «.markdown» do |rule|
  sh «pandoc -s #{rule.source} -o #{rule.name}»
end

Когда мы определяем правило, подобное приведенному выше, у нас есть механизм для преобразования любого файла с расширением .markdown в файл .html . С помощью правил Rake сначала ищет задачу для определенного файла, например quartermaster_gadgets.html . Но когда он не может его найти, он использует простое правило .html для поиска источника, который мог бы добиться успешного выполнения. Таким образом, вам не нужно создавать длинный список файлов, а использовать только общее «правило», которое определяет, как обрабатывать определенные файловые задачи. Довольно круто!

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

Из тела правила в блоке (и файловых задач) у нас есть доступ к имени и источнику правил. Мы можем извлечь информацию из передаваемого аргумента — имя через rule.name и его источник (он же источник файла) через rule.source . Выше мы могли бы избежать дублирования имен файлов и вместо этого обобщать шаблон. Точно так же мы могли бы получить список предпосылок или зависимостей с rules.prerequisites . Для файловых задач или любых других задач, конечно, то же самое относится.

Говоря о зависимостях, они могут функционировать как список для повторения. Нет необходимости создавать each отдельный цикл, если вы правильно разыграете свои карты.

1
2
3
4
5
task :html => %W[quartermaster_gadgets.html, research_gadgets.html]
 
rule «.html» => «.md» do |r|
  sh «pandoc -s #{r.source} -o #{r.name}»
end

Как видите, нам не нужно было вручную перебирать список статей. Мы просто включили Rake в работу и использовали зависимости, что намного проще и чище.

Что еще круче для СУШКИ, так это то, что правила могут принять в качестве предварительного условия объект proc — анонимный функциональный объект, лямбда в основном. Это означает, что вместо одного шаблона в качестве предварительного условия мы можем передать нечто более динамичное, что позволит нам создать сеть шаблонов, которая поймает больше, чем одна рыба. Например, правила для .markdown и .md .

У них будет то же тело правила, но только другая модель в качестве предпосылки. Это похоже на определение новой задачи File для каждого объекта, возвращаемого объектом proc. Другой способ работы с правилами — это, конечно, регулярные выражения. Вы передаете шаблон как зависимость, и если вы получили соответствие, файловая задача может быть выполнена. Сладких вариантов нет?

1
2
3
4
5
6
7
8
9
some_markdown_list = […]
 
detect_source = proc do |html_file_name|
  some_markdown_list.detect { |markdown_source|
end
 
rule ‘.html’ => detect_source do |r|
  sh «pandoc -s #{r.source} -o #{r.name}»
end

Если вы плохо знакомы с лямбда-страной или еще не поняли это полностью, вот небольшой курс. Procs — это объекты, которые вы можете передавать, которые могут быть выполнены позже, как и лямбды. Кстати, оба объекта являются объектами Proc. Разница тонкая и сводится к аргументам, которые передаются им. Лямбды проверяют количество аргументов и могут по этой причине взорвать ArgumentError — процессам все равно. Другое отличие заключается в том, как они обрабатывают операторы возврата. Проц выходит из области, в которой выполнялся объект proc. Лямбды просто выходят из области лямбды и продолжают запускать, так сказать, следующий код в строке. Это не супер важно, но я подумал, что для новичков среди вас это тоже не повредит.

Это краткий список флагов, которые вы можете передать в грабли.

  • --rules

Показывает, как Rake пытается применять правила — след для правил. Неоценимо, если вы имеете дело с парой правил и сталкиваетесь с ошибками.

1
2
3
4
$ rake quartermaster_gadgets.html —rules
Attempting Rule quartermaster_gadgets.html => quartermaster_gadgets.md
(quartermaster_gadgets.html => quartermaster_gadgets.md … EXIST)
pandoc -s quartermaster_gadgets.md -o quartermaster_gadgets.html
  • -t

Помните задачу solve_bonnie_situation из первой статьи? Давайте добавим этот флаг к этой задаче Rake и включим трассировку. Мы также получаем обратную трассировку, если сталкиваемся с ошибками. Это, безусловно, удобно для отладки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ rake solve_bonnie_situation -t
** Invoke solve_bonnie_situation (first_time)
** Invoke get_mr_wolf (first_time)
** Execute get_mr_wolf
You ain’t got no problem Jules, I’m on it!
** Invoke calm_down_jimmy (first_time)
** Execute calm_down_jimmy
Jimmy, do me a favor, will you?
** Invoke figure_out_bonnie_situation (first_time)
** Execute figure_out_bonnie_situation
If I was informed correctly, the clock is ticking.
** Invoke get_vince_vega_in_line (first_time)
** Execute get_vince_vega_in_line
Come again?
** Invoke clean_car (first_time)
** Execute clean_car
I need you two fellas to take those cleaning products and clean the inside of the car.
** Invoke clean_crew (first_time)
** Execute clean_crew
Jim, the soap!
** Invoke get_rid_of_evidence_at_monster_joes (first_time)
** Execute get_rid_of_evidence_at_monster_joes
So what’s with the outfits?
** Invoke drive_into_the_sunrise (first_time)
** Execute drive_into_the_sunrise
Call me Winston!
** Execute solve_bonnie_situation
You know, I’d go for breakfast.

Установка Rake.application.options.trace_rules = true в самом файле Rake указывает Rake показать нам трассировочную информацию о правилах, когда мы запускаем задачу. Это здорово, потому что когда мы запускаем трассировку через rake -t с одним флагом, мы получаем всю необходимую информацию для отладки. Мы не только получаем список вызовов задач, но и видим, какие правила были применены или предприняты.

  • -P

Показывает список предпосылок для всех задач. Здесь мы снова solve_bonnie_situation задачу solve_bonnie_situation . Опуская вывод для других задач, это будет его выделенный вывод:

01
02
03
04
05
06
07
08
09
10
11
12
$ rake solve_bonnie_situation -P
rake solve_bonnie_situation
    get_mr_wolf
    calm_down_jimmy
    figure_out_bonnie_situation
    get_vince_vega_in_line
    clean_car
    clean_crew
    get_rid_of_evidence_at_monster_joes
    drive_into_the_sunrise

Если вам интересно, запустите rake -P . Довольно интересный выход.

  • -m

Запускает задачи как многозадачные.

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

Задачи, которые мы выполнили до сих пор, запускают все задачи последовательно, одну за другой. Это безопасная ставка, если ваш код в порядке, но он также медленнее. Если скорость важна для какой-то задачи, мы можем немного помочь многопоточными задачами. Имейте в виду, однако, что последовательный подход является лучшим вариантом при некоторых обстоятельствах.

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

1
2
3
multitask :shoot_bond_movie => [:shoot_car_chase, :shoot_love_scene, :shoot_final_confrontation] do
  puts «Principal photography is done and we can start editing.»
end

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
task :shoot_love_scene do
  …
end
 
task :prepare_italy_set do
  …
end
 
task :shoot_car_chase => [:prepare_italy_set] do
  …
end
 
task :shoot_final_confrontation => [:prepare_italy_set] do
  …
end
 
multitask :shoot_bond_movie => [:shoot_car_chase, :shoot_love_scene, :shoot_final_confrontation] do
  puts «Principal photography is done and we can start editing.»
end

Обе shoot_car_chase и shoot_final_confrontation зависят от prepare_italy_set чтобы prepare_italy_set финишировал первым, что, кстати, выполняется только один раз. Мы можем использовать этот механизм для прогнозирования порядка при параллельном выполнении задач. Не доверяйте порядку исполнения, если это как-то важно для вашей задачи.

Ну, я думаю, вы теперь полностью готовы написать серьезный рейк-бизнес. Надеемся, что правильное использование этого инструмента сделает вашу жизнь как разработчика Ruby еще более радостной. Во второй статье я надеюсь, что смогу рассказать, что такое простой, но изумительный инструмент Rake. Он был создан настоящим мастером своего дела.

Мы все обязаны Джиму Вейриху огромным уважением за создание этого элегантного инструмента для сборки. Сообщество Ruby, конечно, не совсем то же самое, так как он скончался. Однако наследие Джима явно здесь, чтобы остаться. Еще один гигант, на которого нам повезло.