Прощай XML-build… Здравствуй Rake!

в 13:57, , рубрики: .net, make, msbuild, nant, ruby, разработка, сборка проекта

Введение

Сегодня (Monday, April 26th, 2010 at 8:54 am? Прим. перев.) в очередной раз я твитнул о том, что не являюсь фанатом систем сборки проектов, основанных на XML. Да, я понимаю для чего они нужны. Да, они были хороши в свое время. И да, я до сих пор использую их каждый день. Но несмотря на все это я считаю, что есть более удобные способы решить эту задачу. Наиболее частым ответом на мой твит были слова: «Ну а альтернатива-то какая?» Одна из важнейших вещей, которым научила меня жизнь, это то, что не стоит жаловаться на что-либо до тех пор пока тебе нечего предложить взамен. Так что я здесь и сейчас предложу альтернативное решение…

Проблема

Прежде чем погрузимся с головой в дискуссию, позвольте задать вам вопрос… что у разработчиков (программистов, прим. перев.) получается делать лучше всего? За себя отвечу так: писать код. В таком случае почему мы должны выкручивать себе мозг, впихивая «многабукв» в монструозные XML-файлы? Почему мы не можем просто писать код? Обычно говорят, что основанные на XML приложения позволяют легче делать изменения без перекомпиляции, обладают большей гибкостью и облегчают написание инструментов, т.к. они базируются на формате, который все понимают. Но лично я на самом деле не считаю этот инструмент уж настолько ценным. Для меня это скоре просто пережитки былой истерии по поводу XML, которая имела место в программерском мире несколько лет назад. Кто работает с такими системами сборки уже несколько лет может подтвердить, что поначалу они выглядели смешно и тупо.
Люди так увлеклись прославлением XML, что забывают указать на то, чего мы лишаемся начиная писать код в XML. К примеру, что насчет всех тех инструментов, на улучшение которых наша индустрия потратила так много времени. Мы лишаемся привычных нам редакторов, дебаггеров, библиотек и прочих инструментов лишь затем, чтобы воспользоваться несколькими минорными преимуществами, которые имеет XML. Не всякую задачу можно описать в XML. Ведь XML файлы это просто система конфигурирования для кода, работающего в ее фоне. В конце концов мы все равно возвращаемся к написанию скриптов, запускаемых через конфигурацию в XML.
Так что же за преимущества имеет XML? Мы можем его с легкостью распарсить? Но как часто вы пишете парсер для файлов сборщика? Вы просто пишете XML-конфиг и скармливаете его NAnt. Вам собственноручно ничего парсить не нужно. Нет надобности в компиляции? Окей, первый стоящий аргумент, ведь мы же не хотим собирать наш сценарий сборки перед тем как собирать, собственно, сам проект. Иначе нам придется писать сценарий сборки для сценария сборки для сценария сборки… и т.д. «Курица, яйцо удовлетворения» (радует перевод проблемы «Курица <-> яйцо» в исполнении translate.google.com, прим. перев.). Ну так ведь это ограничение лишь некоторых языков. Как насчет языка, который не требует компиляции, который может выполняться аналогично NAnt и MSBuild путем передачи исполняемому файлу ссылки на интерпретируемый? Вы поняли к чему я веду. Есть множество языков, работающих подобным образом, причем несколько из них работают на платформе .Net.
Если вы следите за моим блогом, то наверное заметили, что я фанат Ruby (и я, прим. перев.). Несмотря на то, что профессионально с ним не работаю, я нахожусь в перманентном восхищении простотой и красотой этого языка. И хоть в мире Ruby компиляция — довольно редко используемая процедура, но нужда в процессе сборки проекта также велика как и в нашем мире статически компилируемых языков. Кстати, народ, а вы знаете, что одним из распространенных заблуждений по поводу сборки в мире .Net является то, что сборка нужна лишь для компиляции! Уффф! Полегчало вроде… В языках типа C#, конечно, при сборке прежде всего делается компиляция, но после этого предстоит сделать еще много какой работы. Сборщик часто должен подготовить и запустить юнит-тесты, скопировать файлы, упаковать дистрибутив и, возможно, отгрузить его в деплой. Сборщик — это скала, на которой строится сложный повторяющийся раз за разом процесс.
Итак, есть у нас Ruby. Точнее IronRuby 1.0 (на момент написания перевода актуальна версия 1.1). И мы же можем воспользоваться им, чтобы создать сборщик, пользоваться которым и легко, и приятно! Так? Ну конечно же.

