от Petar Tahchiev(26-08-2005)

рейтинг (42)   [ добре ]  [ зле ]

Printer Friendly Вариант за отпечатване

Въведение Защо да се занимаваме с разучаването на ANT ?

Представете си, че във вашата фирма разработвате определен софтуер. Всеки ще се съгласи, че това не е внезапен процес, а старателно планиране и поетапно внедряване на нова функционалност. Веднъж написан даден фрагмент от код почти никога не остава същият докрая на проекта. Ето защо е необходимо да разполагаме със средство за автоматизиране на всички странични процеси освен освен самото писане на код. Apache Ant е проект на Jakarta с отворен код, и е безценен помощник на програмиста в тази насока. За да ви опиша напълно колко полезен е той нека да разиграем примерен сценарий. Имаме някакъв софтуерен проект(уеб-услуга), който се намира някъде във Source Control системата. Пишейки един прост скриптов файл ние бихме могли всеки път когато Ant го изпълни да свалим проекта от Source Control системата в някоя папка на локалния си компютър, да компилираме проекта, да стартираме тестовете, които има и да побликуваме резултатите от тях на уеб-сървара си(или да попълним SQL таблица с резултатите), да пакетираме отделните модули(като EJB-тата и други) в Jar файлове, да пакетираме уеб модулите в War файл, цялото съдаржание да пакетираме в един Ear файл, да копираме Ear файла на един отдалечен, презокеански(например) сървър, и накрая да изпратим резултатите по електронната поща до себе си за да се уверим, че всичко е протекло нормално. И не само това - поставяйки .xml файла в Source Control системата вие помагате на вашите колеги от екипа, защото всеки от тях би могъл да направи гореописаната процедура на собственият си компютър. Докато чакаме Ant да се погрижи за всичко през това време можем да вършим допълнително друга работа, като при това със сигурност ще спестим време, ако извършвахме всички операции поотделно сами.


Малко повече за това какво всъщност е Ant.

Ant е билд туул специално пригоден за Java, но може да бъде използван и в други езици. В момента може да бъде използван буквално за всяка задача. Предимствата му са, че е написан изцяло на Java, което веднага го прави платформено независим, приема иструкциите си под формата на xml файл (build.xml), което пък улеснява боравенето и подръжката изключително много. Освен това разполага с богата документация и мейл листа за потребителите, където можете да задавате вашите въпроси. Ant прилича много на едно средство за автоматизиране - Make, но за разлика от Make, Ant е значително по-лесен за използване.
Процеса по инсталирането е максимално опростен: единственото, което трябва да направите е да свалите от сайта на Jakarta архива и да го разархивирате в подходящата директория. Препоръчвам ви да си направите системна променлива $ANT_HOME която да сочи към мястото където сте разархивирали архива и да я добавите към системната променлива $PATH, за да може да извиквате Ant от всяка директория, в която се намирате. Освен това трябва да сте дефинирали променилвата $JAVA_HOME до мястото където е инсталирана вашата Java.


Да започваме!

Ако сте убедени, че Ant(съкращение от англ. Another Neat Tool) ще улесни значително живота ви, можем да пристъпим към основната част на днешната статия, а именно как да опишем стъпките, през който искаме да преминем. За целта трябва да създадем xml документ, с името build.xml. Не е задължително да кръщаваме файла си по този начин, но това също си има предимства. Работата е там, че за да стартираме Ant от командния ред обикновено пишем:

ant

Тогава той ще провери текущата директория за файл на име build.xml и ако го намери ще го изпълни, докато ако кръщаваме файловете си по друг начин трябва изрично да подчертаем с атрибута -buildfile, кой файл искаме да се изпълни:

ant -buildfile myfile.xml


Както предполагам знаете, всеки xml документ трябва да има един обхващащ таг за всички тагове, които са в него. Build.xml също не прави изключение. Тук заглавния таг се нарича <project> и може да приема различен брой параметри.

 <project name="MyProject" basedir="." default="all">
 ..........
 </project>
 

name параметъра задава името на проекта, а basedir посочва в коя директория ще се развиват действията по-надолу в проекта. basedir може също да се разглежда като пропърти, което сме дефинирали в project-а. default посочва от кой таргет(за таргети и пропъртита ще говорим по-надолу) ще започне изпълнението на проекта. Тук е уместно да спомена, че единствено атрибута default е задължителен.
Във файла, който създаваме отделните задачи, които искаме да се изпълнят трябва да опишем в отделни таргети, който зависят в изпълнението си един от друг. Веднага ще ви дам пример:


 <target name="compile" depends="init" description="Compiles the source code">
 ........
 </target>
 

