Изучаем инструменты автоматизации сборки программного обеспечения нового поколения

Упрощаем процесс тестирования с помощью CruiseControl и SCons

Comments

Лет десять назад, когда разработка программного обеспечения стала моей профессией, мы с моей командой писали код, выполняли несколько локальных тестов и затем принимали окончательную рабочую версию. Поскольку количество необходимых тестов было очень большим, каждый разработчик выполнял только свой определенный набор тестов. Каждый вечер в 20:00 выполнялось задание cron, которое компилировало исходный код и запускало процесс интеграционного тестирования. Периодически, раз в несколько дней, выяснялось, что фрагменты кода двух разных разработчиков отлично работают в изолированной среде, но не работают при интеграционном тестировании.

Быстрый темп разработки программного обеспечения на протяжении последних десяти лет привел к пониманию, что нам необходимы новые, более совершенные инструменты для интеграционного тестирования. Здесь и приходит на помощь ПО CruiseControl (см. ссылку в разделе Ресурсы), которое аналогично Makefile и отличается лишь тем, что основано на Python. Таким образом, вам не придется изучать еще один специфичный для определенной системы язык сборки, наподобие make или jam.

Работа с CruiseControl

CruiseControl прозрачно интегрируется с довольно большим количеством инструментов, включая Concurrent Versions System (CVS), Subversion, Git, Perforce, Microsoft® Visual SourceSafe и IBM® Rational® ClearCase®. В этой статье используется система управления версиями Subversion, поскольку она является наиболее популярной.

Вы можете загрузить CruiseControl с домашней страницы проекта (см. ссылку в разделе Ресурсы). На момент написания этой статьи последней была версия 2.8.4; она и используется во всех примерах.

В основе системы CruiseControl лежит файл config.xml, который выполняет сборку и тестирование в ответ на изменение кода, а также уведомляет о результатах. В CruiseControl определены несколько тегов, которые обычно используются в XML-файлах: <listeners>, <bootstrappers>, <modificationset>, <schedule> и <publishers>.

Создание конфигурационного файла

Корневой элемент конфигурации называется cruisecontrol и не содержит атрибутов. Обычно корневой элемент содержит один или несколько элементов property. Элементы property содержат пары "имя-значение" и очевидным образом задают параметры директорий, log-файлов и т. д. Пример простой конфигурации приведен в листинге 1.

Листинг 1. Добавление свойств в конфигурацию
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value=""/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
</cruisecontrol>

Следующий и, вероятно, наиболее важный тег конфигурации – это тег <project>. Конфигурационный файл может содержать несколько тегов <project>, каждый из которых определяет отдельный проект. Если у вас имеется несколько проектов, которые вы хотите запускать параллельно, то необходимо использовать тег <thread>, присвоив атрибуту count этого тега значение, превышающее 1. Если сборка выполняется на сервере с многоядерным процессором, обязательно попробуйте использовать этот прием. Обратите внимание на то, что тег <thread> является частью иерархии тегов <system>. Использование тега <thread> продемонстрировано в листинге 2.

Листинг 2. Конфигурация для серверов с многоядерными процессорами
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value="/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
  <system>
     <configuration>
         <threads count="8" />
     </configuration>
  </system>
</cruisecontrol>

Для каждого тега <project> необходимо задать атрибут name. В листинге 3 продемонстрировано использование нескольких тегов <project>.

Листинг 3. Добавление нескольких проектов в CruiseControl
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value=""/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
  <system>
     <configuration>
         <threads count="8" />
     </configuration>
  </system>
  <project name="project1">
    <! need to configure project child tags>
  </project>
  <project name="project2">
     <! need to configure project child tags>
  </project>
</cruisecontrol>

У тега <project> имеется несколько интересных атрибутов. Наиболее интересными для нас являются атрибуты buildafterfailed и requireModification. Если атрибут buildafterfailed имеет значение True, то CruiseControl будет продолжать попытки выполнения сборки проекта даже если последняя сборка завершилась с ошибкой. Это может быть полезно в случаях, когда в системе имеется много зависимостей: например, если исходные файлы хранятся в удаленном хранилище, доступ к которому может быть нарушен, или если какая-то информация извлекается из базы данных, которая может не вернуть ответ на запрос вовремя. По умолчанию атрибут buildafterfailed имеет значение False, а атрибут requireModification – значение True. Последний атрибут указывает CruiseControl на то, что повторная сборка должна выполняться только в том случае, если в базе исходного кода появились отметки об изменении. Если необходимо выполнять сборку с заданной периодичностью вне зависимости от того, был ли изменен исходный код или нет, установите значение этого атрибута в False. В следующем примере продемонстрировано использование этих двух атрибутов.

  <project name="project1" buildafterfailed=true requireModification=no>
    <! need to configure project child tags>
  </project>