Краткий обзор установки IronRuby

Сперва скачаем дистрибутив с официального сайта http://ironruby.net/. Там вы сможете скачать дистрибутивы как под .Net 2.0, так и под .Net 4.0. Процесс установки от конкретного выбора зависить не должен.
Скачав zip-файл…
*** От переводчика:
на сайте уже доступен автоматизированный инсталятор, который сделает нужные операции по настройке системы; также можно скачать исходники интерпретатора и/или библиотеки для встраивания в свое приложение. Поэтому описание ручной установки опускаю за ненадобностью.

После успешной установки интерпретатора вы можете запустить через командную строку интерактивный интерпретатор (команда «ir») и получить возможность поиграться с Ruby.

puts "CodeThinked.com is awesome!!"

Запускаем Rake

Rake — это как раз тот инструмент, о котором мы говорили выше. Он не использует XML. Это просто скрипт, написанный полностью на Ruby. Если вы пишете на интерпретируемых языках, то теоретически вам незачем использовать XML для решения задачи сборки. Вы же можете просто писать код. IronRuby поставляется со своей версией Rake, и чтобы убедиться, что у нас все настроено правильно, нужно просто набрать в командной строке «rake». Через несколько секунд вы должны получить сообщение о том, что не найдено ни одного rake-файла. Теперь давайте создадим проект и простенький rake-файл к нему.
Первое, что я сделаю — создам для проекта каталог (пусть это будет c:developmentRakeTest). В указанном каталоге создам файл Rakefile.rb. Далее следует открыть его в любимом текстовом редакторе (напр., NotePad++, прим. перев.) и поместить в него следующий код:

require 'rake'
 
task :default => [:congratulate_me]
 
desc "Tells you that you are awesome"
task :congratulate_me do
    puts "Congrats, you have rake running. Wasn't that easy?"
end

После этого вам остается в командной строке перейти в каталог с этим файлом и набрать «rake». В ответ вы должны получить «Congrats, you have rake running. Wasn’t that easy?»
И ведь и вправду просто, не так ли? Теперь в вашем распоряжении полноценный сборщик. В коде выше вы можете увидить подключение библиотеки Rake, установку по-умолчанию запуска задачи «congratulate_me», а также определение самой задачи вместе с ее описанием. Выглядит как простецкий nant скрипт, но ёжкин кот, мы писали код.
Теперь посмотрим чего нам будет стоить добавить дополнительные задачи и объединить их в одной задаче, либо запустить сборку не с дефолтной задачи, а с какой-то другой. Итак, добавим пару задач в сценарий:

task :congratulate_team do
    puts "Congrats to the team!"
end
 
task :congratulate_everyone => [:congratulate_me,:congratulate_team] do
    puts "Phew, that was a lot of congrats"
end

Вы видите как все стало чуть сложнее. У нас есть одна задача, которая просто пуляет строку в консоль, и есть другая, у которой вслед за именем идет како-то массив. Это аналог функциональности NAnt позволяющей назначить зависимость данной задачи от других. Это позволяет до выполнения данной задачи запустить другие. Теперь если мы поменяем в Rakefile.rb имя дефолтной задачи на «congratulate_everyone», то увидим, что при запуске скрипта сборки задача congratulate_everyone выполняется после успешного выполнения двух других. И нам, кстати, необязательно указывать дефолтную задачу. Мы можем вызывать конкретную задачу по ее имени. Делается это вот так: «rake congratulate_team».
Мы с вами уже сэмулировали многое из того, что умеют фреймворки NAnt или MSBuild. Все потому, что эти фреймворки просто позволяют нам соединять в последовательности несколько задач для выполнения определенного действия. Мы также имеем набор задач в скрипте и возможность выполнять их в определенной последовательности. И нам не надо никаких специальных конфигов. Мы просто можем объявить в скрипте переменную или прочитать данные откуда захотим. К тому же, мы можем подключить к скрипту другие ruby-файлы, тем самым получив доступ к настройкам, коду и задачам из разных мест. Кажется мы смогли сделать многое из того, что позволяет сделать XML, но бесплатно.
Осталась лишь одна проблема — сделать то же, что и умеют, не так ли? Мы ведь в итоге хотим поиметь возможность сборки солюшенов студии, прогонять тесты, достукиваться до системы контрола версий, копировать и зипповать файлы, пихать файлы на FTP и т.д. Может мы что-то упустили? Так получится ли у нас сравняться с MSBuild и NAnt? Хорошо что спросили.