Може би се досещате, че атрибута depends указва от кой таргет зависи нашия. В нашия пример таргета compile зависи от таргета init, което ще рече че за да се изпълни таргета compile първо трябва да се е изпълнил таргета init. По този начин можем да подредим таргетите си по приоритет на изпълнение: първо свали сорс кода от Source Control системата, след това го компилирай, и т.н. Всеки таргет приема различни параметри. description (от нашия пример) не е параметър, а задава кратко описание на това каква задача изпълнява отделния таргет. description-и не само, че могат да се слагат на всеки таргет (и на всеки проект), но е много добра практика, която силно препоръчвам. Ползата е в това, че ако трябва да разчитате чужд файл(както често се случва) много по-бързо ще се ориентирате. Отделно във всеки таргет използвайки различни таскове задаваме какво точно включва операцията компилиране(например). Ето отново един пример:


 <javac srcdir="/home/user/MySourceFolder/" destdir="/home/user/MyDestinationFolder"/>

С този таск се оказва всички *.java файлове, който се намират в директорията srcdir да се компилират в директорията destdir. Тук е уместно да спомена една особеност за компилирането с Ant, а именно Ant има таск, с чиято помощ НЕ компилира файлове, който не са променяни от последната компилация(още едно доказателство, че с Ant найстина пестим време).

Когато пътят до една директория е дълъг и тя се налага да се използва често в нашия файл е уместно да дефинираме така наречените пропъртита. Пропъртитата имат свойствата донякъде на константни символи, които ние дефинираме още в началото на самия файл. Ето един един пример, от който ще ви стане ясно за какво става дума:

 <property name="src.dir" value="/Some/Realy/Long/URL/To/One/Of/Your/Folders"/>
 
 <property name="tomcat.home" value="/The/SPECIFIC/URL/TO/TOMCAT'S/HOME/DIRECTORY"/>
 

На първия ред ние дефинираме едно пропърти, под името src.dir и задаваме за негова стойност съдържанието на полето value. Всеки път когато се наложи да използваме директорията посочена в атрибута value, можем да я достъпваме с помощта на името на пропъртито оградено в ${}. Например:

 <copy file="${src.dir}/MYFile.java" todir="${tomcat.home}/webapps"/>,
 

което ще копира файла MYFile.java от /Some/Realy/Long/URL/To/One/Of/Your/Folders/ директорията в директорията webapps на Томкет инсталацията.
Друга причина да използваме пропъртита е показана в по-предния пример на втория ред. Там дефинираме отново едно пропърти, но тук с друга цел, а именно когато всеки един човек от екипа ви си свали build.xml файла, той може да промени настройките на пропъртитата си според това как е конфигурирана машината, на която той работи(съгласете се, че не всички инсталират Томкет в една и съща директория). По този начин файла е преизползваем. Когато пропъртитата станат прекалено много е удобно да ги изнесем в отделен файл(това е файл с разширение .properties според Java стандартите) или още по-добре за да намалим броя им да вземем директно някои от системните променливи(пропъртита). Това могат да бъдат например $TOMCAT_HOME, $ANT_HOME, $USER, $JAVA_HOME, $PATH и всички други. За повече информация ви препращам към документацията на Ant.

По-важни таскове в Ant

В тази част ще се спра на детайлното раглеждане на някои от по-полезните таскове в Ant.

mkdir. Това е таскът с който се създават директории. Със сигурност ще ви се наложи да създавате директории, най-малкото за да има къде да си държите компилираните файлове, а и за прегледност. Синтаксиса му е до болка прост:

 <mkdir dir="theNameOfMyDir"/>.  
 

Като резултатът от изпълнението ще е нова директория с името theNameOfMyDir. Тук може освен име на директория да посочим и път до директория.

delete. Обратно на mkdir изтрива отделен файл, група от файлове, или директория. Параметрите, които може да приема са
  • file - с него указваме файлът, който желаем да изтрием
  • dir - указва директорията, която искаме да премахнем.
  • verbose - в зависимост от това дали е true или false отпечатва/неотпечатва в конзолата кои файлове трием.(по подразбиране стойността му е true)
  • includeEmptyDirs - ако му зададем стойност true и използваме fileset, ще изтрие и директориите без файлово съдържание.
  • fileset- дефинира група от файлове, който да бъдат изтрити.
Пример:

 <delete>
 	<fileset dir="../temp">
 		<include name="*.*"/>
 		<exclude name="*.java"/>
 	</fileset>
 </delete>
 
 
Ще изтрие всички файлове от директорията ../temp, които нямат разширение java.

