Documentele explicative ale produselor software sunt folosite ca manuale de referință pentru proiectanții interfețelor utilizator, pentru programatorii care scriu codul și pentru testerii care se asigură că produsul funcționează corect.
Într-o aplicație multimodulară, fiecare componentă este dezvoltată și lansată independent. Păstrarea documentației actualizate pentru fiecare componentă nu este ușoară deoarece nu totul ține doar de redactarea ei, ci și de centralizarea tuturor documentelor în așa fel încât să fie găsite ușor de către persoanele interesate.
Scopul articolului de față este prezentarea unei abordări pentru simplificarea acestui proces, bazându-se pe Build Automation, care să colecteze și să publice documentele.
Vom trece împreună prin procesul de configurare al artifactelor Apache Maven, al serverului Jenkins CI, pentru ca în final vom ajunge la crearea unui proiect ce regenerează un website care reflectă starea actuală a întregului proiect, oferind acces la toate documentele disponibile dintr-un singur loc.
Studiul nostru de caz este un framework scris în limbajul de programare Java, care este format din mai multe module Maven. Fiecare modul conține unul sau multe documente scrise în Markdown. Documentele sunt scrise de către programatori, iar acestea pot fi readme-uri, howto-uri, documentații tehnice și altele.
Înainte să începem, va trebui să ne imaginăm cum va arăta soluția finală. Ne dorim o pagină html frontală care să afișeze cele mai recente documente scrise de către programatori pentru modulele lansate.
Livrarea împreună a release note-urilor și a altor documente cu produsul software și JavaDoc reprezintă o practică obișnuită. Ele însoțesc produsul, iar cu ajutorul lor, consumatorii produsului îl pot folosi și înțelege corect.
Prin lansarea artifactelor Maven, fișierele compilate ajung într-un depozit (Repository) de cod structurat. În acest depozit pot ajunge nu doar fișierele compilate, ci și JavaDoc-ul, codul sursă sau un întreg website ce prezintă informații despre dependențele proiectului, informații despre issue tracking, integrare continuă (CI), echipă și multe altele.
Website- ul proiectului este cel mai important element în atingerea scopului nostru, pentru că acesta va agrega toate legăturile spre _website-_urile fiecărui modul într-o singură pagină html.
Imaginați-vă că pagina html arată în felul următor:
<div>
<h1>module1</h1>
<a href=”modules/module1/index.html”>About</a>
<a href=”modules/module1/Readme.html”>Readme</a>
<a href=”modules/module1/ReleaseNotes.html”>Release Notes</a>
</div>
<div>
<h1>module2</h1>
<a href=”modules/module2/index.html”>About</a>
<a href=”modules/module2/Readme.html”>Readme</a>
<a href=”modules/module2/ReleaseNotes.html”>Release Notes</a>
</div>
Acest model presupune prezența fișierelor referite pe disc, acestea fiind generate în timpul procesului.
Modulele noastre sunt artifacte Maven, iar abordarea este bazată pe Maven Build Lifecycle.
Generăm _site-_ul folosind maven-site-plugin. Folosind doar acest plugin putem genera elementele implicite, cum sunt Project Summary, Project Plugins, Dependencies și altele.
Cu puțină configurare și ajutor, putem interveni în fluxul normal al _plugin-_ului. În acest mod putem include fișierele noastre Markdown ca fișiere html, referite din _website-_ul generat.
Ca să transformăm fișierele Markdown în html, avem nevoie de d_oxia-module-markdown_ca dependență pentru acestplugin. Folosind acesta, procesul de generare al website-ului se uită în interiorul directorului src/site/markdown și convertește fiecare fișier cu extensia .md într-un fișier html.
Această etapă poate părea simplă, dar dacă fișierele noastre *.md conțin imagini, acestea sunt pur și simplu trecute cu vederea de către plugin. Acest plugin doar copiază conținutul directorului src/site/resources.
De asemenea, noi dorim ca fișierele noastre Markdown să fie accesibile programatorilor și în modul offline, ei putând oricând arunca o privire peste ele. Referirea imaginilor din directorul src/site/resources va funcționa doar în modul offline, deoarece în urma generării _website-_ului directorul resources nu va mai fi prezent, iar astfel vom avea parte de referințe defecte către imagini.Ideal ar fi să avem toate fișierele Markdown în directorul src/site/resources, referind imaginilie din src/site/resources/images, fiind mai simplu din images (ca director relativ) deoarece după generarea_website-_ului conținutul directorului images este contopit cu celelalte imagini pe care _plugin-_ul le generează în directorul target/site/images.
În concluzie, directorul src al unui modul are următoarea structură:
În interiorul fișierului Readme.md găsim referințele spre imagini:
![Alternative text]
(images/image1.jpg „Text descriptiv”)
După ce _website—_ul este generat, ne așteptăm să găsim fișierele noastre în directorul target astfel:
Cele două fișiere html ar trebui să fie referite în cadrul fișierelor html generate de către plugin. Pentru a realiza acest lucru, avem nevoie să-i comunicăm lui Maven următoarele instrucțiuni:
Înainte de a genera _website-ul, copiați toate fișierele *.md_ din src/site/resources în src/site/markdown. Atenție, directorul nou poate fi inexistent.
Generați _website-_ul pentru proiect, dar în rezultatul final este de preferat să existe referințe către documentele generate pe baza fișierelor Markdown. Avem un fișier Readme.md și unul ReleaseNotes.md, deci ar trebui să facă referire la fișierele Readme.html și ReleaseNotes.html.
Începem cu cel de-al doilea punct, prin descrierea referințelor spre viitoarele fișiere html în cadrul src/site/site.xml:
<?xml version=”1.0” encoding=”UTF-8”?>
<project xmlns=”http://maven.apache.org/DECORATION/1.4.0” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=”http://maven.apache.org/DECORATION/1.4.0 [http://maven.apache.org/xsd/decoration-1.4.0.xsd](http://maven.apache.org/xsd/decoration-1.4.0.xsd) [http://maven.apache.org/DECORATION/1.4.0](http://maven.apache.org/DECORATION/1.4.0) „>
<!-- temă, reclame și alte configurări ale site-ului -->
<body>
<!—- meniu pentru documentele programatorilor -->
<menu name=”Developer documents”>
<item name=”Readme” href=”Readme.html”/>
<item name=”ReleaseNotes” href=”ReleaseNotes.html”/>
</menu>
<!-- meniu pentru javadoc, jxr, și altele -->
<menu ref=”reports”/>
</body>
</project>
Plugin- ul citește aceste instrucțiuni și generează _website-_ul în concordanță. Am adăugat un nou meniu ce referă documentele noastre.
Primul și ultimul punct implică manipulare de fișiere, iar acest lucru este o sarcină perfectă pentru un task Ant. În Maven putem folosi maven-antrun-plugin pe care îl configurăm să execute două sarcini:
prima execuție creează directorul src/site/markdown și copiază toate fișierele *.md în interiorul lui. Acest lucru trebuie făcut înainte de începerea procesului de generare, deci în faza de pre-site;
Mai jos este prezentat fișierul pom.xml rezultat:
<project .. >
<dependencies>
...
</dependencies>
<build>
<!—- pregătește fișierele Markdown pentru Maven Site-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>pre-markdown</id>
<phase>pre-site</phase>
<configuration>
<tasks>
<delete dir=”${project.basedir}/src/site/markdown” />
<mkdir dir=”${project.basedir}/src/site/markdown” />
<copy todir=”${project.basedir}/src/site/markdown”>
<fileset dir=”${project.basedir}/
src/site/resources” includes=”**/*.md” />
</copy>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
<execution>
<id>post-markdown</id>
<phase>site</phase>
<configuration>
<tasks>
<delete dir=”${project.basedir}/src/site/markdown” />
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<!—generatorul de site e legat de sectiunea de raportare -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.doxia</groupId>
<artifactId>doxia-module-markdown
</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
</plugin>
</build>
<reporting>
<outputDirectory>${project.build.directory}/site</outputDirectory>
<!—configurare pentru alte lucruri legate de raportare, cum sunt maven-javadoc-plugin, maven-jxr-plugin -->
</reporting>
</project>
Cu ajutorul comenzii mvn site
website-ul dorit va fi generat în directorul target/site.
Următoarea fază în atingerea scopului nostru este să împachetăm website- ul generat într-un fișier jar care să fie încărcat în Maven Repository.
Plugin- ul site știe cum să creeze un fișier jar pe baza fișierelor din directorul target/site. Tot ce trebuie să facem este să apelăm mvn site:jar, dar cu o singură remarcă: faza pre-site este executată doar dacă apelăm mvn site
, fără scopul: jar. Pentru a fi siguri că fișierele Markdown sunt luate în considerare chiar și când directorul target este gol sau inexistent, ar trebui să apelăm mvn site site:jar
.
Rezultatul este un fișier jar nou, target/module1-site.jar. Ca să putem considera acest pas complet, mai trebuie să încărcăm acest fișier jar în Maven Repository. Acest lucru este posibil cu ajutorul Maven Deploy Plugin.
Scopul acestui proiect este agregarea tuturor resurselor disponibile într-un singur website. Pe lângă documentațiile modulelor, el poate ține și documente generale, cum ar fi primii pași ai programatorilor care intră pe proiect sau documentații tehnice de ansamblu. Pentru acestea, maven-site-plugin poate fi aplicat folosind aceeași manieră ca și în cazul modulelor.
Pentru a descărca website- urile generate folosim Maven Dependency Plugin. Acesta ne ajută să obținem artifactele și fișierele *-site.jar încărcate la pasul anterior. Scopul nostru aici este să dezarhivăm toate aceste fișiere în interiorul directorului target/site/modules, iar astfel putem menține structura dorită pentru website.
Pentru a obține arhivele *-site pentru module, toate trebuie declarate ca dependențe ale proiectului resurselor în fișierul pom.xml:
<project .. >
<dependencies>
<!-- module 1 -->
<!-- module 2 -->
<!-- ... -->
<dependencies>
<build>
<plugins>
<!-- antrun pentru a genera html suplimentar din markdown -->
<!-- (!) -->
<!-- groovy plugin pentru a executa operții de intrare/ieșire pe disc, explicate în secțiunile -->
<!-- (!) -->
<!-- site plugin pentru a genera site-ul proiectului actual -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin
</artifactId>
<executions>
<execution>
<id>sites-modules</id>
<phase>compile</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<classifier>site</classifier>
<!—acesetea sunt importante, aici se enumeră toate artifactele care sunt biblioteci ale proiectului (module), separate prin virgulă (,) -->
<includeArtifactIds>module1, module2, ...</includeArtifactIds>
<failOnMissingClassifierArtifact>false
</failOnMissingClassifierArtifact>
<outputDirectory>
${project.build.directory}/site/modules
</outputDirectory>
<useSubDirectoryPerArtifact>true
</useSubDirectoryPerArtifact>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Acest plugin va dezarhiva conținutul fiecărui website de modul într-un subdirector separat din cadrul target/site/modules al proiectului resurselor.
Ultima chestiune importantă aici este proiectarea fișierului index.html în așa fel încât el va conține referințe către toate sub-site-urile modulelor. Pentru că modulele noastre sunt versionate, vrem ca proiectul resurselor să-și dea seama singur de căile către sub-site-uri. Dacă facem pagina de index dinamică, putem adăuga foarte ușor un script care populează pagina cu conținutul corespunzător, prin declararea unui vector într-un fișier .js separat, ca și în exemplul de mai jos:
var modules = [
„module1-1.3-SNAPSHOT-site-jar”,
„module2-1.5-site-jar”,
...
];
Codul JavaScript poate utiliza vectorul modules și introduce următoarele elemente DOM în pagina index:
<div>
<h1>module1</h1>
<h2>Version 1.3-SNAPSHOT</h2>
<a href=”modules/module1-1.3-SNAPSHOT-site-jar/index.html”>About</a>
<a href=”modules/module1-1.3-SNAPSHOT-site-jar/Readme.html”>Readme</a>
<a href=”modules/module1-1.3-SNAPSHOT-site-jar/ReleaseNotes.html”>Release Notes</a>
</div>
<div>
<h1>module2</h1>
<h2>Version 1.5</h2>
<a href=”modules/module2-1.5-site-jar/index.html”>About</a>
<a href=”modules/module2-1.5-site-jar/Readme.html”>Readme</a>
<a href=”modules/module2-1.5-site-jar/ReleaseNotes.html”>Release Notes</a>
</div>
Fișierul nostru modules.js este populat în timpul procesului de build al proiectului resurselor cu ajutor din partea groovy-maven-plugin. Scopul acestuia este acela de a executa un cod care iterează prin directoarele din cadrul target/site/modules și imprimă numele lor în fișierul /site/config/modules.js, iar în acest fel noi obținem vectorul de căi spre module. Codul poate fi citit mai jos:
...
<plugin>
<!—imprimă în config/modules.js numele directoarelor corespunzătoare -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
<![CDATA[println(„==== Creează config/modules.js ====”);
File modFile = new File(„${project.build.directory}/site/config/modules.js”);
BufferedWriter modWriter = new BufferedWriter(new FileWriter(modFile));
modWriter.writeLine(„var modules = [„);new File( „${project.build.directory}/site/modules”).eachDir() { dir -> modWriter.writeLine(„’” + dir.getName() + „’,”);
}
modWriter.writeLine(„];”);
modWriter.close();
]]>
</source>
</configuration>
</execution>
</executions>
</plugin>
...
Prin apelarea mvn site site:jar în cadrul proiectului resurselor obținem arhiva website- ului dorit. Această arhivă poate fi mai apoi încărcată într-un Server Web HTTP și făcută accesibilă tuturor celor interesați.
Având toate modulele configurate și proiectul resurselor creat, toate comenzile mvn pot fi apelate cu ușurință de către Jenkins CI, iar_website-_ul final poate fi încărcat pe un Server Web HTTP ca pas post build. De fiecare dată când un modul este lansat, sub-website- ul lui este publicat, iar website- ul principal poate fi regenerat. În acest fel ne asigurăm că documentațiile cele mai recente sunt disponibile programatorilor, fără a fi nevoie de niciun efort sau intervenție suplimentară. Toate acestea sunt făcute în spiritul integrării continue.