Далее необходимо сконфигурировать иерархию дочерних тегов для каждого отдельного проекта. Прежде всего необходимо понять, как связать исходный код с тегом <project>. Есть ли для этого специальный тег? Конечно, да. Именно эту задачу выполняет тег <modificationset>. В листинге 4 продемонстрировано, как получить доступ к репозиторию исходных кодов Subversion из CruiseControl.

Листинг 4. Конфигурирование репозитория исходных кодов в CruiseControl
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value="/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
  <system>
     <configuration>
         <threads count="8" />
     </configuration>
  </system>
  <project name="project1">
    <modificationset>
       <svn RepositoryLocation="svn+ssh://dev/svn/project/trunk"
          username="integration" password="integration" >
    </modificationset>
  </project>
</cruisecontrol>

Если вы предпочитаете выполнять сборку из локальной копии репозитория, то можно использовать атрибут LocalWorkingCopy, как показано в листинге 5.

Листинг 5. Запуск сборки из проверенной локальной копии исходного кода
  <project name="project1">
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
  </project>

Обратите внимание на то, что в теге <svn> необходимо использовать либо атрибут RepositoryLocation, либо атрибут LocalWorkingCopy. Атрибуты username и password являются необязательными и их необходимо использовать только если этого требует система аутентификации. Для других элементов управления исходным кодом используются аналогичные теги, например, <cvs> и <vss>. Код из листинга 5 наводит на следующий интересный вопрос: если указана только локальная рабочая копия, то кто отвечает за то, чтобы она всегда была актуальной? Ответ на этот вопрос приводит нас к тегу <bootstrappers>. В зависимости от используемого репозитория тег <bootstrappers> может иметь дочерние теги, например, <svnbootstrapper> или <clearcasebootstrapper>. В листинге 6 продемонстрирована автоматическая установка системы в заданное состояние для конфигурации из листинга 5.

Листинг 6. Проверка исходного кода с помощью bootstrapper
  <project name="project1">
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
  </project>

Тег <svnbootstrapper> указывает CruiseControl на локальную копию исходного кода, к которой необходимо применить команду обновления перед запуском процесса сборки.

Итак, исходный код настроен. Теперь нужно определиться с тем, когда должен запускаться процесс сборки и какую команду следует запускать для этого. Как вы уже можете предположить, нам потребуется новый тег. Итак, встречайте тег <schedule>, использование которого демонстрируется в листинге 7.

Листинг 7. Выполнение сборки с помощью тега schedule
  <project name="project1">
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
  </project>

Тег <exec> (дочерний тег <schedule>) выполняет указанную команду или сценарий как часть процесса сборки для указанной в атрибуте workingdir директории. На случай, если сборка завершится с ошибкой, предусмотрен атрибут errorstr, с помощью которого можно задать пользовательскую строку, содержащую сообщение об ошибке. Если необходимо выполнять сборку с заданной периодичностью независимо от того, был ли изменен исходный код в репозитории, или нет, можно использовать атрибут interval тега <schedule>, в котором указывается временной интервал (в секундах) между сборками, как показано в листинге 8.

Листинг 8. Задание временного интервала (1 час) между сборками
  <project name="project1" requireModifications=no>
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule interval=3600>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
  </project>

После того как процесс сборки будет запущен, может возникнуть необходимость в отслеживании ее статуса. Вероятно, проще всего использовать для этого Web-интерфейс. Так мы подошли к рассмотрению дочернего элемента тега <project> под названием listeners. Особый интерес для нас представляет тег <currentbuildstatuslistener>, который записывает статус сборки в файл. Если требуется явно указать директорию, в которой должны создаваться log-файлы сборки, используйте для этого тег <log> и его необязательный атрибут dir. Тег <log> содержит дочерний тег <merge>, который используется для объединения различной информации интеграционного тестирования в виде файла журнала CruiseControl. Команда

