Практически Groovy: Написание сценариев Ant с помощью Groovy

Сочетание Ant и Groovy позволяет создавать более выразительные и лучше проверяемые программы

В мире управления компоновкой властвуют Ant и Maven, но зачастую XML является не cлишком выразительным форматом конфигурации. Во втором выпуске новой серии статей по практическому применению Groovy Эндрю Гловер представляет утилиту компоновки Groovy, которая значительно облегчает сочетание Groovy с Ant и Maven для создания более выразительных и поддающихся проверке программ.

Эндрю Гловер, президент компании, Stelligent Incorporated

Эндрю ГловерЭндрю Гловер является президентом компании Stelligent Incorporated , которая помогает другим фирмам решать проблемы качества программного обеспечения. Для этого используются эффективные стратегии тестирования и технологии непрерывной интеграции, которые позволяют коллективам разработчиков постоянно контролировать качество кода, начиная с ранних стадий разработки. Просмотрите блог Энди , там можно найти список его публикаций.



21.01.2008

Ant почти не имеет себе равных по распространённости и полезности в качестве инструмента компоновки проектов Java. Даже Maven, недавно появившаяся утилита компоновки, позаимствовала большую часть своих сильных сторон из опыта Ant. Однако оба эти инструменты обладают недостаточной расширяемостью. Даже несмотря на то, что переносимость XML сыграла большую роль в продвижении Ant и Maven на передний край разработки, его роль в качестве формата конфигурирования компоновки во многом ограничивает выразительность процесса компоновки.

Например, хотя и в Ant, и в Maven существует возможность использования условной логики, её реализация в XML весьма затруднительна. Кроме того, несмотря на то, что существует возможность расширения процесса компоновки Ant путём добавления специальных задач, это как правило ограничивает возможности приложения узкими рамками. Поэтому в этой статье я покажу вам, как объединить Groovy и Ant в рамках Maven для получения большей выразительности и более точной настройки процесса компоновки.

Как вы скоро увидите, Groovy привносит привлекательные новшества как в Ant, так и в Maven; и прелесть такого подхода в том, что Groovy часто выручает в случаях, когда этого не может сделать XML! На самом деле похоже, что создателям Groovy надоело создавать процессы компоновки Ant и Maven на XML, поскольку они реализовали AntBuilder, новый мощный инструмент, поддерживающий работу с Ant в сценариях Groovy.

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

Краткий обзор замыканий

Как и многие его хорошо известные предшественники, Groovy поддерживает понятие безымянных функций или замыканий. Возможно, вы знакомы с замыканиями, если вы писали на Python или Jython, где замыкания реализованы с помощью ключевого слова lambda. В Ruby вам вообще нужно сильно постараться, чтобы написать сценарий без использования блоков и (или) замыканий. Даже в языке Java поддерживается ограниченная форма безымянных функций посредством анонимных внутренних классов.

Замыкания в Groovy являются безымянными функциями первого класса, которые могут инкапсулировать функции. Создатель Ruby Юкихиро Мацумото осознал пользу замыканий, когда обнаружил, что эти мощные объекты первого класса могут быть переданы "другой функции, и эта функция может вызвать переданное [замыкание]" (ссылку на интервью можно найти в разделе Ресурсы ). Конечно же, лучший способ оценить возможности замыканий в Groovy - это поработать с этим замечательным языком самостоятельно.

Об этой серии руководств

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


Замыкания в действии

В листинге 1 я переработал код из первой статьи этой серии; на этот раз мы сделаем акцент на замыканиях. Если вы читали прошлую статью вы, вероятно, помните пакет Java (см. раздел Ресурсы), фильтрующий объекты, который я использовал для демонстрации модульного тестирования в Groovy. Я начну с того же примера, но существенно дополню его замыканиями. Здесь мы также используем интерфейс Filter на базе Java.

Листинг 1. Помните его? Простой интерфейс Java-фильтра
public interface Filter {
  void setFilter(String fltr);  
  boolean applyFilter(String value);
}

После последнего определения типа Filter я привел реализацию двух методов, а именно RegexPackageFilter и SimplePackageFilter, в которых применялись регулярные выражения и простые операции String соответственно.

Пока, для кода, написанного без замыканий, всё хорошо. В листинге 2 вы можете увидеть, как код начинает меняться (в лучшую сторону!) благодаря всего лишь нескольким синтаксическим изменениям. Я начну с определения общего типа Filter, приведенного ниже, но на этот раз в Groovy. Обратите внимание, что теперь атрибут strategy связан с классом Filter. Этот атрибут является экземпляром замыкания, которое будет выполняться при вызове метода applyFilter.

