Actions

Building Joomla Extensions with Apache Ant

From Joomla! Documentation

Revision as of 11:37, 16 August 2011 by Rvsjoen (Talk | contribs)

This page will make an effort to provide a both consistent and flexible extensions structure, in addition to give a short explanation on how to use the included ant scripts to build and package extensions.


Contents

Component

NOT FINISHED

Module

NOT FINISHED

Plugin

NOT FINISHED

Template

NOT FINISHED

Environment

The build scripts depend on three environment variables, these values can either be set in the environment, you can tell your IDE to set them when running the script or as a last resort, you can edit common.xml and set them manually. Setting them manually is not recommended for projects with multiple participants or testing environments as the paths have to match.

SOURCEDIR

This is the absolute path to the Joomla installation you are testing the extension on

BUILDDIR

This is the path to a temporary directory used to build packages, can be a relative or an absolute path

RELEASEDIR

This is the folder in which to put the packaged extensions when script is invoked with the 'package' target

Build Targets

NOTE: The PREFIX property in the build targets has different meaning depending on the extension type, for plugins it is used to determine the plugin group, and for other types it is used to determine whether the extension is for the site or for the administrator. To make a build target with an admin extension set PREFIX to "administrator"

buildcomponent

The target to build a component takes the component name and prefix, prefix is either empty or "administrator" if we are building a component for the backend
<antcall target="buildcomponent">
  <param name="NAME" value="com_helloworld"/>
  <param name="PREFIX" value=""/>
</antcall>

buildmodule

The target to build a module takes the module name and prefix, prefix is either empty or "administrator" if we are building a module for the backend
<antcall target="buildmodule">
  <param name="NAME" value="module_helloworld"/>
  <param name="PREFIX" value=""/>
</antcall>

buildtemplate

The target to build a template takes the template name and prefix, prefix is either empty or "administrator" if we are building a template for the backend
<antcall target="buildtemplate">
  <param name="NAME" value="helloworld"/>
  <param name="PREFIX" value=""/>
</antcall>

buildplugin

The target to build a plugin takes the template name and prefix, prefix is the plugin group to which the plugin belongs
<antcall target="buildplugin">
  <param name="NAME" value="plg_helloworld"/>
  <param name="PREFIX" value="system"/>
</antcall>

Scripts

build.xml

This file is per project or per extension, whatever the user sees fit, it includes the common.xml file and specifies which extensions to build. You can have one build file per extension or you can use one build file to build many extensions. It is, however, preferable to have one build file for each extension named by the extension, so the build file for a helloworld component would be build_com_helloworld.xml or simply com_helloworld.xml. This is shown in the downloadable example packages.

<?xml version="1.0" encoding="UTF-8"?>
<project name="helloworld" default="all" basedir=".">
       <import file="common.xml"/>
       <target name="all" depends="build,sync,package"/>
       <target name="package">
               <antcall target="_package">
                       <param name="NAME" value="com_helloworld"/>
               </antcall>
       </target>
       <target name="build">
               <antcall target="buildcomponent">
                       <param name="NAME" value="com_helloworld"/>
                       <param name="PREFIX" value=""/>
               </antcall>
       </target>
       <target name="sync">
               <antcall target="_sync">
                       <param name="NAME" value="com_helloworld"/>
               </antcall>
       </target>
</project>

common.xml

This is where the magic happens, this file contains build targets to build any module, plugin, template or component. You should normally not have to modify this file unless you need to manually set the environment variables or change the overall structure of an extension.

