Phing - SvnLogTask

Phing obsahuje ve verzi 2.3 tyto Tasky pro práci se Subversion.

  • SvnCheckoutTask
  • SvnExportTask
  • SvnLastRevisionTask
  • SvnUpdateTask

To se hodí a také ve svém buildu používám tento postup:

  1. udělám export HEAD revision (SvnExportTask)
  2. načtu si číslo HEAD revision (SvnLastRevisionTask)
  3. potom vygeneruju SVN Log (SvnLogTask)
  4. vygeneruju API dokumentaci pro build (PhpDocumentorTask)
  5. všechno zakomprimuji do souboru s dokumentací a se zdrojáky a uložím kam potřebuju (ZipTask)

V postupu je něco co není standardní součástí Phingu i když si myslím, že se to tam objeví. Phing pro práci se SVN používá VersionControl_SVN 0.3.1 alpha. Bohužel neexistuje zatím stable verze této PEAR knihovny což je škoda, protože funguje celkem dobře a má implementováno vše co potřebuji. Když používáte VersionControl_SVN dá se pracovat s několika návratovými typy:

  • VERSIONCONTROL_SVN_FETCHMODE_ASSOC,
  • VERSIONCONTROL_SVN_FETCHMODE_OBJECT,
  • VERSIONCONTROL_SVN_FETCHMODE_XML,
  • VERSIONCONTROL_SVN_FETCHMODE_RAW,
  • VERSIONCONTROL_SVN_FETCHMODE_ALL,
  • VERSIONCONTROL_SVN_FETCHMODE_ARRAY

V Phingu se používá, ale výhradně VERSIONCONTROL_SVN_FETCHMODE_ASSOC což není někdy úplně vhodné. V případě, že máte totiž zapnutý přepínač pro výpis v XML je lepší návratový typ VERSIONCONTROL_SVN_FETCHMODE_XML. Proto jsem musel upravit phing\tasks\ext\svn\SvnBaseTask.php.

Tuto část

$options = array('fetchmode' => VERSIONCONTROL_SVN_FETCHMODE_ASSOC, 'svn_path' => $this->getSvnPath());

jsem nahradil tímto kódem

        // Set up runtime options. Will be passed to all
        // subclasses.
        if ($mode=="log")
        {
        $options = array('fetchmode' => VERSIONCONTROL_SVN_FETCHMODE_XML, 'svn_path' => $this->getSvnPath());       
        }
        else
        {
        $options = array('fetchmode' => VERSIONCONTROL_SVN_FETCHMODE_ASSOC, 'svn_path' => $this->getSvnPath());
        }

Po této úpravě, která jistě by šla udělat lépe. Jsem se dal do psaní vlastního tasku SvnLogTask.php. Task vrátí log z repozitory v XML formátu. Pokud chceme plain text, tak to prožene XSLT transformací a potom ještě vymaže whitespaces z celého dokumentu.

require_once 'phing/Task.php';
require_once 'phing/tasks/ext/svn/SvnBaseTask.php';

class SvnLogTask extends SvnBaseTask
{
    private $name = 'log.xml';
    private $xml = true;
    private $verbose = false;
    /**
     * The setter for the attribute "name"
     */
    public function setName($str) {
        $this->name = $str;
    }
    /**
     * The setter for the attribute "xml"
     */
    public function setXML($str) {
        $this->xml = $str;
    }   
    /**
     * The setter for the attribute "verbose"
     */
    public function setVerbose($str) {
        $this->verbose = $str;
    }       
    /**
     * The main entry point
     *
     * @throws BuildException
     */
    function main()
    {
        $this->setup('log');
        $this->log("Log SVN"); 
       
         $switches = array('verbose' => $this->verbose);
         $output=$this->run(array(), $switches);

        $doc = new DOMDocument();
        $doc->formatOutput = true;        
        $doc->loadXML($output);             
        // output format 
        
        if ($this->xml=="true")
        {        
          $doc->save($this->getToDir()."/".$this->name);
        }
        else
        {                 
         // print in plain
         $xsl = new DOMDocument;
         $xsl->load(dirname(__FILE__).'\LogTxt.xsl');
         $proc = new XSLTProcessor;
         $proc->importStyleSheet($xsl); // attach the xsl rules
         $output=$proc->transformToDoc($doc)->firstChild->wholeText;

         $pat[0] = "/^\s+/";
         $pat[1] = "/\s{2,}/";
         $pat[2] = "/\s+\$/";
         $rep[0] = "";
         $rep[1] = " ";
         $rep[2] = "";
         $after=preg_replace($pat, $rep, $output);
         $str=str_replace("\\n ","\n", $after);         
         file_put_contents($this->getToDir()."/".$this->name,$str);
        }
    }
}

Potom se to dá už použít v build.xml.

        <taskdef name="svnlog" classname="phing.tasks.ext.svn.SvnLogTask" />
        <svnlog
           svnpath="${svnpath}"
           repositoryurl="${rep}"
           name="CHANGELOG"
           xml="true"
           verbose="true"
           todir="${tmp}\log"/>