Листинг 2. Фильтр Groovy -- с замыканиями
class Filter{
   strategy
   boolean applyFilter(str){
      return strategy.call(str)
   }
}

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

Теперь мне нужно определить замыкания. Первое замыкание моделирует SimplePackageFilter (из предыдущей статьи), выполняя простые операции String с заданным параметром. При создании нового типа Filter конструктору будет передаваться соответствующее замыкание simplefilter. Второе замыкание (которое приведено после нескольких assert, используемых для проверки кода) применяет регулярное выражение к заданной String. И снова я создаю новый тип Filter, передаю замыкание регулярного выражения rfilter и выполняю несколько assert для того, чтобы убедиться в том, что всё идёт хорошо. Всё это показано в листинге 3:

Листинг 3. Простое волшебство замыканий Groovy
simplefilter = { str | 
   if(str.indexOf("java.") >= 0){
     return true
   }else{
     return false
   }
}
		
fltr = new Filter(strategy:simplefilter)
assert !fltr.apply("test")
assert fltr.apply("java.lang.String")
		
rfilter = { istr |
   if(istr =~ "com.vanward.*"){
     return true
   }else{
     return false
   }
}
		
rfltr = new Filter(strategy:rfilter)
assert !rfltr.apply("java.lang.String")
assert rfltr.apply("com.vanward.sedona.package")

Впечатляет, не правда ли? Используя замыкания, я смог отложить определение нужного мне поведения до времени выполнения, поэтому мне не нужно определять новый тип Filter и компилировать его, как это было ранее. Хотя я могу делать нечто подобное в коде Java с помощью анонимных внутренних классов, в Groovy всё проще и менее запутано.

Замыкания, несомненно, являются мощным инструментом. Кроме того, они предоставляют различные способы обработки поведения, что активно используется в Groovy (и в его дальнем родственнике Ruby). Поэтому замыкания являются основой для следующего раздела, который называется "Сборка с помощью компоновщика"


Компоновка с помощью компоновщиков

Одной из основных отличительных черт Ant в Groovy является понятие компоновщика. По сути, компоновщики позволяют легко реализовывать в Groovy вложенные древовидные структуры, например, документы XML. И это, господа, и есть главный фокус: С помощью компоновщика, а именно AntBuilder, вы можете без особых усилий создать XML-файлы компоновки Ant и выполнить нужные действия не работая с самим XML. И это не единственное преимущество работы с Ant в Groovy. В отличие от XML, Groovy обладает очень выразительной средой разработки, в которой вы можете с легкостью создавать циклические конструкции, условия и даже повторно использовать код вместо упражнений с буфером обмена, которыми приходилось до сих пор заниматься при создании файлов build.xml. И все это - на платформе Java!

Прелесть компоновщиков, и особенно AntBuilder в Groovy, состоит в том, что их синтаксис следует логике, схожей с представляемым ими документом XML. Методы экземпляра AntBuilder соответствуют задачам Ant; более того, у этих методов могут быть параметры (в виде map), соответствующие атрибутам задач. Более того, вложенные теги, например, include и fileset, определяются как замыкания.

Элементы компоновки: Пример 1

Я начну ваше знакомство с компоновщиками с крайне простого примера: задачи Ant, вызывающей echo. В листинге 4 я создал обычную версию XML тега Ant echo (здесь нет никаких сюрпризов):

Листинг 4. Задача Echo в Ant
<echo message="This was set via the message attribute"/>
<echo>Hello World!</echo>

Листинг 5 более интересен, в нём я взял тот же тег Ant и переопределил его в Groovy, используя тип AntBuilder. Теперь я могу использовать атрибут echo, message, или просто передать нужную String.

Листинг 5. Задача Ant Echo в Groovy
ant = new AntBuilder()
ant.echo(message:"mapping it via attribute!")		 
ant.echo("Hello World!")

Особенно в компоновщиках впечатляет то, как они позволяют совмещать обычные возможности Groovy с синтаксисом компоновщика, открывая таким образом широчайшие возможности. В листинге 6 вы уже можете увидеть, насколько безграничны возможности:

Листинг 6. Управление процессом с помощью Groovy и Ant
ant = new AntBuilder()
ant.mkdir(dir:"/dev/projects/ighr/binaries/")

try{

    ant.javac(srcdir:"/dev/projects/ighr/src", 
       destdir:"/dev/projects/ighr/binaries/" )

}catch(Throwable thr){
    ant.mail(mailhost:"mail.anywhere.com", subject:"build failure"){
       from(address:"buildmaster@anywhere.com", name:"buildmaster")
       to(address:"dev-team@anywhere.com", name:"Development Team")
       message("Unable to compile ighr's source.")
    }
}

