Apache Ant
Ant is an installation tool similar to make build that creates a directory structure and fills it with files as directed by a special buildfile called
build.xml
. This file is written in XML and structured around a sequence of
targets defined using the XML
<target>
tag.
This is not a full primer on Ant; it's only enough to understand how the e-Lab deployment scripts work. The deploying developer will invoke
quarkcat/deploy-from-svn
, which in turn calls
quarkcat/internal-deploy-from-svn
, which is what invokes Ant and its
build.xml
file.
Targets are like waypoints along the build process. When you invoke Ant while supplying the name of a target, Ant will open
build.xml
and execute the file up to that target. A targets defined in a
build.xml
file must specify any other targets it depends on; when completing a target, Ant completes these subtargets first.
For our deployment script,
build.xml
is found in the root directory of the repository, or
quarkcat/sw/i2u2svn/build.xml
.
The script
internal-deploy-from-svn
invokes Ant with the target
"deploy-all-clean"
. From the build file, this target is defined as
<target name="deploy-all-clean" depends="distclean, deploy-all">
</target>
Note that there is no content inside this particular set of
<target>
tags. Essentially, this target is simply shorthand for "complete the
distclean
target, then complete the
deploy-all
target" (subtargets are always completed in the order listed). In brief,
distclean
erases the existing website installation, while
deploy-all
re-installs it from the fresh local copy of the repository that
internal-deploy-from-svn
checked out.
The complete chain of target dependencies in the buildfile is shown below:
<nicer image to be added later>
/ clean-jars
deploy-all-clean --> / distclean --> | clean-classes
\ deploy-all \ clean-web
|
|
| / deploy-cosmic \
| | deploy-cms |
| -------> | deploy-cms-tb | --> do-deploy-* --> / build-* --> build-common
| deploy-ligo | \ do-deploy-common --> / build-common
| deploy-embedded / \ clean-jars
| ---------------
\ deploy-i2u2
where "
*" represents each of
cosmic
,
cms
,
cms-tb
,
ligo
, or
embedded
, as appropriate. That is,
deploy-cosmic
depends on
do-deploy-cosmic
, which depends on
build-cosmic
, etc. The target
deploy-i2u2
has no dependencies and is the last target to be executed during a typical deployment.
The first target to be completed,
distclean
, removes files from the live website directories within
quarkcat/sw/tomcat/webapps/elab/
(recall that files served to the client are within
tomcat/webapps/ROOT/
).
-
clean-jars
deletes the Java archive .jar
files in elab/WEB-INF/lib/
-
clean-classes
removes the Java .class
files in elab/WEB-INF/classes/
-
clean-web
removes .html
and .jsp
files in the elab/
directory itself.
After this cleaning, the next fully-executed target invoked by
internal-deploy-from-svn
is
build-common
.
target build-common
The full text of the
build-common
target is
<target name="build-common">
<mkdir dir="common/build"/>
<delete quiet="true">
<fileset dir="common/build"
includes="**/*.class"/>
</delete>
<javac srcdir="common/src/java" destdir="common/build" debug="true">
<classpath refid="cp-common"/>
<compilerarg value="-deprecation"/>
</javac>
<!-- the properties file -->
<copy todir="common/build">
<fileset dir="common" includes="elab.properties"/>
<filterset>
<filter token="container.home" value="${container.home}"/>
<filter token="webapps" value="${webapps}"/>
<filter token="elab.name" value="${elab.name}"/>
<filter token="vds.home" value="${vds.home}"/>
<filter token="portal.home" value="${portal.home}"/>
</filterset>
</copy>
</target>
There are three main components of this task:
- Prepare the
common/build/
directory
- Compile
.java
files within the common/src/java/
directory into .class
files within common/build/
- Copy configuration files into
common/build/
1. Prepare common/build/
<mkdir dir="common/build"/>
<delete quiet="true">
<fileset dir="common/build"
includes="**/*.class"/>
</delete>
<mkdir dir="common/build"/>
creates the directory
common/build/
. Relative paths are interpreted with respect to wherever
build.xml
is, so this will be
quarkcat/sw/i2u2svn/common/build/
. The directory
common/build/
is not included in the repository, so it will not exist the first time you deploy from a fresh working copy; it will typically already exist afterwards, though. The
<mkdir>
task does not overwrite an existing directory, so if
common/build/
does already exist, the
<delete>
tag clears away unwanted files so that we can write fresh ones. The
<fileset>
tag defines the set of files to be deleted, in this case everything within
common/build/
that is a
.class
file.
The
quiet="true"
attribute of the
<delete>
tag keeps these deletions from being written to the terminal output. The double-asterisk ** syntax is an Ant pattern-matching wildcard that can stand for any number of directories at once (the usual
*
can stand for only one directory name at a time). Thus,
**/.class
matches any
.class
file within the specified root directory of the fileset, no matter how many directories deep it is. In other words, this construction makes the delete recursive within
common/build/
.
2. Compile .java source files into common/build/
<javac srcdir="common/src/java" destdir="common/build" debug="true">
<classpath refid="cp-common"/>
<compilerarg value="-deprecation"/>
</javac>
The
<javac>
task compiles a Java source tree. The
srcdir
attribute gives the source directory, the location of the Java files to be compiled. The
destdir
attribute is the destination directory, the place to store the output
.class
files. From inspection, you can see that the directory structure under
srcdir="common/src/java"
mirrors that of
destdir="common/build"
; the
<javac>
task recursively scans for and compiles each
.java
file within
common/src/java/
and places the output
.class
file in the corresponding location in
common/build/
. Only if a
.java
file has no corresponding
.class
file, or if the
.java
file is
newer than its output
.class
file, will
<javac>
compile that source.
Compare source:
quarkcat/sw/i2u2svn/common/src/java/
|--be/telio/mediastore/ui/upload/MonitoredDiskFileItem.java
| ...
|--gov/fnal/elab/<lots of subdirectories and .java files>
|--org/
|--gryphyn/common/catalog/replica/ElabRC.java
|--mindrot/BCrypt.java
with destination:
quarkcat/sw/i2u2svn/common/build/
|--be/telio/mediastore/ui/upload/MonitoredDiskFileItem.class
| ...
|--gov/fnal/elab/<lots of subdirectories and .class files>
|--org/
|--gryphyn/common/catalog/replica/ElabRC$OneElementSet$1.class
| ElabRC$OneElementSet.class
| ElabRC.class
|--mindrot/BCrypt.class
Note that, by far, the bulk of the action is within the
gov/fnal/elab/
directory, representing the
gov.fnal.elab
Java package. I don't know what the deal is with the
ElabRC$*.class
files within the
common/build/org/gryphyn/
subdirectory. Perhaps remnants of an earlier experiment?
The
debug="true"
option specifies that debugging information should be printed to the terminal, based on the value of
debuglevel
, which does not appear to be set in
build.xml
. Presumably it can be passed in as an argument when the buildfile is invoked outside of
internal-deploy-from-svn
.
The tag
<classpath refid="cp-common"/>
establishes
"cp-common"
as the class path for this particular Java compilation, as if one had used the
-classpath
flag when invoking
javac
from the command line. The parameter
cp-common
itself is defined earlier in
build.xml
:
<path id="cp-common">
<fileset dir="common/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${container.home}/${container.libs}">
<include name="**/*.jar"/>
</fileset>
<pathelement location="common/build"/>
<pathelement location="common/resources/classes"/>
</path>
The file
quarkcat/sw/i2u2svn/build.properties
defines
${container.home} = quarkcat/sw/tomcat/
(i.e., the Tomcat installation directory) and
${container.libs} = lib
. Thus, the
<fileset>
tasks define all
.jar
files within the
quarkcat/sw/i2u2svn/common/lib/
and
quarkcat/sw/tomcat/lib/
directories as part of the path
cp-common
. In addition, the
<pathelement>
tasks add the specific directories
common/build/
and
common/resources/classes/
identified by the
location
attribute. The Java class path for
build-commons
<javac>
task is therefore the four directories
quarkcat/sw/
|--i2u2svn/common/
| |--build/
| |--lib/
| |--resources/classes/
|--tomcat/lib/
or
sw/i2u2svn/common/build/
:
sw/i2u2svn/common/lib
:
sw/i2u2svn/common/resources/classes
:
sw/tomcat/lib
(though not necessarily in that order). Note that
tomcat/lib/
is
outside the repository, whose root directory is
i2u2svn/
. These are standard Tomcat
.jar
files included with the installation (i.e., you'll never need to edit them or worry about them).
Back to
build-common
:
<compilerarg>
gives values to pass to the Java compiler, as if provided at the command line. The one here,
-deprecation
, tells
<javac>
to provide descriptions when deprecated members or classes are invoked.
3. Copy config files into common/build/
<copy todir="common/build">
<fileset dir="common" includes="elab.properties"/>
<filterset>
<filter token="container.home" value="${container.home}"/>
<filter token="webapps" value="${webapps}"/>
<filter token="elab.name" value="${elab.name}"/>
<filter token="vds.home" value="${vds.home}"/>
<filter token="portal.home" value="${portal.home}"/>
</filterset>
</copy>
[NB: This section is problematic. As written, it appears to accomplish several different varieties of nothing.]
After compilation,
build-common
copies files to the
common/build/
directory, as given by
todir="common/build"
. The files to be copied are given by the
<fileset>
task, which specifies
i2u2svn/common/
as its root and includes any file named
elab.properties
within this root as an element of the fileset. Since the include attribute is not recursive, this copies
only the file
common/elab.properties
into the directory
common/build
.
However, the file common/elab.properties
does not exist in the repo. Furthermore, the destination
i2u2svn/common/build/
directory includes no
elab.properties
file, before or after the execution of
build.xml
. This element of the buildfile copies nothing in both theory and in practice.
[There are the files
quarkcat/sw/i2u2svn/common/resources/classes/elab.properties
and
quarkcat/sw/local-settings/common/resources/classes/elab.properties. The latter does not exist in the repository, only in local environments. Changes to it, however, are reflected in
quarkcat/sw/i2u2svn/common/resources/classes/elab.properties
after deployment. Be aware of this and figure out how it works.]
As part of the
<copy>
task, the buildfile defines a set of
<filter>
tags that alter the contents of the copied files while in transit. It works like this:
In the source file, any string that you intend to be changed is called a
token and should be bracketed with "@" symbols:
@token.example@
. During copying, the
<copy>
task looks for
token
attributes defined within
<filter>
tags and replaces any tokens that match them with the corresponding
value
attributes. In the above, for example, any copied files that contain the text "@container.home@" will have that string replaced with whatever
${container.home}
evaluates to when the copies are written to
common/build/
(
${container.home}
is defined in
i2u2svn/build.properties
; mine, for instance, is
container.home=/Users/jgriffith/quarkcat/sw/tomcat8
). The original files are not altered.
The tokens given here (i.e.,
@container.home@
) never appear anywhere within the
quarkcat/
folder, so I can't figure out what purpose these serve. In particular, none of them appear in any of the
elab.properties
files that appear in the installation, and only "elab.name" and "vds.home" appear in uncommented form in them (and even then without "@" token delimiters).
target build-<elab>
When
build-common
completes, Ant returns to the sequence of
build-<elab>
targets
build-cosmic
,
build-cms
,
build-cms-tb
,
build-ligo
and
build-embedded
("cms-tb" is the CMS test beam, which is no longer an active e-Lab, since CMS has real beam to work with now. I have no idea what "embedded" is, though). Since all of these targets are highly similar,
build-cosmic
suffices as an example for all of them.
Note that when the chain of targets hits
deploy-<elab>
, the invocation of
do-deploy-<elab>
is not simply a
depends
attribute, but a more elaborate
<antcall>
task that functions much the same as
depends
, but which allows the e-lab name to be passed as a parameter, like so:
<target name="deploy-cosmic">
<antcall target="do-deploy-cosmic">
<param name="elab.name" value="cosmic"/>
</antcall>
</target>
The variable
elab.name
will appear later in the chain.
The full code for example target
build-cosmic
is
<target name="build-cosmic" depends="build-common" description="--> builds COSMIC">
<mkdir dir="cosmic/build"/>
<javac srcdir="cosmic/src/java" destdir="cosmic/build" debug="true">
<include name="**/*.java"/>
<classpath refid="cp-common"/>
<classpath refid="cp-cosmic"/>
<compilerarg value="-deprecation"/>
</javac>
<copy todir="common/build">
<fileset dir="cosmic" includes="elab.properties.cosmic"/>
<filterset>
<filter token="container.home" value="${container.home}"/>
<filter token="webapps" value="${webapps}"/>
<filter token="elab.name" value="${elab.name}"/>
<filter token="vds.home" value="${vds.home}"/>
<filter token="portal.home" value="${portal.home}"/>
</filterset>
</copy>
</target>
First, the target creates the directory
quarkcat/sw/i2u2svn/cosmic/build/
. Then it compiles
.java
files from
cosmic/src/java/
into
cosmic/build/
.
Compare source:
quarkcat/sw/i2u2svn/cosmic/src/java/gov/fnal/elab/
|--cosmic/<.java files and subdirectories with .java files>
|--usermanagement/CosmicElabUserManagementProvider.java
|--impl/CosmicDatabaseUserManagementProvider.java
with destination:
quarkcat/sw/i2u2svn/cosmic/build/gov/fnal/elab/
|--cosmic/<.class files and subdirectories with .class files>
|--usermanagement/CosmicElabUserManagementProvider.class
|--impl/CosmicDatabaseUserManagementProvider.class
[Note that
cosmic/build/gov/fnal/elab/cosmic/
also contains classes with $ in the filename that don't correspond to source files in
cosmic/src/java/gov/fnal/elab/cosmic/]
Classpaths are defined as
cp-common
and
cp-cosmic
. We saw
cp-common
before when it was used by
build-common
, but
cp-cosmic
looks like
<path id="cp-cosmic">
<fileset dir="cosmic">
<include name="lib/**/*.jar"/>
</fileset>
<pathelement location="cp-cosmic/build"/>
</path>
This is problematic for several reasons.
First, it defines a
<fileset>
of all
.jar
files within
cosmic/lib/
(recursive) as part of the path.
There is no cosmic/lib/
in the repository. Second, the
<pathelement>
location
cp-cosmic/build
does not exist; there is no
cp-cosmic/
directory. If this is a typo and
cosmic/build/
is the intended path element, this wouldn't make sense because
cosmic/build/
does not exist until
build-cosmic
creates it immediately prior in the deployment process; obviously there won't be any class files there for
<javac>
to find. Thus, the classpath
cp-cosmic
is void, as defined.
Finally, the file
cosmic/elab.properties.cosmic
is copied into
common/build/
with filters as noted. Again,
the file cosmic/elab.properties.cosmic
does not exist to be copied. The only files by this name in the repository are in the obsolete directories
config/www13/home/quarkcat/sw/local-settings/cosmic/resources/classes/elab.properties.cosmic
config/www17/home/quarkcat/sw/local-settings/cosmic/resources/classes/elab.properties.cosmic
config/www18/home/quarkcat/sw/local-settings/cosmic/resources/classes/elab.properties.cosmic
These three files have the nearly identical content
data.dir = /disks/i2u2/cosmic/data ## 17 and 18 only
data.dir = /disks/i2u2-dev/cosmic/data ## 13 only
#override the default user management provider
provider.usermanagement = gov.fnal.elab.usermanagement.impl.CosmicDatabaseUserManagementProvider
analysis.runtime.estimator.set = gov.fnal.elab.cosmic.estimation.CosmicAnalysisRunTimeEstimatorSet
target do-deploy-common
With target
build-cosmic
complete, the buildfile executes target
do-deploy-common
. This target depends on
build-common
, which was completed as a prerequisite to
build-cosmic
(and to
build-<elab>
generally) and on
clean-jars
, which was completed as a prerequisite of
distclean
.
<target name="do-deploy-common" depends="build-common, clean-jars">
<mkdir dir="${container.home}/${webapps}/elab"/>
<mkdir dir="${container.home}/${webapps}/elab/capture" />
<mkdir dir="${container.home}/${webapps}/elab/capture/img" />
<mkdir dir="${container.home}/${webapps}/elab/WEB-INF"/>
<mkdir dir="${container.home}/${webapps}/elab/WEB-INF/classes"/>
<mkdir dir="${container.home}/${webapps}/elab/WEB-INF/lib"/>
<mkdir dir="${container.home}/${webapps}/elab/${elab.name}"/>
<copy todir="${container.home}/${webapps}/elab/capture">
<fileset dir="capture" includes="*" />
</copy>
<copy todir="${container.home}/${webapps}/elab/WEB-INF/lib">
<fileset dir="common/lib" includes="**/*.jar"/>
</copy>
<copy todir="${container.home}/${webapps}/elab/WEB-INF">
<fileset dir="common/resources" includes="**/*.*"/>
</copy>
<copy todir="${container.home}/${webapps}/elab/WEB-INF/classes">
<fileset dir="common/build" includes="**/*.*" excludes="elab.properties"/>
</copy>
<concat destfile="${container.home}/${webapps}/elab/WEB-INF/elab.properties" force="no">
<fileset dir="common/build" includes="elab.properties*"/>
</concat>
<copy todir="${container.home}/${webapps}/elab/${elab.name}">
<fileset dir="common/src/jsp" includes="**/*.*"/>
</copy>
</target>
First, this target creates a set of new directories. With
container.home = /Users/jgriffith/quarkcat/sw/tomcat8
,
webapps = webapps
, and
elab.name=cosmic
(for this example), the directories
quarkcat/sw/tomcat8/webapps/elab/
quarkcat/sw/tomcat8/webapps/elab/capture/
quarkcat/sw/tomcat8/webapps/elab/capture/img/
quarkcat/sw/tomcat8/webapps/elab/WEB-INF/
quarkcat/sw/tomcat8/webapps/elab/WEB-INF/classes/
quarkcat/sw/tomcat8/webapps/elab/WEB-INF/lib/
quarkcat/sw/tomcat8/webapps/elab/cosmic/
(individual installations mayl differ on
container.home
, of course).
Note that unlike the
build/
directories, these are located outside of the repository structure of
i2u2svn/
and in the Tomcat directory, where the live files are stored.
Next (and last), the target copies files according to
source |
what |
destination |
capture/ |
everything in capture/ |
elab/capture/ |
common/lib/ |
all .jar files (recursive) |
elab/WEB-INF/lib/ |
common/resources/ |
everything |
elab/WEB-INF/ |
common/build/ |
everything except common/build/elab.properties |
elab/WEB-INF/classes/ |
common/src/jsp |
everything |
elab/cosmic/ |
Note that
there are no such files as common/build/elab.properties*
.
In addition, there's one
<concat>
task
<concat destfile="${container.home}/${webapps}/elab/WEB-INF/elab.properties" force="no">
<fileset dir="common/build" includes="elab.properties*"/>
</concat>
that takes all files of the form
common/build/elab.properties*
(say,
elab.properties
and
elab.properties.cosmic
,
which - again - don't exist) and concatenates them into a single file
tomcat8/webapps/elab/WEB-INF/elab.properties
. In the event that this destination file exists and is newer than any of the source files, the
force="no"
attribute prevents this more up-to-date file from being overwritten.
Also note that in the post-deployment environments of my localhost (dvt4) and
i2u2-prod,
the destination file tomcat8/webapps/elab/WEB-INF/elab.properties
does not exist.
target do-deploy-<elab>
From
do-deploy-common
, the chain of
<elab>
deployment returns to the e-Lab-specific
do-deploy-<elab>
, or
do-deploy-cosmic
in the current example.
<target name="do-deploy-cosmic" depends="build-cosmic, do-deploy-common">
<mkdir dir="${container.home}/${webapps}/elab/${elab.name}"/>
<copy todir="${container.home}/${webapps}/elab/WEB-INF">
<fileset dir="cosmic" includes="lib/**/*.jar"/>
</copy>
<mkdir dir="cosmic/resources"/>
<copy todir="${container.home}/${webapps}/elab/WEB-INF">
<fileset dir="cosmic/resources" includes="**/*.*"/>
</copy>
<copy todir="${container.home}/${webapps}/elab/WEB-INF/classes">
<fileset dir="cosmic/build" includes="**/*.*"/>
</copy>
<copy todir="${container.home}/${webapps}/elab/${elab.name}" overwrite="true">
<fileset dir="cosmic/src/jsp" includes="**/*.*"/>
</copy>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/graphics"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/jsp"/>
</exec>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/users"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/jsp"/>
</exec>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/graphics"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/references"/>
</exec>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/graphics"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/test"/>
</exec>
</target>
gadgegggfdsg
This target begins by making the directory tomcat8/webapps/elab/cosmic. It then copies all .jar files from within cosmic/lib/ (recursive) into tomcat8/webapps/elab/WEB-INF/.
Then, it makes the directory cosmic/resources/
Example: deploy-cosmic
For example, here's the first subtarget,
deploy-cosmic
:
<target name="deploy-cosmic">
<antcall target="do-deploy-cosmic">
<param name="elab.name" value="cosmic"/>
</antcall>
</target>
<target name="do-deploy-cosmic" depends="build-cosmic, do-deploy-common">
<mkdir dir="${container.home}/${webapps}/elab/${elab.name}"/>
<copy todir="${container.home}/${webapps}/elab/WEB-INF">
<fileset dir="cosmic" includes="lib/**/*.jar"/>
</copy>
<mkdir dir="cosmic/resources"/>
<copy todir="${container.home}/${webapps}/elab/WEB-INF">
<fileset dir="cosmic/resources" includes="**/*.*"/>
</copy>
<copy todir="${container.home}/${webapps}/elab/WEB-INF/classes">
<fileset dir="cosmic/build" includes="**/*.*"/>
</copy>
<copy todir="${container.home}/${webapps}/elab/${elab.name}" overwrite="true">
<fileset dir="cosmic/src/jsp" includes="**/*.*"/>
</copy>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/graphics"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/jsp"/>
</exec>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/users"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/jsp"/>
</exec>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/graphics"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/references"/>
</exec>
<exec executable="ln" logError="true">
<arg value="-s"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/graphics"/>
<arg value="${container.home}/${webapps}/elab/${elab.name}/test"/>
</exec>
</target>
The target
deploy-cosmic
itself simply performs an
antcall
task, which is a way of passing off to a subtarget that allows you to include parameters (the
depends
keyword of the
<target>
tag doesn't allow parameter passing). Inside the
<antcall>
tag we define the parameter name-value pair
"elab.name"
and
"cosmic"
. When the subtarget
do-deploy-cosmic
executes, it will replace all instances of
elab.name
with the string
"cosmic"
.
do-deploy-cosmic
will, in order, complete the subtarget
build-cosmic
, complete the subtarget
do-deploy-common
, and then execute the tasks within its
<target>
tags.
-- Main.JoelG - 2016-05-03