<merge dir=dir-name pattern="*.xml">

объединяет все XML-файлы из директории dir-name в один log-файл CruiseControl. Это полезная функция, если вам необходимо видеть общую картину двух процессов – сборки и тестирования. Такая конфигурация приведена в листинге 9.

Листинг 9. Обновленная конфигурация
  <project name="project1" requireModifications=no>
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule interval=3600>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
    <log dir="/build/logs/project1">
       <merge dir="/build/tests/project1" pattern="*.output" />
    </log>
    <listeners>
       <currentbuildstatuslistener file="/build/staus/latest" />
    </listeners>
  </project>

Мы почти закончили, если не считать нескольких действий, выполняемых по окончании сборки. Например, может потребоваться отправлять сообщения разработчикам или поместить весь исходный код в zip-файл. Здесь нам может помочь тег <publishers>. Этот тег имеет множество дочерних тегов, позволяющих выполнять различные действия по окончании процесса сборки. Для наших целей можно использовать самый распространенный подход – отправку уведомлений по электронной почте. В этом случае тег <publishers можно использовать с его дочерним тегом <email> или <htmlemail>, как показано в листинге 10.

Листинг 10. Добавление функции уведомления по электронной почте
  <project name="project1" requireModifications=no>
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule interval=3600>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
    <log dir="/build/logs/project1">
       <merge dir="/build/tests/project1" pattern="*.output" />
    </log>
    <listeners>
       <currentbuildstatuslistener file="/build/staus/latest" />
    </listeners>
    <publishers>
        <email mailhost="smtp.myjob.com" subject="Build/Test Stats">
            <failure address="all@myjob.com" />
            <success address="developers@myjob.com" />
        </email> 
    </publishers>
  </project>

SCons

Еще одним отличным инструментом автоматизации сборки программного обеспечения является SCons. SCons означает software construction (конструирование программного обеспечения) и с легкостью заменяет собой make. Зачем же использовать SCons, если у нас уже есть make? Прежде всего make, automake, autoconf и прочие инструменты имеют специальный синтаксис, тогда как SCons основан на Python. Кроме того, синтаксис SCons намного проще запомнить, а с файлами SConstruct (аналог Makefile) проще работать. Также SCons обладает специализированной поддержкой файлов C/C++ и Java™ и обладает всей функциональностью make, используя намного меньше кода.

Приложение "Hello, World" на SCons

В листинге 11 представлен код простой программы "Hello, World", написанный на C++.

Листинг 11. Программа "Hello, World" на C++
#include <iostream>
using namespace std;

int main( )
{
  cout << "Hello World" >> endl;
}

Ниже приводится содержимое SConstruct-файла для создания исполняемого файла:

env = Environment()
env.Program(hello.cpp)

Если вы сравните это с вариантом использования make, то сразу же ощутите все преимущества SCons. Функция Program носит название builder_method и является API-интерфейсом Python, который говорит SCons о том, что нужно выполнить сборку исполняемого файла. Можно также одновременно выполнять сборку нескольких исполняемых файлов и выводить в процессе сборки различные сообщения, как показано в листинге 12.

Листинг 12. Сборка нескольких исполняемых файлов с выводом дополнительных сообщений
env = Environment()
env.Program('hello.cpp')

print "Done with building hello, on to hello2"       
env.Program('hello2.cpp')
print "Finished building hello2"

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

Полезные приемы использования SCons