Дружим Rake и .Net

Вы можете задаться вопросами: Так откуда мне выковырить все эти таски для скрипта? Случаем, не придется ли мне их все врукопашную забивать? Может эта проблема решена уже? И догадайтесь что я вам отвечу! Конечно же, проблема эта решена уже за вас. Лекарство от головняка зовется "Albacore". Это небольшой проект на GitHub. Не кидайтесь устанавливать его прямо сейчас. Я вам сейчас расскажу как сделать это в два счета.
Вместе с IronRuby поставляется специальный инструмент «igem» (аналог «gem» в MRI Ruby). Эта тулза позволяет вам устанавливать дополнительные пакеты IronRuby на вашу машину. Этот инструмент сам заботится о копировании пакета из репозитория на машину вместе с его зависимостями. Так что одной командой «igem install albacore» в консоли будет установлено все необходимое для работы Albacore. Вам останется только добавить в rake-файл всего одну строку:

require 'albacore'

Теперь я собираюсь создать простеньки проект консольного .Net приложения «RakeTestApp» и на его примере посмотреть как работает сборка через Albacore. Начну я с того, что добавлю в проекты конфиг с именем, к примеру, «AutomatedRelease», а затем в каждом из проектов в солюшене поменяю рабочий каталог сборки на "../build_output". Таким образом я смогу запустить специальную msbuild-задачу, предоставленну ю в наше распоряжение Albacore, чтобы обработать .sln файл и скомпилировать наш солюшен.
Сперва я создам задачу, которая будет выполнять последовательно все остальные:

task :full => [:clean,:build_release,:run_tests]

Затем я создам задачу, которая будет удалять рабочий каталог сборки «build_output»,, т.е. будет выполнять очистку.

task :clean do
    FileUtils.rm_rf 'build_output'
end

Как видите, нам не нужны никакие особые таски, чтобы просто удалить файлы. Нужный функциональ уже реализован в стандартных библиотеках Ruby. Теперь я создам первую задачу, унаследованную от задачи Albacore. Эта задача будет выполнять компиляцию солюшена:

msbuild :build_release do |msb|
  msb.properties :configuration => :AutomatedRelease
  msb.targets :Build
  msb.solution = "RakeTestApp/RakeTestApp.sln"
end

Итак, вместо «task» мы использовали «msbuild» и назвали задачу «build_release». Эта задача принимает один параметр — конфигурацию. Здесь мы выставляем конфигурацию в «AutomatedRelease», указываем нужную цель и путь к описателю солюшена, который надо собрать. Также у нас есть тесты, которые нужно прогонять при сборке. Мы можем унаследовать задачу от NUnit задачи Albacore. Выглядеть это будет так:

nunit :run_tests do |nunit|
    nunit.path_to_command = "tools/nunit/nunit-console.exe"
    nunit.assemblies "build_output/RakeTestAppTests.dll"
end

Опять же эта таска принимает один аргумент, через который мы можем задать некоторые параметры задачи. Мы здесь просто указываем исполняемый файл и список сборок с тестами (разделитель — запятая).

Теперь все вместе

require 'rake'
require 'albacore'
 
task :default => [:full]
 
task :full => [:clean,:build_release,:run_tests]
 
task :clean do
    FileUtils.rm_rf 'build_output'
end
 
msbuild :build_release do |msb|
  msb.properties :configuration => :AutomatedRelease
  msb.targets :Build
  msb.solution = "RakeTestApp/RakeTestApp.sln"
end
 
nunit :run_tests do |nunit|
    nunit.path_to_command = "tools/nunit/nunit-console.exe"
    nunit.assemblies "build_output/RakeTestAppTests.dll"
end

Вот и все! Мы создали простой инструмент сборки с помощью IronRuby, Rake и Albacore. Но это лишь малая толика того, что умеет Albacore. С этой библиотекой я могу использовать ndepend, ncover, sftp, sql-команды, производить архивирование директорий с zip, вызывать ssh, unzip и многое другое. Все эти действия лего выполняются с Albacore.

Заключение

Если вы как я чувствуете себя уставшим от сборки через XML-конфиги, то я советую вам попробовать IronRuby, Rake и Albacore. При наихудшем раскладе это будет небольшого количества времени. Но зато, если выгорит, то вы навсегда освободитесь от Татаро-Монгольского Ига гнета XML-конфигов. Надеюсь вам понравилось!

Вот ссылка на сорцы.

Автор: HomoLuden

* - обязательные к заполнению поля