Moje vlastní XSLT transformace (phing\tasks\ext\svn\LogTxt.xsl) na plain text není přiliš vhodná pro nějaké systémové řešení. Lepší je parametr nechat zapnutý. Vzít XML a pomocí XsltFilter aplikovat vlastní transformaci.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
<xsl:template match="log">                
            CHANGELOG\n
            <xsl:apply-templates select='logentry'/>
</xsl:template>
 
 
<xsl:template match="logentry">       
            \n R<xsl:apply-templates select='@revision'/>\n
            <xsl:apply-templates select='date' /> - <xsl:apply-templates select='author'/>\n
            <xsl:apply-templates select='msg'/>\n         
</xsl:template>
 
</xsl:stylesheet>

Nakonec přidám ještě moje build.propeties a buid.xml jednoho moje projektu pro ilustraci.

# Property files contain key/value pairs
# key=value
tmp = c:\tmp
svnpath = c:\apps\svn\bin\svn.exe
rep = file:///rootwww/rep_cvut/akce/trunk
wc =  c:\rootwww\wc_cvut\akce

Tady v build.propeties si všimněte jen jediného detailu, ale ten vás může stát hodně času. Cesty k svn a k repozitory neobsahují mezery, pokud máte Subversion např. v Program Files může to nadělat více problémů než užitku a chyby se projevují různě a ne zcela systémově. Doporučuji se tomu předem vyhnout, ušetříte si čas a nervy.

<?xml version="1.0" ?>
<project name="akce2" basedir="." default="main">

    <!-- Sets the DSTAMP, TSTAMP and TODAY properties -->  
    <tstamp/>  
  
    <!-- Load our configuration -->  
    <property file="./build.properties" />
    <taskdef name="svnlog" classname="phing.tasks.ext.svn.SvnLogTask" />
    
    <property name="package"  value="${phing.project.name}" override="true" />
    <property name="builddir" value="${tmp}/build/${phing.project.name}" override="true" />
    <property name="srcdir"   value="${project.basedir}" override="true" />


    <target name="svn" description="SVN executes">
         <!-- Export HEAD copy do /tmp/ -->       
       <svnexport
       svnpath="${svnpath}"
       repositoryurl="${rep}"
       force="yes"
       todir="${tmp}\export\${phing.project.name}"/>
        <!-- Získání čísla HEAD -->
        <svnlastrevision
           svnpath="${svnpath}"
           workingcopy="${wc}"
           propertyname="svn.lastrevision"/>       
        <!-- Vygenerování aktuálního logu -->           
        <svnlog
           svnpath="${svnpath}"
           repositoryurl="${rep}"
           name="CHANGELOG"
           xml="false"
           verbose="true"
           todir="${tmp}\export\${phing.project.name}"/>    
    </target>

    <target name="phpdoc" description="API Documentation" depends="svn">
        <!-- Generování phpdoc dokumentace -->
        <phpdoc title="Akce2008 API Documentation"
          destdir="${builddir}/apidocs"
          sourcecode="yes"
          defaultpackagename="Akce2008"
          output="HTML:default:default">
           <fileset dir="${tmp}/export/${phing.project.name}">
              <include name="*/*.php" />      
              <exclude name="inc/phpmailer/**" />
              <exclude name="build/**" />
           </fileset>
            <projdocfileset dir=".">
                  <include name="CHANGELOG" />
             </projdocfileset>           
        </phpdoc>
      </target>

        <!-- Fileset for all files -->
        <fileset dir="${tmp}/export/${phing.project.name}" id="allfiles">
            <include name="**" />
            <exclude name="build.xml" />
            <exclude name="build.properties" />
        </fileset>

    <!-- Main Target -->
    <target name="main" description="main target" depends="phpdoc">
        <!-- Zdrojové kódy pro příslušnou revizi -->
        <zip destfile="${builddir}/${phing.project.name}-R${svn.lastrevision}-${DSTAMP}${TSTAMP}.zip" basedir="${tmp}/export/${phing.project.name}" />                    
        <!-- Vygenerovanou dokumentaci přesunu do ZIP s číslem příslušné revize -->
        <zip destfile="${builddir}/${phing.project.name}-apidocs-R${svn.lastrevision}-${DSTAMP}${TSTAMP}.zip" basedir="${builddir}/apidocs" />        
        <!-- Smazaní temp adresáře -->
        <delete dir="${builddir}/apidocs" includeemptydirs="true" verbose="true" failonerror="true" />

    </target>
</project>

Vlastní build se pustí pomocí phing z příkazové řádky tam kde máte build.xml uložený. Pokud se nedaří Phing nainstalovat a nakonfigurovat pro chod např. s PHPDoc, tak doporučuji postup ze stránek Phingu.

pear channel-discover pear.phing.info
pear install -a phing/phing
pear install channel://pear.php.net/VersionControl_SVN-0.3.1

Doufám, že někomu rady budou k užitku, Phing je skvělý produkt i když většina asi dá za přednost Antu pokud nedělají čistě PHP.