Ниже приводится несколько типовых задач, которые можно выполнять при помощи SCons:

  • Присвоение исполняемому файлу произвольного имени. Например, при сборке исходного файла hello2.cpp необходимо получить исполняемый файл с названием newworld2. Для этого укажите требуемое имя в качестве первого аргумента Program:

    env = Environment()
    print "Beginning building newworld2"
    env.Program('newworld2',  'hello2.cpp')
    print "Finished building newworld2"
  • Сборка исполняемого файла из нескольких исходных файлов. Например, файл newworld2 зависит от файлов hello2.cpp и world3.cpp. В этом случае SConstruct-файл должен выглядеть следующим образом:

    env = Environment()
    print "Beginning building newworld2"
    env.Program('newworld2',  ['hello2.cpp', 'world3.cpp'])
    print "Finished building newworld2"
  • Чтобы очистить сборку, просто наберите в командной строке scons c.
  • Сборка двух исполняемых файлов, имеющих несколько общих исходных файлов. Логично выполнять компиляцию этих файлов только один раз. Вот, как можно это сделать:

    env = Environment()
    
    common = ['common1.cpp', 'common2.cpp']
    hello1_sources = ['hello1.cpp'] + common
    hello2_sources = ['hello2.cpp', 'world2.cpp'] + common
           
    env.Program('hello1', hello1_sources)
    env.Program('hello2', hello2_sources)
  • Часто бывает необходимо выполнить компоновку с определенными библиотеками, например, pthread или math. Функция Program может работать с дополнительными аргументами, содержащими переменные LIBS и LIBPATH. Ниже показано, как можно выполнить компоновку с библиотекой pthread:

    env = Environment()
    env.Program('new-exe',
                   'concurrent.cpp', 
                   LIBS = 'pthread',
            LIBPATH = ['/usr/lib', '/usr/local/lib'])
  • Что если для создания исполняемого файла требуются все cpp-файлы, хранящиеся в директории? К счастью, нет необходимости перечислять их все. Эту задачу упрощает функция Glob:

    env = Environment()
    Program('new-exe',
                   Glob('*.cpp'),
                   LIBS = 'pthread',
            LIBPATH = ['/usr/lib', '/usr/local/lib'])
  • С помощью SCons очень просто собрать статическую библиотеку. Для этого просто вызовите функцию Library:

    env = Environment()
    env.Library('arith', ['math1.cpp', 'math2.cpp', 'math3.cpp'])

    Обратите внимание на то, что внутренние механизмы SCons создают файлы math1.o, math2.o и math3.o, затем ar rc libarith.a math1.o math2.o math3.o и, наконец, ranlib libarith.a.

  • Создать совместно используемую библиотеку в SCons так же просто, как и статическую:

    env = Environment()
    env.SharedLibrary('arith', ['math1.cpp', 'math2.cpp', 'math3.cpp'])
  • Может возникнуть необходимость скомпилировать разные исходные файлы с разными опциями. Например, если необходимо передать в исходный код опцию DX86_64, означающую оптимизацию для 64-разрядных платформ, то SConstruct-файл будет выглядеть следующим образом:

    env = Environment()
    env.Object('hello.cpp', CCFLAGS='-DX86_64')
    env.Object('world.cpp', CCFLAGS='-DX86_64')
    env.Program('new-exe', ['hello.o', 'world.o']

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

  • В этой статье я упоминал о том, что имя целевого файла должно являться первым аргументом. Если вы хотите сделать все наоборот или поиграть с порядком аргументов, то можно поступить так:

    common = ['common1.cpp', 'common2.cpp']
    hello1_sources = ['hello1.cpp'] + common
    hello2_sources = ['hello2.cpp', 'world2.cpp'] + common
           
    Program(source = hello1_sources, target = 'hello1')
    Program(target = 'hello2', source = hello2_sources)

    Обратите внимание на то, что source и target – это специальные ключевые слова SCons.

  • Хотя Program, SharedLibrary и остальные функции являются членами объекта env, их можно использовать без указания префикса env. Таким образом, допустимо использовать следующий SConstruct-файл:

    common = ['common1.cpp', 'common2.cpp']
    hello1_sources = ['hello1.cpp'] + common
    hello2_sources = ['hello2.cpp', 'world2.cpp'] + common
           
    Program('hello1', hello1_sources)
    Program('hello2', hello2_sources)

Заключение

Из этой статьи вы узнали о CruiseControl и непрерывной интеграции, а также о наиболее важных тегах (<bootstrappers>, <modificationset>, <schedule> и <listeners>). Также мы применили систему SCons для сборки небольшого проекта на C++ и узнали, как выполнять сборку статических и совместно используемых библиотек. Оба этих инструмента обладают гораздо большими возможностями, чем рассматривалось в этой статье, поэтому обязательно обратитесь за дополнительной информацией к разделу Ресурсы.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=934072
ArticleTitle=Изучаем инструменты автоматизации сборки программного обеспечения нового поколения
publish-date=06142013