Технология, о которой я расскажу здесь, может быть немного старомодной, но я думаю, что я представлю кое-что интересное некоторым людям, особенно тем, кто начинает автоматизировать обычные задачи своих проектов, такие как компиляция, тестирование, развертывание и т. Д. ; похоже, что все, кто начинает это делать, выбирают Apache Ant в качестве основного инструмента, и именно этот инструмент я и использую в этой статье.
Я собираюсь показать вам способ создания расширяемой среды для определения и выполнения связанных задач Ant. Полную версию фреймворка можно скачать здесь .
Типичный сценарий для фреймворка следующий: вам необходимо выполнить последовательность операций для развертывания вашего проекта, скажем, загрузить его из VCS, скомпилировать, протестировать, сгенерировать документацию, создать резервную копию базы данных, упаковать все и развернуть Это. Вы хотите сделать все это, набрав простую команду, которая запускает последовательность. Например, вы хотите сказать: выполнить развертывание . Вот и все. Полный цикл операций вплоть до развертывания вашего приложения выполняется один за другим.
Что ж, мы собираемся достичь этого, используя Ant в качестве основного инструмента.
ОБОСНОВАНИЕ
Во-первых, мы должны заметить, что эти задачи не являются изолированными, но они зависят друг от друга. Вы не можете начать компилировать свой код до того, как загрузите его из VCS. Таким образом, существует зависимость между этими задачами, которые могут быть заранее разработаны. Ant позволяет нам легко устанавливать зависимости между задачами. Некоторые задачи могут быть изолированными (без зависимости от других задач), но в целом задачи зависят друг от друга.
Второе, о чем мы должны знать, это то, что, если мы собираемся создать структуру, мы должны попытаться (тесно) справиться со всеми случаями, которые могут нам представить. Я имею в виду, вы не можете ожидать, что все загрузят свой исходный код из репозитория Git, потому что есть некоторые люди, все еще использующие Subversion. Или скомпилируйте их проект с помощью javac, потому что есть другие компиляторы и языки. Таким образом, мы должны создать структуру, которая принимает все виды задач. Но люди выполняют общие задачи, такие как «Загрузка из VCS», «Компиляция кода» или «Резервное копирование базы данных», будь то PostgreSQL или MongoDB. Так что эти задачи можно как минимум сгруппировать по категориям.
Третье, о чем следует беспокоиться, это то, что людям не нужно выполнять ВСЕ задачи за один раз или вообще. Есть люди, которые не делают тесты. Или приложения, которым не нужен сервер данных. Таким образом, должен быть способ точно определить, какие задачи вы хотите выполнить.
Для этого нам нужны три вещи:
- Создайте категории задач и конкретные задачи внутри этих категорий, например: категория «Резервное копирование базы данных» содержит задачи «Дамп базы данных PostgreSQL» или «Резервное копирование базы данных SQLite»; или «Развернуть приложение» означает «Загрузить на сервер Http» или «Передать на сервер FTP».
- Определите зависимости задач. Или, что еще лучше, зависимости между категориями задач, например: «Компилировать код», идут после «Загрузка из VCS».
- Определите, какие именно задачи мы хотим выполнить из всего множества конкретных задач, таких как: я хочу «Экспортировать код из Github», «Компилировать код с помощью gcc» и «Развернуть приложение на Http-сервер» (не тестирование, правда?) ,
Итак, давайте запачкаем руки.
ЗАДАЧИ И КАТЕГОРИИ
Наши категории и их задачи будут установлены на основе каталогов. Я собираюсь ссылаться на базовый каталог как «./». У нас будет каталог для задач под названием «./tasks/». Итак, если у нас есть категория «compile» и задача «compile-javac» (да, давайте начнем использовать больше имен роботов; компьютеры, подобные им :-)), то у нас будет каталог, например «./tasks/». компиляции / компиляции Javac /».
Каждая конкретная задача будет определять свой собственный способ выполнения внутри файла build.xml, который определяет задачу и находится в собственном каталоге задачи. Таким образом, у нас будет файл вроде ./’tasks/compile/compile-javac/build.xml ‘.
А пока давайте определим конкретные задачи как фиктивные. Они будут только печатать объяснение того, что они должны делать. Наш файл ./’tasks/deploy/ftp-trasfer/build.xml ‘будет выглядеть так:
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="ftp-transfer" default="default"> <target name="default"> <echo message="doing deploy - ftp transfer"/> </target> </project>
Файл сборки для конкретной задачи является автономным файлом сборки; то есть он определяется как обычный файл сборки Ant с целями, которые содержат все вызовы, необходимые для выполнения задачи, например, выполнение утилиты pg_dump для резервного копирования базы данных PostgreSQL.
ОПРЕДЕЛЕНИЕ ЗАВИСИМОСТИ МЕЖДУ КАТЕГОРИЯМИ
Теперь нам нужно определить зависимости между категориями задач. Эти зависимости будут выражаться как зависимости обычных целей Ant, каждая цель соответствует каждой категории.
Зависимости будут определены в файле «./tasks/dependencies.xml», который является файлом сборки Ant. А пока давайте посмотрим, что может содержать этот файл:
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="dependencies" default="do.all"> <target name="do.export"> <ant dir="${basedir}/export/${export.specific.task}" inheritAll="false"/> </target> <target name="do.compile" depends="do.export"> <ant dir="${basedir}/compile/${compile.specific.task}" inheritAll="false"/> </target> </project>
Что мы делаем в каждой цели здесь, так это вызываем удаленную цель; мы вызываем цель, объявленную по умолчанию, в файле build.xml, расположенном в каталоге конкретной задачи. Например, цель do.compile выполняет цель по умолчанию в каталоге. выполнить (т.е. ‘compile-javac’).
ОПРЕДЕЛЕНИЕ ЗАДАЧ, КОТОРЫЕ МЫ ХОТИМ ВЫПОЛНИТЬ
Определить задачи, которые мы хотим выполнить, так же просто, как загрузить в «./tasks/dependencies.xml» файл свойств, который определяет эти задачи, и выполнить каждую цель в зависимости от условия, в котором была определена задача для соответствующей категории. В Ant это можно сделать так:
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="dependencies" default="do.all"> <!-- Load specific tasks from specific-tasks.properties --> <dirname property="basedir" file="${ant.file.dependencies}"/> <property file="${basedir}/specific-tasks.properties"/> <target name="do.export" if="export.specific.task"> <ant dir="${basedir}/export/${export.specific.task}" inheritAll="false"/> </target> <target name="do.compile" depends="do.export" if="compile.specific.task"> <ant dir="${basedir}/compile/${compile.specific.task}" inheritAll="false"/> </target> </project>
Обратите внимание, как я загружаю файл ‘./tasks/specific-tasks.properties’ и затем создаю условия if для выполнения целей или нет. Также обратите внимание, что я следую определенным соглашениям для именования свойств, которые определяют конкретные задачи; Это хорошая практика, но вы можете называть их так, как хотите, если вы проверяете их по правильному имени в каждой цели.
Пример файла specific-tasks.properties выглядит следующим образом:
export.specific.task=git-export compile.specific.task=javac-compile test.specific.task=junit-test build.specific.task=custom-build database.specific.task=postgres-dump doc.specific.task=javadoc deploy.specific.task=ftp-transfer
ПРОВОДКА ВСЕ ВМЕСТЕ: РАМКА
Все, что у нас сейчас есть, это куча файлов Ant и целей. Да, в. Но как мы запускаем цепь?
Нам нужно только вызвать «конечную» категорию, которую мы хотим выполнить. Если вы хотите выполнить задачи вплоть до тестирования, вы должны вызвать категорию «тест». Для этого я создал пакетный скрипт с именем invoke.bat, который содержит следующее:
@echo off set script_dir=%~dp0 set script_dir=%script_dir%## set script_dir=%script_dir:\##=##% set script_dir=%script_dir:##=% set ANT_HOME=%script_dir%/3p/ant set PATH=%PATH%;%ANT_HOME%/bin set ant_lib_extras=%script_dir%/3p/ant/lib/extras set category=%1 set target=do.%category% cd .. ant -quiet -lib "%ant_lib_extras%" -buildfile tasks/dependencies.xml %target% pause
ПРИМЕЧАНИЕ. Среда имеет встроенный Ant, так что он может быть уверен, что Ant существует, и избежать какой-либо работы для вас. Он также содержит версию скрипта для Linux.
Вы можете выполнить скрипт следующим образом: invoke deploy
Результат его запуска с приведенным выше примером файла specific-tasks.properties должен выглядеть следующим образом:
[echo] doing export - git export [echo] doing compile - javac compile [echo] doing test - junit test [echo] doing build - custom build [echo] doing database - postgres dump [echo] doing doc - javadoc [echo] doing deploy - ftp transfer
If you run invoke test, then the output is like:
[echo] doing export - git export [echo] doing compile - javac compile [echo] doing test - junit test
PSEUDO-CODE MIGHT EXPLAIN BETTER
For a better understanding of what’s happening under the hood some pseudo code might work:
function invoke(category) { // Let Ant figure out the execution order of category targets up to category orderedCategoriesList = AntCreateOrderedCategoriesExecutionUpTo(category, './tasks/dependencies.xml'); foreach(cat in orderedCategoriesList) do if isset '$cat.specific.task' in './tasks/specific-tasks.properties' then execute default target in './tasks/$cat/${$cat.specific.task}/build.xml' else continue end }
EXTENDING THE FRAMEWORK
You can create new tasks inside the categories already defined by just creating a new directory for that task inside the corresponding category folder, and create its ‘build.xml’ file.
To create a new category you should create a folder with the name of the category inside the ‘./tasks’ directory, and the folders and ‘build.xml’ file for each specific task. Then you should create a target for this category in ‘dependencies.xml’, and define or reassemble all the dependencies.
To invoke a new task or category, just edit ‘specific-tasks.properties’ and run the script.
CONCLUSIONS
The framework is based on Ant’s basic features, so it’s very simple. It relies on a pre-elaborated directory structure to define categories and tasks hierarchies; the ‘dependencies.xml’ to define dependencies between tasks categories; and a simple script that manages to trigger the chain of tasks you ask for. You can try more on your own by downloading the framework.