jar. Създава jar архивен файл, като автоматично се грижи да създаде директорията META-INF/ и файла MANIFEST.MF, който трябва да е в нея. Параметрите, които може да према са:
  • jarfile - името на файла, който искаме да създадем
  • basedir - името на директорията, която да бъде използвана за document root
  • includes/excludes - в тези два модела задаваме, разделени със запетайки, кои директории ще се /не ще се включват в basedir атрибута.
  • manifest - името на манифест файла, който желаем да използваме(ако има такъв).
Пример:

 <jar jarfile="${dest.dir}/myBrandNewJarFile.jar" basedir="."/>,
 
 
като резултатът след изпълнението му ще е файла myBrandNewJarFile.jar, който ще се намира в директорията ${dest.dir}(това е дефинирано пропърти както вече сте се досетили). Файловата структура в архива ще е същата както в текущата директорията, защото basedir атрибута дефинира точно това - коя папка да бъде document root за архива.

war. Създава war архив. Параметрите, които приема са:
  • warfile - името на(или и пътя на) war файла, който искаме да създадем
  • webxml - името на web.xml файла, който искаме да използваме
  • basedir - директорията, кото ще използваме за document root
  • includes/excludes - дефинират дадени модели, по който ще включваме/изключваме директории от/в атрибута basedir
  • manifest - пътя до MANIFEST.MF файла, който искаме да използваме
  • classes - пътя до classes директорията, която искаме да използваме
  • lib - пътя до lib директорията, която искаме да използваме
  • webinf - пътя до WEB-INF директорията, която искаме да използваме
Пример:

 <war warfile="myWarFile" basedir="." webinf="./WEB-INF" webxml="../web.xml">
 	<lib dir="../lib"/>
 	<classes dir="../classes"/>
 </war>
 

move. Има подобен синтаксис на copy, който вече разгледахме, но за разлика от него не копира, а премества файл от една папка в друга.

 <move file="./myFile.myExt" todir="${my.dir}"/>
 

junit таска се използва за да се стартират unit тестове. Този таск е много често използван на практика, и затова ще го разгледам по-подробно. Junit е framework, с който се пишат unit тестовете за Java. По-важните параметри, които може да приема таска са:
  • printsummary - в зависимост от това дали е on/off отпечатва/неотпечатва статистика за всеки TestCase, които изпълним.
  • fork - в зависимост от това дали е on/off ще стартира тестовете в отделна виртуална машина от тази, в която върви Ant.
Тага junit може да приема и вложени таскове(ако се чудите какви са погледнете примера за този таск). По-важни вложени таскове:
  • formatter - задава формата, в който ще бъдат отпечатани резултатите от изпълнението на тестовете.
    • type - задава формата на файловете. Приема стойности xml или plain(за текстови файлове).
    • usefile - true/false указват дали да използва файл за резултата или не.
  • test - стартира отделен junit тест.
    • name - задава името на клас-файл на теста, който ще искаме да се изпълни.
    • fork - yes/no за това дали да използва нова виртуална машина, в която да стартираме тестовете.
    • haltonfailure - ако този атрибут е поставен на "on", Ant прекратява изпълнението на build.xml-а при провал на някой тест.
    • todir - името на директорията, в която ще пазим резултатите от тестовете.
  • batchtest - стартира група junit тестове, който сме указали с fileset.
    • fork - yes/no за това дали да използваме нова виртуална машина, в която да стартираме тестовете.
    • haltonerror - когато го поставим на "on" спираме изпълнението на build.xml-а при провал на някой от тестовете
    • todir - директорията, в която ще пазим резултатите.
Пример:

 <target name="test" depends="compile">
 	<junit printsummary="true" fork="yes">
 		<formatter type="xml"/>
 		<test name="myFirstTest" todir="${reports}"/>
 		<classpath>
 			<fileset dir="${lib}">
 				<include name="**/*.class"/>
 			</fileset>
 		</classpath>
 	</junit>
 </target>
 

Тук сме дефинирали един таргет, чиято задача е да изпълни тестовете, посредством junit таска, който е в него. printsummary="true" показва, че ще отпечатваме статистика за всеки файл. fork сме указали да е yes, следователно тестовете ни ще се изпълнят в отделна виртуална машина. Освен това формата на файловете, които са резултат от изпълнението на таска, ще е xml. По-надолу ще се изпълнят тестовете от testcasemyFirstTest в директорията ${reports}, и добавяме към класпътя на junit таска класовете, които сме компилирали с таска compile. Ако забравим да добавим компилираните класове, който ще тестваме към класпътя, тестовете ще върнат грешка(но няма да се провалят).