В этом примере я попытался обработать ошибку компиляции исходного кода. Обратите внимание, как объект mail , определенный в блоке catch, принимает замыкание, определяющее атрибуты from, to и message .

Вот так! Вот такой Groovy. Конечно же, главное - знать, когда применить все эти умные функции, и этого знания всем нам время от времени не хватает. К счастью, практика – путь к мастерству, и если вы начнёте работать с Groovy (или любым другим языком сценариев, если на то пошло), вы найдёте множество возможностей применить его в реальной жизни - и одновременно поймете, где именно он нужен. В следующем разделе я покажу реальный пример, в котором я использовал некоторые из представленных здесь возможностей.


Использование Groovy

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

  1. Откомпилировать весь исходный код
  2. Запустить алгоритм md5 для каждого двоичного файла класса.
  3. Создать простой отчёт, в котором перечислены все файлы классов и соответствующие им контрольные суммы.

Полностью отказаться от Ant или Maven и использовать Groovy на протяжении всего процесса компоновки будет в этом случае некоторым экстремизмом. Как я уже объяснял ранее, Groovy - отличное дополнение к этим инструментам, а не их замена. Поэтому имеет смысл дополнить две последние позиции выразительностью Groovy, доверив выполнение первых двух шагов Ant или Maven.

На самом деле давайте предположим, что я использовал на первом шаге Maven, поскольку, положа руку на сердце, это моя любимая платформа компоновки. Компиляция файлов исходного кода в Maven выполняется очень просто посредством применения целей java:compile и test:compile; таким образом, я могу оставить всё как есть и создать новую цель, ссылаясь на указанные выше цели как необходимые. Теперь, имея скомпилированные файлы исходного кода, я готов перейти к запуску утилиты, подсчитывающей контрольные суммы, но для начала мне нужно кое-что настроить.


Настройка Md5ReportBuilder

Для того чтобы эту замечательную утилиту для подсчёта контрольных сумм запустить средствами Ant, мне нужно знать две вещи: в какой директории находятся файлы, контрольную сумму которых нужно подсчитать, и в какую директорию должен быть записан отчёт. В качестве первого аргумента утилиты мне нужен список директорий, разделенных запятыми. В качестве второго аргумента - директорию, в которую нужно поместить отчёт.

Я решил назвать класс утилиты Md5ReportBuilder. Его метод main определен в листинге 7.

Листинг 7. Основной метод Md5ReportBuilder
static void main(args) {	 	
		
  assert args[0] && args[1] != null
		
  dirs = args[0].split(",")
  todir = args[1]
		
  report = new Md5ReportBuilder()
  report.runCheckSum(dirs)  
  report.buildReport(todir)		 	
}

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


Добавление контрольной суммы

В состав Ant входит задачаchecksum, которую очень легко вызвать, передав ей fileset , содержащий коллекцию обрабатываемых файлов. В нашем случае такими файлами являются директории, содержащие скомпилированные файлы исходного кода и соответствующие файлы модульного тестирования. Я могу получить эти файлы с помощью итераций по циклу for, который в данном случае обходит коллекцию директорий. Для каждой директории запускается задача checksum; более того, задача checksum запускается только для файлов .class, что видно из листинга 8.

Листинг 8. Метод runCheckSum
/**
 * runs checksum task for each dir in collection passed in
 */
runCheckSum(dirs){
  ant = new AntBuilder()	   
  for(idir in dirs){	   
    ant.checksum(fileext:".md5.txt" ){
      fileset(dir:idir) {
        include(name:"**/*.class")        
      }
   }
 }
}

Формирование отчёта

Теперь задача формирования отчёта сводится к формированию цикла. Каждая вновь создаваемая контрольная сумма файла считывается, и соответствующая информация передаётся PrintWriter, который записывает XML в файл, правда, в самой неудобочитаемой форме, как видно из листинга 9:

Листинг 9. Формирование отчёта
buildReport(bsedir){
  ant = new AntBuilder()
  scanner = ant.fileScanner {
    fileset(dir:bsedir) {
      include(name:"**/*class.md5.txt")
    }
  }

  rdir = bsedir + File.separator + "xml" + File.separator
  file = new File(rdir) 	    

  if(!file.exists()){	 	    
    ant.mkdir(dir:rdir) 
  }

  nfile = new File(rdir + File.separator + "checksum.xml")

  nfile.withPrintWriter{ pwriter |
     pwriter.println("<md5report>")
     for(f in scanner){
       f.eachLine{ line |
         pwriter.println("<md5 class='" + f.path + "' value='" + line + "'/>")
       }
     }
     pwriter.println("</md5report>")
  }		
}