<?xml version="1.0" encoding="UTF-8"?>
<project>
       <property environment="env"/>
       <property name="source" value="${env.SOURCEDIR}"/>
       <property name="release" value="${env.RELEASEDIR}"/>
       <property name="builddir" value="${env.BUILDDIR}"/>
       <property name="verbose" value="false"/>

       <tstamp>
               <format property="NOW" pattern="yyyyMMdd"/>
       </tstamp>

       <loadfile property="dist.revision" srcFile="./.svn/entries" failonerror="false">
               <filterchain>
                       <headfilter lines="1" skip="3"/>
                       <deletecharacters chars="\n"/>
               </filterchain>
       </loadfile>

       <condition property="version" value="${NOW}_r${dist.revision}" else="${NOW}">
                <isset property="dist.revision"/>
       </condition>

       <target name="_clean">
               <delete dir="${builddir}/${NAME}" verbose="${verbose}"/>
       </target>

       <target name="_sync">
               <sync todir="${NAME}" includeemptydirs="true">
                       <fileset dir="${builddir}/${NAME}"/>
                       <preserveintarget>
                               <include name="**/.svn/**"/>
                       </preserveintarget>
               </sync>
       </target>

       <target name="_package">
               <zip destfile="${release}/${NAME}_${version}.zip" basedir="${NAME}" excludes=".svn"/>
       </target>

       <target name="buildplugin" depends="_clean">
               <copy todir="${builddir}/${NAME}" overwrite="true" verbose="${verbose}" failonerror="false">
                       <fileset dir="${source}/plugins/${PREFIX}">
                               <include name="*${NAME}*"/>
                       </fileset>
               </copy>
       </target>
       <target name="buildmodule" depends="_clean">
               <copy todir="${builddir}/${NAME}" overwrite="true" verbose="${verbose}" failonerror="false">
                       <fileset dir="${source}/${PREFIX}/modules/${NAME}"/>
               </copy>
               <copy todir="${builddir}/${NAME}/media" overwrite="true" verbose="${verbose}"  failonerror="false">
                       <fileset dir="${source}/media/${NAME}"/>
               </copy>
               <copy todir="${builddir}/${NAME}/language" overwrite="true" verbose="${verbose}"  failonerror="false" flatten="true">
                       <fileset dir="${source}/${PREFIX}/language">
                               <include name="*/*${NAME}.ini" />
                       </fileset>
               </copy>
       </target>
       <target name="buildtemplate" depends="_clean">
               <copy todir="${builddir}/${NAME}" overwrite="true" verbose="${verbose}" failonerror="false">
                       <fileset dir="${source}/${PREFIX}/templates/${NAME}"/>
               </copy>
       </target>
       <target name="buildcomponent" depends="_clean">
               <mkdir dir="${builddir}/${NAME}/site"/>
               <mkdir dir="${builddir}/${NAME}/admin"/>
               <mkdir dir="${builddir}/${NAME}/admin/sql"/>
               <mkdir dir="${builddir}/${NAME}/media"/>
               <mkdir dir="${builddir}/${NAME}/language"/>
               <mkdir dir="${builddir}/${NAME}/language/admin"/>
               <mkdir dir="${builddir}/${NAME}/language/site"/>
               <copy todir="${builddir}/${NAME}/media" overwrite="true" verbose="${verbose}" failonerror="false">
                       <fileset dir="${source}/media/${NAME}"/>
               </copy>
               <copy todir="${builddir}/${NAME}/admin" overwrite="true" verbose="${verbose}">
                       <fileset dir="${source}/administrator/components/${NAME}"/>
               </copy>
               <copy todir="${builddir}/${NAME}/language/admin" overwrite="true" verbose="${verbose}" flatten="true">
                       <fileset dir="${source}/administrator/language">
                               <exclude name="overrides/*"/>
                               <exclude name="pdf_fonts/*"/>
                               <include name="**/*${NAME}*.ini"/>
                       </fileset>
               </copy>
               <copy todir="${builddir}/${NAME}/site" overwrite="true" verbose="${verbose}">
                       <fileset dir="${source}/components/${NAME}"/>
               </copy>
               <copy todir="${builddir}/${NAME}/language/site" overwrite="true" verbose="${verbose}" flatten="true">
                       <fileset dir="${source}/language">
                               <exclude name="overrides/*"/>
                               <exclude name="pdf_fonts/*"/>
                               <include name="**/*${NAME}*.ini" />
                       </fileset>
               </copy>
               <move file="${builddir}/${NAME}/admin/${NAME}.xml" todir="${builddir}/${NAME}" verbose="${verbose}"/>
               <move file="${builddir}/${NAME}/admin/uninstall.${NAME}.php" todir="${builddir}/${NAME}" verbose="${verbose}" failonerror="false"/>
               <move file="${builddir}/${NAME}/admin/install.${NAME}.php" todir="${builddir}/${NAME}" verbose="${verbose}" failonerror="false"/>
               <move todir="${builddir}/${NAME}/admin/sql" verbose="${verbose}">
                       <fileset dir="${builddir}/${NAME}/admin">
                               <include name="*.sql"/>
                       </fileset>
               </move>
       </target>
</project>

Example workflow

  • Test install of Joomla 1.5
  • Local svn checkout of your extension repository containing extensions in a structure like the attached example
  • Run 'ant -f com_helloworld.xml package' which builds the extension in the specified release directory
  • Install the extension on Joomla test site
  • Use the test site project to develop the extension and debug it
  • When changes are made, run 'ant -f com_helloworld.xml build sync' to first build a temporary tree in the specified build folder, then the 'sync' target will synchronize the changes between the build directory and the extension directory in the repository
  • Inspect the changes and do a svn commit
  • Now you can either package the extension with the 'package target' or continue development on the test site until a new commit is to be made