junitreport Както забелязахме с junit таска можем да получим резултатите от тестовете в xml формат или в текстов формат. С <junitreport> таска обаче можем да трансформираме, получените xml резултати в спретната html страница, след което много лесно да проверим кой тест е успял и кой не. Параметрите, които приема са следните:
  • tofile - указваме име на файл, в който да държим списък на всички файлове, генерирани от Junit(по подразбиране този файл е TESTS-TestSuites.xml и обикновено няма нужда да указваме друг).
  • todir - с този параметър посочваме къде да запишем файла указан с tofile параметъра.
Вложени параметри:
  • fileset - с този вложен таск дефинираме колекцията от файлове, които желаем да бъдат преобразувани.
  • report - това е таска, който генерира html файловете. Той от своя страна приема също вложени параметри.
    • format - приема стойности "frames"/"no frames" и с него указваме дали браузъра ни поддържа frames(по подразбиране атрибута е frames)
    • todir - тук трябва да укажем директорията, в която да бъдат записани изходящите html файлове
Пример:

 <junitreport todir="./xmlFiles">
 	<fileset dir="./xmlFiles">
 		<include name="TEST-*.xml"/>
 	</fileset>
 	<report format="frames" todir="../reports"/>
 </junitreport>	
 
 

Тук сме дефинирали junitreport таска, който просто взима файловете от директорията ./xmlFiles, които отговарят на шаблона "TEST-*.xml", и ги превръща в html файлове в директорията "../reports"

ant. Когато проекта стане голям е смислено да разделим нашия билд файл на по-малки файлове, всеки от които отговаря за правилното компилиране и деплойване на отделен модул от проекта. В тази постановка ще ни трябва и един общ билд файл който да отговаря за последователното извикване на всеки един от отделните buildфайлове. Това извикване става именно с таска ant. Синтаксиса му е следния:

<ant antfile="the name of the ant file" />

а параметрите които приема са:
  • antfile името на файла, който ще извикаме
  • dir пътя до файла, който ще извикаме
  • target задава специфичен таргет, от файла който ще извикаме
  • output задаваме име на файл, в който да запишем резултата от изпълнението

След всички тези приказки е време да разгледаме и коментираме един цялостен файл.

 <project name="MyFirstProject" basedir="." default="all">
 	<description>My First Ant File</description>
 
 	<target name="init" depends="" description="Initializes the needed properties">
 		<property name="src.dir" value="${basedir}/src"/>
 		<property name="dist.dir" value="${basedir}/classes"/>
 		<property name="tomcat.home" value="/home/peter/jakarta/tomcat"/>
 	</target>
 	<target name="prepare" depends="init" description="Makes some directories">
 		<mkdir dir="${dist.dir}"/>
 	</target>
 	<target name="compile" depends="prepare" description="Compiles the source code">
  		<javac srcdir="${src.dir}" destdir="${dist.dir}"/>
 	</target>
 	<target name="package" depends="compile" descriptoin="Makes a jar file from the compiled source">
 		<jar jarfile="${basedir}/myJar.jar" basedir="${dist.dir}"/>
 	</target>
 	<target name="clean" depends="compile" description="Deletes the output of Ant">
 		<delete dir="${dist.dir}" verbose="true"/>
 	</target>
 	<target name="deploy" depends="clean" description="Copies the already created jar to Tomcat's webapps dir">
 		<copy file="${basedir}" todir="${tomcat.home}"/>
 	</target>
 	<target name="all" depends="deploy" description="Created Just for calling the deploy target"/>
 </project>
 


Да го разгледаме сега по-обстойно. Файлът започва с глобален елемент <project>, който показва, че името му е MyFirstProject, а изпълнението му трябва да започне от all таргета(според атрибута default), и дефинира едно глобално пропърти за текущата директория. Първия таг от проекта е description. Той показва как да задаваме кратко описание на целия проект. Да разгледаме таргетите една по една от горе надолу. Първия дефинира три променливи:srd.dir, dist.dir и tomcat.home. Обръщам внимание на факта, че този таргет не зависи от никои, и понеже е извикван от prepare той ще се изпълни първи. Следващият таргет зависи от init и създава една директория, в която ще компилираме сорс кода си, компилирането се извършва в следващия таргет. compile зависи от prepare и компилира сорс кода от директорията src.dir в директорията dist.dir. По-надолу таргета package както подсказва описанието и името му архивира директорията ${dist.dir} във файл myJar.jar, който се намира в ${basedir}. Добавили сме и един таргет който да изтрие всички създадени от Ant фаилове и директории. Предпоследния таргет просто копира току-що създадения war файл в директорията webapps на Tomcat. Последния таргет all е интересен с това, че той пръв ще се изпълни(default="all"), зависи от други таргети и няма тяло. Тоест той съществува единствено за да "извика" таргета deploy.