Итак, что же происходит в этом отчёте? Во-первых, я с помощью FileScanner нахожу все файлы с контрольными суммами, созданные методом checksum, приведенным в листинге 8. После этого я создаю новую директорию и новый файл в этой директории. (Как здорово я проверил существование директории одним простым оператором if, не так ли?)После этого я открываю соответствующий файл и, используя изящное замыкание, считываю каждый соответствующий File из коллекции scanner. Пример завершается выводом содержимого отчёта в элемент XML.

Ваши цели

Читатели, не знакомые с Maven, могут задать вопрос, о каких goal идёт речь. Рассматривайте goal в Maven как target в Ant. Цель (goal) - это просто способ группировки команд. Целям (goal) Maven присваиваются имена, которые могут быть вызваны с помощью команды maven. При вызове выполняются все задачи, описанные в указанной goal.

Готов поспорить, вы оценили мощь метода withPrintWriter на экземплярах File. Мне не нужно беспокоиться об ошибках и закрытии файла, поскольку всё уже сделано за меня. Я просто указываю с помощью замыканий, что нужно сделать, и всё!


Запуск утилиты

Следующий акт нашего представления Groovy – процесс компоновки – а именно, подключение моего файла maven.xml. К счастью, это самая простая часть нашего простого упражнения, как можно увидеть в листинге 10:

Листинг 10. Запуск Md5ReportBuilder в Maven
<goal name="gmd5:run" prereqs="java:compile,test:compile">

  <path id="groovy.classpath">						
    <ant:pathelement path="${plugin.getDependencyClasspath()}"/>
    <ant:pathelement location="${plugin.dir}"/>
    <ant:pathelement location="${plugin.resources}"/>            
  </path>

  <java classname="groovy.lang.GroovyShell" fork="yes">
    <classpath refid="groovy.classpath"/>
    <arg value="${plugin.dir}/src/groovy/com/vanward/md5builder/Md5ReportBuilder.groovy"/>
    <arg value="${maven.test.dest},${maven.build.dest}"/>
    <arg value="${maven.build.dir}/md5-report"/>
  </java>
</goal>

Как я уже объяснял выше, я решил, что утилита расчёта контрольных сумм будет запускаться после завершения компиляции; поэтому у моей цели есть два необходимых условия: java:compile и test:compile. Всегда важно указывать пути классов, поэтому я уделил особое внимание созданию правильного пути для запуска Groovy. Наконец, цель, показанная в листинге 10, вызывает командный процессор Groovy, передавая сценарий для запуска и два соответствующих аргумента - разделенный запятыми список директорий, по которым нужно рассчитать контрольные суммы, и директорию, в которую будет записываться отчёт.


Подбираем хвосты

После того, как я составил файл maven.xml, все почти закончено, но остался ещё один шаг: Мне нужно обновить файл project.xml , указав необходимые зависимости. Неудивительно, что для работы Maven необходимо указать файлы, нужные Groovy. К их числу относятся asm, библиотека для работы с байт-кодом, утилита commons-cli (обрабатывает параметры командной строки) Ant, а также соответствующий ant-launcher. Зависимости для рассматриваемого примера показаны в листинге 11:

Листинг 11. Зависимости Groovy
<dependencies>
  <dependency>
    <groupId>groovy</groupId>
    <id>groovy</id>
    <version>1.0-beta-6</version>
  </dependency>

  <dependency>
    <groupId>asm</groupId>
    <id>asm</id>
    <version>1.4.1</version>
  </dependency>

  <dependency>
    <id>commons-cli</id>
    <version>1.0</version>
  </dependency>

  <dependency>
    <groupId>ant</groupId>
    <artifactId>ant</artifactId>
    <version>1.6.1</version>
  </dependency>

  <dependency>
    <groupId>ant</groupId>
    <artifactId>ant-launcher</artifactId>
    <version>1.6.1</version>
  </dependency>
</dependencies>

Повторение урока

В этом втором выпуске Практически Groovy вы увидели, что произойдёт, если совместить выразительность и возможности Groovy с непревзойдённой эффективностью Ant и Maven. Для обоих инструментов Groovy предлагает привлекательный формат компоновки, альтернативный XML. Он значительно расширяет возможности процесса компоновки, позволяя контролировать ход работы программы с помощью конструкций циклов и условной логики.

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

В следующем месяце я познакомлю вас с другой возможностью Groovy, также использующей замыкания. GroovySql - чрезвычайно полезная небольшая утилита, которая очень облегчает работу с запросами, обновлениями и вставками в базы данных, а также со всей соответствующей логикой. До следующего раза!

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=283155
ArticleTitle=Практически Groovy: Написание сценариев Ant с помощью Groovy
publish-date=01212008