В статье описывается способ разделения логики и реализации логики в ant-скриптах, примененный для решения одной практической задаче по рефакторингу большого проекта в SVN-репозитории.
Имеется проект в SVN из 15 000 файлов и 5 000 папок. Проекту почти 10 лет, на нем поработало несколько поколений разработчиков разной квалификации. В какой-то момент, пару-тройку лет назад, а может и раньше, архитектура проекта «потекла». Разные модули и слои стали писаться в разных стандартах организации кода, возникли циклические зависимости между модулями. В итоге в SVN за долгие года образовалась свалка. Проект собирается, но совершенно шаманским способом.
Привести код к единому формату хранения. При этом сохранить историю изменений по каждому файлу и не останавливать процесс разработки.
Сохранить историю по одному файлу или папке в SVN довольно просто с помощью команды svn copy. При небольшом количестве файлов все можно сделать вручную.
С разбором большого проекта сложно. Пока будешь вручную разбирать 15 000 файлов, разработчики накоммитят новых изменений и их тоже нужно будет копировать. Замкнутый круг.
Нужна автоматизация. Скриптик, который раз! — и переводит проект в новую структуру.
Задача была выполнена, а побочным продуктом стал подход к написанию ANT-скриптов, который в большом программировании называется инкапсуляция. Хочу поделиться полученным подходом.
Исходные коды (значительно укороченные, без названий компании и продукта) доступны на гуглокоде.
Составил план действий:
Описание новой структуры из пункта один выходит за рамки данной статьи.
Скрипт для копирования можно писать на bat, bash.
Поскольку у нас используется средство сборки ANT, то я начал писать его на нем.
В моем случае для написания ANT-скрипта и получения первого успешного результата потребовалось пару недель.
Скрипт состоял из команд такого вида:
Он получился очень длинным, плохо читаемым и модифицируемым.
У него было одно достоинство — он работал. Но предстояло писать подобный файл для svn copy… А это несколько напрягало.
После напряженных раздумий, решил, что надо писать по-другому. Избавляться от copy-paste, выносить в отдельный файл маппинг и прятать реализацию в подключаемых файлах. Но как это сделать в ANT? С помощью макросов для ANT!
C помощью директивы macrodef определяем новый таск в ant.macros
Таск copy_by_pattern копирует файлы по маске include.
Код выше преобразуется в
Добиваемся того, чтобы он работал как и до этого.
А потом пишем реализацию copy_by_pattern для svn copy в svn.macros
Этот таск получает файлсет и конвертирует его локальную переменную (появились в ANT 1.8) out.script с помощью pathconvertи regexpmapper. Затем выводит фрагмент скрипта с svn copy в файл ${script.filename}
Примечание: в ANT до версии 1.8 не было локальных переменных. Записав в переменную значение, его потом нельзя изменить.
Пример фрагмента скрипта с svn copy, получаемого на выходе:
ключ --parents создаст все необходимые новые директории
У нее есть 4 варианта копирования:
Наиболее удобен вариант WC -> WC:, потому что все остальные выполняют немедленный коммит в репозиторий. Перетасовка 15 000 файлов проекта создаст 15 000 коммитов. Вариант WC -> WC позволяет делать один отложенный коммит.
Пересмотрите свои ANT-скрипты.
Если вы видите в своих ANT-скриптах много повторяющихся фрагментов,
их можно объединить в новый таск с помощью macrodef.
А заодно можете разделить логику от реализации.
Предыстория
Имеется проект в SVN из 15 000 файлов и 5 000 папок. Проекту почти 10 лет, на нем поработало несколько поколений разработчиков разной квалификации. В какой-то момент, пару-тройку лет назад, а может и раньше, архитектура проекта «потекла». Разные модули и слои стали писаться в разных стандартах организации кода, возникли циклические зависимости между модулями. В итоге в SVN за долгие года образовалась свалка. Проект собирается, но совершенно шаманским способом.
Задача
Привести код к единому формату хранения. При этом сохранить историю изменений по каждому файлу и не останавливать процесс разработки.
Сложности
Сохранить историю по одному файлу или папке в SVN довольно просто с помощью команды svn copy. При небольшом количестве файлов все можно сделать вручную.
С разбором большого проекта сложно. Пока будешь вручную разбирать 15 000 файлов, разработчики накоммитят новых изменений и их тоже нужно будет копировать. Замкнутый круг.
Нужна автоматизация. Скриптик, который раз! — и переводит проект в новую структуру.
Результат
Задача была выполнена, а побочным продуктом стал подход к написанию ANT-скриптов, который в большом программировании называется инкапсуляция. Хочу поделиться полученным подходом.
Исходные коды (значительно укороченные, без названий компании и продукта) доступны на гуглокоде.
- mapping.xml — маппинг, откуда и куда копировать, по какому правилу
- old2new.properties — глобальный конфиг.
- macros.ant.xml — подключаемый ant-файл с реализацией копирования в файловой системе
- macros.svn.xml — подключаемый ant-файл с реализацией копирования с svn copy.
История того, как я пришел к таким скриптам.
Составил план действий:
- придумать новую структуру
- написать скрипт для копирования в файловой системе проекта из старой структуры в новую
- отладиться на новой структуре, написать скрипты сборки и т.п. — отладка может занять много времени и десятков (если не сотен) запусков операций копирования в файловой системе
- написать скрипт, который готовит скрипт с svn copy
- отладиться на новой структуре в svn
- объявить час Х, прогнать скрипт и перевести всех разработчиков на новую структуру.
Реализация плана действий:
Описание новой структуры из пункта один выходит за рамки данной статьи.
Скрипт для копирования можно писать на bat, bash.
Поскольку у нас используется средство сборки ANT, то я начал писать его на нем.
В моем случае для написания ANT-скрипта и получения первого успешного результата потребовалось пару недель.
Скрипт состоял из команд такого вида:
<mkdir dir="@{to}"/>
<copy todir="@{to}" overwrite="false">
<fileset dir="@{from}">
<include name="@{include}"/>
<include name="@{include}"/>
<exclude name="@{include}"/>
</fileset>
</copy>
Он получился очень длинным, плохо читаемым и модифицируемым.
У него было одно достоинство — он работал. Но предстояло писать подобный файл для svn copy… А это несколько напрягало.
После напряженных раздумий, решил, что надо писать по-другому. Избавляться от copy-paste, выносить в отдельный файл маппинг и прятать реализацию в подключаемых файлах. Но как это сделать в ANT? С помощью макросов для ANT!
C помощью директивы macrodef определяем новый таск в ant.macros
<macrodef name="copy_by_pattern">
<attribute name="from"/>
<attribute name="to"/>
<attribute name="include"/>
<sequential>
<echo message="copy from dir @{from} to dir @{to} by pattern @{include}"/>
<mkdir dir="@{to}"/>
<copy todir="@{to}" overwrite="false">
<fileset dir="@{from}">
<include name="@{include}"/>
</fileset>
</copy>
</sequential>
</macrodef>
Таск copy_by_pattern копирует файлы по маске include.
Код выше преобразуется в
<copy_by_pattern from="${dir.input.base}/module1"
to="${dir.output.base}"
include="**"/>
<copy_by_pattern from="${dir.input.base}/module2"
to="${dir.output.base}"
include="**"/>
<copy_by_pattern from="${dir.input.base}/module3"
to="${dir.output.base}"
include="**"/>
Добиваемся того, чтобы он работал как и до этого.
А потом пишем реализацию copy_by_pattern для svn copy в svn.macros
<macrodef name="copy_by_pattern">
<attribute name="from"/>
<attribute name="to"/>
<attribute name="include"/>
<sequential>
<fileset id="localfs" dir="@{from}">
<include name="@{include}"/>
</fileset>
<local name="out.script"/>
<pathconvert property="out.script" refid="localfs" pathsep="${line.separator}">
<chainedmapper>
<identitymapper/>
<regexpmapper from="@{from}/(.*)"
to="${cli.command} @{from}/\1 @{to}/\1"
handledirsep="yes"/>
</chainedmapper>
</pathconvert>
<echo file="${script.filename}"
message="${out.script}${line.separator}"
append="true"/>
</sequential>
</macrodef>
Этот таск получает файлсет и конвертирует его локальную переменную (появились в ANT 1.8) out.script с помощью pathconvertи regexpmapper. Затем выводит фрагмент скрипта с svn copy в файл ${script.filename}
Примечание: в ANT до версии 1.8 не было локальных переменных. Записав в переменную значение, его потом нельзя изменить.
Пример фрагмента скрипта с svn copy, получаемого на выходе:
svn copy --parents D:\path_old\app.xml D:\path_new\app.xml
svn copy --parents D:\path_old\Cls.java D:\path_new\Cls.java
svn copy --parents D:\path_old\page.jsp D:\path_new\page.jsp
svn copy --parents D:\path_old\js.js D:\path_new\js.js
ключ --parents создаст все необходимые новые директории
Замечание о команде svn copy
У нее есть 4 варианта копирования:
WC -> WC: copy and schedule for addition (with history)
WC -> URL: immediately commit a copy of WC to URL
URL -> WC: check out URL into WC, schedule for addition
URL -> URL: complete server-side copy; used to branch and tag
Наиболее удобен вариант WC -> WC:, потому что все остальные выполняют немедленный коммит в репозиторий. Перетасовка 15 000 файлов проекта создаст 15 000 коммитов. Вариант WC -> WC позволяет делать один отложенный коммит.
Заключение
Пересмотрите свои ANT-скрипты.
Если вы видите в своих ANT-скриптах много повторяющихся фрагментов,
их можно объединить в новый таск с помощью macrodef.
А заодно можете разделить логику от реализации.