Накрая ми се иска да поговорим за един много често срещан проблем в практиката. Така наречените групи от файлове/директории. Често ние искаме да групираме даден брой файлове по някакъв признак(например искаме да компилираме само тези фаилове който съдържат в името си Test) в този случай се налага да се прибегне до fileset. Синтаксиса е следния:

 <fileset dir=".">
 	<include name="includeName"/>
 	<exclude name="excludeName"/>
 </fileset>
 
В този пример създаваме група от файлове в текущата директория. Групата е с атрибути include и exclude. В include задаваме някакъв модел(шаблон), по който искаме файловете, които отговарят на модела да се включат към групата. Обратно на това в exclude задаваме модела, по който ще разграничим файловете който не трябва да влизат в групата. По същия начин можем да групираме и директории, но тага ще е <dirset>. Има още много начини да групираме файлове. Тук ще споменем само path тага. Той има следния синтаксиз:

 <path id="compile.cp">
 	<pathelement location="/home/user/jars/myJar.jar"/>
 	<pathelement path="${classpath}"/>
 </path>
 
Понеже в един файл можем да имаме много групи, атрибута id задава уникален идентификатор на всяка от тях. Важно е да запомним, че с pathelement указваме местоположението на ЕДИН файл или директория, който искаме да включим към групата, а с path и посочвайки id-то на групата сме в състояние да включим цяла група, която вече е дефинирана, към нашата група. Групите от файлове се използват когато извършваме операции над файловете(копиране, преместване, изтриване, сменяне на permission и др.), когато изпълняваме тестове, когато компилираме:

<javac srcdir="mySourceDirectory" destdir="myDestinationDirectory" classpathref="compile.cp"/>


Където compile.cp е някаква група от файлове, които трябва да се добавят към класпътя за да е успешно компилирането.

Полезни съвети

Накрая искам само да подчертая няколко добри практики, които е добре да спазваме. Пишете description-и. Това е една много добра практика както вече отбелязах и може би в началото ще ви се струва излишна загуба на време(от собствен опит го казвам), но всичко се възвръща с течение на времето, особено когато проекта стане много голям. Друга практика, която силно ви препоръчвам е да спазвате някаква конвенция. Някои таргети се използват постоянно в едни същи проекти и за именуването им има утвърдени шаблони, които е добре да запомним и прилагаме. По-долу привеждам списък с някои от тях.

 init - за първоначално дефиниране на пропъртитата.		
 prepare - за поготовка на файловата структура(създаване на директории, копиране, и т.н)
 fetch - за сваляне на файлове от Source Control системата.
 compile - за компилиране на сорс-кода
 test - за изпълнението на test-case-вете
 package - за пакетиране на компилирания код
 deploy - за деплойване на някой сървър
 publish - за публикуване на резултатите някъде
 clean - за изчистване на междинните резултати
 all - обикновено се използва за default таргет, който не изпълнява никакви таскове.
 

Още една препоръка, която много хора често пренебрегват. Става въпрос за това да се пишат коментарите(xml коментарите) и description-те на английски език. Аз лично нямам нищо против българския, дори напротив, но е добре да помним че, естеството на работата на програмиста е свързано пряко с английския език и едва ли ние ще сме единствените хора, който ще четем кода си.


Заключителни думи.

Ant е проект с отворен код, който се поддържа от голяма група програмисти, и който постоянно се обогатява откъм таскове. В момента почти няма Java IDE, което да е без вградена подръжка за Ant. Възможностите му далеч не се изчерпват само с това, което разгледахме, а задачаите, която Ant може да изпълни граничат единствено с .

Искрено се надявам да съм успял поне малко да ви убедя в предимствата на Ant и да съм ви показал част от красотата на този проект. За тези от вас, които са заинтересувани съм привел няколко полезни връзки. На добър час!


Изказвам искрена благодарност на Христина Тахчиева за многобройните полезни съвети и препоръки при оформянето на статията.



Списък на полезните връзки в интернет:
http://ant.apache.org Това е официалния сайт на Ant. Има много полезна информация, детайлно ръководство и mail-list, към който може да се абонирате.


---------------------------------
Петър Тошев Тахчиев - София, 24 Август 2005 година


<< Крайна цел: FreeBSD | T2 Project има нужда от помощ >>