Hanumant’s Java Workshop

Turbo Charged Java Development!

Creating an RPM for a Java Application

If you are preparing for Oracle Java Certification, don’t forget to check out Mock Exams and Questions from Enthuware. They are the best!

Ok, so why would I want create an RPM for a Java application anyway?

Well, there can be several reasons. I wanted to deploy my application on several CentOS boxes. All these boxes are hooked up to a central repository server. To deploy any application to these appliance, the RPM for that application needs to be added on this repo server. The boxes are synched with the repo server automatically. So basically, if I add an RPM on this repo server, the boxes can easily grab it just like any other application. No user intervention required in the whole process.

The ground work

There are several online guides and tutorials that describe how to build an RPM. The ones that I read were -

  1. https://pmc.ucsc.edu/~dmk/notes/RPMs/Creating_RPMs.html
  2. http://docs.fedoraproject.org/drafts/rpm-guide-en/ch-creating-rpms.html
  3. http://genetikayos.com/code/repos/rpm-tutorial/trunk/rpm-tutorial.html
  4. http://www.ibm.com/developerworks/library/l-rpm1/

While all of the above gave me the basic concepts and the general idea of how to build an RPM package, I found several key pieces of information missing which caused confusion. Also, none of them explained it from a java developer’s perspective.  My goal in this blog is to document my findings, the information that would have saved me a week of effort had it been given in the above refered articles, for myself [it's amazing how soon you forget things :-) ], and for any other poor soul who is banging his head trying to build an RPM for a Java application.

I advise you to go through all of the above articles before reading further.

The Standard Steps

All of the following steps are to be done on a Linux box, the one where you want to build the RPM.

Step 1: Install the package rpm-build

yum install rpm-build

or if you are not logged in as root but your account is in the sudo list -

sudo yum install rpm-build

Notice that the package name is rpm-build but the command that you use for building is rpmbuild (not rpm-build) :rolleyes:

Step 2: The .rpmmacros file

You need to create this file containing the following two lines in your home directory.

%_topdir       /home/hdeshmukh/rpm
%_tmppath      /home/hdeshmukh/rpm/tmp

This file basically tells rpmbuild to use your personal account space for building the rpm instead of using shared space.

Step 3: Create the directory structure

rpmbuild requires the following directory structure under %_topdir directory -

The following command makes these directories -

$mkdir ~/rpm ~/rpm/BUILD ~/rpm/RPMS ~/rpm/RPMS/noarch ~/rpm/SOURCES ~/rpm/SPECS ~/rpm/SRPMS ~/rpm/tmp

Key Information – rpmbuild builds rpms for various CPU architectures such as i386 and i686. For a Java application, you don’t care about that. So you just need to create one directory named ~/rpm/RPMS/noarch instead of multiple directories such as ~/rpm/RPMS/i386, one for each architecture.

Key information for bundling a Java Application

Impedence Mismatch

As you know (I am assuming that you have gone through the above mentioned URLs) that rpmbuild actually 1. “builds” the sources and 2. “installs” the output of the build process.  Now, in Linux world, this basically means compiling the sources using “make”. The place where I was stuck and frustrated was the liberal reference to “make” in the above mentioned articles.  I know it is a build tool but honestly speaking, I have never used make and I do not care about it. I don’t know what exactly it does, what cfg file in what path does it need, what does it create and where it puts what it creates. The next stumbling block for me was the “install” process. All the articles refer to “make -install”. Again, I have no clue what/how/where does it install. May be there is a config file somewhere that it reads, but I don’t know.

Build Process in the Java World: In Java world, you have, for “sources”:

  1. a set of .java and .properties files (organized in some application specific directory structure)
  2. a set of third party jars
  3. an ant build.xml file that describes the build process

The build process uses ant (instead of make) i.e. ant <targetname> and it uses the build.xml to generate the final output, usually, in the form of a jar file.

Install Process in the Java World: For a simple application, you can just drop your final jar anywhere you like and just run java -jar <jarname>. For a complex application, such as an enterprise application (a war or an ear), you might have to drop the war or ear to appropriate directory of the application server. In some cases, you might even want to explode the jar to an approriate directory on the machine and then execute some command like java -classpath ./lib/a.jar:./lib/b.jar -Dx=1 -Dy=2 com.mycomp.myapp.AppStarter to run your application.

With the above discussion in reference, I will now introduce the heart of the build process, the spec file :drumroll: The lines in the bold font is the code that goes in the spec file and the lines in the regular font are my annotations.

The .spec file

Summary: Lease Alert Monitor
Name: LeaseAlert 
Should not have any space.
Version: 1
Release: 1  
Name version and release become part of the name of the output rpm file. In this case it will be LeaseAlert_1_1.rpm
License: Restricted
Group: Applications/System
BuildRoot: %{_builddir}/%{name}-root 
BuildRoot is the directory where rpmbuild will “install” the output of the “build” process (whatever that process it).  %{_builddir} points to %_topdir/BUILD/ so our BuildRoot will be ~/rpm/BUILD/leasealert-root (%_topdir is defined in .rpmacros to be ~/rpm)
URL: http://mycompany.net/
Vendor: Mycompany
Packager: Hanumant Deshmukh
Prefix: /usr/local 
In the “install” process (specifed in the spec file below), you have to specify the exact directory path where you want to install (i.e. copy the files, basically) on the machine where the application is being installed. You may believe that the application will be installed in say, /usr/local/javaapps/leasealert directory, but at the install time, the machine may not have /usr/local or the user may not want to install it there. May be the user wants to install it a /home/hdeshmukh/javaapps/leasealert The Prefix value specifies what part of your install directory is changeable by the user. So when a user installs your RPM, he can specify the prefix and the application will be installed under <prefix>/javaapps/leasealert instead of /usr/local/javaapps/leasealert.
BuildArchitectures: noarch
For java apps, you don’t care about the CPU architecture

%description
Lease Alert Monitor

%prep For java apps, there is nothing to prepare. However, in some cases, you might want to pull the sources from a source code control system. The command to pull the sources (and all the files that are required for building) and put it in the SOURCES directory (explained in build section below) should go here.

%build
pwd
This just shows where rpmbuild is executing from.
cd %{_sourcedir}  When rpmbuild reaches the build section, it is in BuildRoot directory (specified above in the beginning of the spec file). But our sources are in the SOURCES directory. %{_sourcedir} is a standard variable available in rpmbuild and in our case it points to ~/rpm/SOURCES. The goal here is to cd to the directory from where ant can find the build.xml and execute the build target.
ant tar My build.xml is in the top of the SOURCES directory and the name of my target is tar. The output of this ant command is a tar file named leasealert.tar in ~/rpm/BUILD directory. Please see the description of my ant file below to learn the contents and structure of this tar file.

%install
pwd
At this time, your current directory is BuildRoot. This is where the application will be installed on YOUR machine (i.e. the machine on which you are creating the rpm) and NOT ON end user’s machine. When the rpm is installed on the end users machine, the directory path upto BuildRoot will be removed. So if you install your app (on your rpm build machine) in <BuildRoot>/usr/local/javaapps/leasealert, when the user installs your rpm, the application will be installed in /usr/local/javaapps/leasealert. Of course, the user may specify the prefix as /home/hdeshmukh, in which case the app will be installed in /home/hdeshmukh/javapps/leasealert
rm -rf $RPM_BUILD_ROOT This is just to make sure that the BuildRoot is empty. $RPM_BUILD_ROOT points to ~/rpm/BUILD/leasealert-root in my case. Note that this command will NOT be executed on end user’s machine.
mkdir -p $RPM_BUILD_ROOT/usr/local/edns/standalonejava/leasealert The path after $RPM_BUILD_ROOT, is where you want (subject to the prefix change) the application to be installed on the end user’s machine. In my case, it is /usr/local/edns/standalonejava/leasealert. This directory will be created on end user’s machine if it does not already exist. Not because of this mkdir command here but because the rpm file will contain the application in /usr/local/edns/standalonejava/leasealert directory and will explode the contents in this directory on end user’s machine. In case of a web application, you should give the path to your application server’s document root directory.
cd $RPM_BUILD_ROOT/usr/local/edns/standalonejava/leasealert
tar -xf $RPM_BUILD_ROOT/../leasealert.tar
My install process just requires me to explode the tar file in $RPM_BUILD_ROOT/usr/local/edns/standalonejava/leasealert directory. In case of a web app, you may want to explode it in the application server’s document root directory. So you have to make that directory (in the previous step) before exploding the tar. 

%clean
rm -rf $RPM_BUILD_ROOT
This is executed after the rpm file is already built. So no need to keep this stuff anymore.

%files Here, you have to list ALL the files that you want to copy on the end user’s machine. The path to the files is as per the deployment structure used under BuildRoot. So no need to specify $RPM_BUILD_ROOT/usr/local…
%defattr(-,root,root)
/usr/local/edns/standalonejava/leasealert/leasealert.jar
/usr/local/edns/standalonejava/leasealert/leasecapacitymonitor.properties
%attr(755,root,root) /usr/local/edns/standalonejava/leasealert/run.sh
This is the shell script file that contains the java command to excute my main class along with some properties, so I want to make it executable.
/usr/local/edns/standalonejava/leasealert/lib/mail.jar

%changelog
* Tue Oct 20 2008 Hanumant
- Created initial spec file

I hope the contents of the spec file are clear. Now, run $rpmbuild -ba ~/rpm/SPECS/leasealert.spec to build the rpm. LeaseAlert_1_1.rpm should be created in /RPMS/noarch directory.

The build.xml file

The deployment structure of my standalone java application is follows -

<deploydirectory>/leasealert.jar <– contains application class files
<deploydirectory>/leasecapacitymonitor.properties
<deploydirectory>/run.sh <-shell script to run the application
<deploydirectory>/lib/mail.jar <– third party jarfiles

In case you are wondering about the contents of run.sh, it is quite simple:

#!/bin/sh
java -classpath .:leasealert.jar:./lib/mail.jar com.mycompany.MyClass

As you have probably already noticed in the spec file, I want the deploy directory to be /usr/local/edns/standalonejava/leasealert

Now, the goal of my build.xml is to compile the sources and bundle all the stuff (jar file, lib, props, and run.sh) into a single tar file named leasealert.tar. The directory structure contained within the tar file should be such that, when exploded during the “install” process of rpmbuild, it should reflect the deployment structure. The following is how I achieved it -

<project name=”leasealert” basedir=”/home/hdeshmukh/rpm/” default=”main”>

    <property name=”rpmroot.dir”     value=”/home/hdeshmukh/rpm”/>
    <property name=”lib.dir”     value=”${rpmroot.dir}/SOURCES/lib”/>
All my third party jars are here.
    <property name=”src.dir”     value=”${rpmroot.dir}/SOURCES/src”/> All .java files, and property files are here.
    <property name=”build.dir”   value=”${rpmroot.dir}/BUILD”/> This is where the the final leasealert.tar file is generated and put by ant.
    <property name=”classes.dir” value=”${build.dir}/classes”/>This is where the .class files are generated by ant.
    <property name=”jar.dir”     value=”${build.dir}/jar”/> This is where the the leasealert.jar file is generated and put by ant.

    <property name=”main-class”  value=”com.mycompany.myapp.MyClass”/>

Standard ant stuff —

    <path id=”classpath”>
        <fileset dir=”${lib.dir}” includes=”**/*.jar”/>
    </path>

    <target name=”clean”>
        <delete dir=”${build.dir}”/>
    </target>

    <target name=”compile”>
        <mkdir dir=”${classes.dir}”/>
        <javac srcdir=”${src.dir}” destdir=”${classes.dir}”  classpathref=”classpath” source=”1.6″ target=”1.6″/>
    </target>

    <target name=”jar” depends=”compile”>
        <mkdir dir=”${jar.dir}”/>
        <jar destfile=”${jar.dir}/${ant.project.name}.jar” basedir=”${classes.dir}”>
            <manifest>
                <attribute name=”Main-Class” value=”${main-class}”/>
            </manifest>
        </jar>
    </target>

    <target name=”tar” depends=”jar”> This generates the final leasealert.tar
      <copy file=”${src.dir}/run.sh” tofile=”${jar.dir}/run.sh” />
      <copy toDir=”${jar.dir}/” >
          <fileset dir=”${src.dir}”>
              <include name=”**/*.properties”/>
          </fileset>
      </copy>
      <mkdir dir=”${jar.dir}/lib”/>
      <copy todir=”${jar.dir}/lib”>
          <fileset dir=”${lib.dir}”>
              <include name=”**/*.*”/>
         </fileset>
      </copy>

      <tar destfile=”${build.dir}/leasealert.tar” basedir=”${jar.dir}”/>
    </target>

    <target name=”clean-build” depends=”clean,jar”/>

    <target name=”main” depends=”clean, tar”/>

</project>

I am assuming that you already have ant installed on your build machine. If not, you can build your stuff elsewhere and just copy the outout into the BUILD directory of rpm. You can install ant using the command- sudo yum install apache-ant

Closing Remarks

The approach that I have chosen here is to use build mechanism of rpmbuild to kickoff the ant build process. It seems, ant 1.7 has a rpmbuild task that allows you to kickoff the rpmbuild process from an ant build process. Don’t get too excited though, because you require the same spec file in this approach as well :D I have ant 1.6, which does not have rpmbuild task and I was too exhausted to upgrade it and generate an rpm using this approach. So I leave the details of how to do that as an exercise for the readers :)

As always, comments welcome!

OCAJP Mock Exams and Questions

October 22, 2008 Posted by | Java | 12 Comments

   

Follow

Get every new post delivered to your Inbox.