For Java Developers

Packaging Java software has specifics which we will try to cover in this section aimed at Java developers who are already familiar with Java language, JVM, classpath handling, Maven, pom.xml file structure and dependencies.

Instead we will focus on basic packaging tools and relationships between Java and RPM world. One of the most important questions is: What is the reason to package software in RPM (or other distribution-specific formats). There are several reasons for it, among others:

  • Unified way of installation of software for users of distribution regardless of upstream projects

  • Verification of authenticity of software packages by signing them

  • Simplified software updates

  • Automatic handling of dependencies for users

  • Common filesystem layout across distribution enforced by packaging standards

  • Ability to administer, monitor and query packages installed on several machines through unified interfaces

  • Distribution of additional metadata with the software itself such as licenses used, homepage for the project, changelogs and other information that users or administrators can find useful

Example RPM Project

RPM uses spec files as recipes for building software packages. We will use it to package example project created in previous section. If you didn’t read it you don’t need to; the file listing is available here and the rest is not necessary for this section.

Directory listing
Makefile
src
src/org
src/org/fedoraproject
src/org/fedoraproject/helloworld
src/org/fedoraproject/helloworld/output
src/org/fedoraproject/helloworld/output/Output.java
src/org/fedoraproject/helloworld/input
src/org/fedoraproject/helloworld/input/Input.java
src/org/fedoraproject/helloworld/HelloWorld.java

We packed the project directory into file helloworld.tar.gz.

Example spec file
# Comments start with hash sign. Caution: macros in comments are still
# expanded, which can lead to unexpected results. To stop macro from expanding,
# double its percent sign (e.g. %%mvn_install)

# First part usually consists of metadata such as package name, version, and
# much more
Name:		helloworld
Version:	1.0
Release:	1%{?dist}
Summary:	This is a minimal spec file example

License:	GPLv2+
# Homepage URL of the project
URL:		http://www.fedoraproject.org

# Our source archive. %{name} expands to 'helloworld' so the resulting
# tarball name would be 'helloworld.tar.gz'.
Source0:    %{name}.tar.gz

# Packages that contain only architecture independent files, such as shell
# scripts or regular Java programs (not JNI libraries), should be marked as 'noarch'
BuildArch:  noarch

# Project's build time dependency. We don't really need JUnit, it just
# serves as and example
BuildRequires: junit

%description
Very short description since we have nothing to say.

%prep
# section for preparation of sources, applying patches
# or other things which can be done before running the build
# The macro setup is used to unpack sources
%setup -q

%build
# Section for compiling and generally assembling the final pieces.
# Our Makefile builds the project JAR file
%make_build

%install
# Installation into directory prepared by RPM expressed as %{buildroot}
install -p -m 644 helloworld.jar %{buildroot}%{_javadir}/helloworld.jar

# We use macro %jpackage_script to generate wrapper script for our JAR
# Will be explained in later sections
%jpackage_script org.fedoraproject.helloworld.HelloWorld "" "" %{name} helloworld true

# List of files that this package installs on the system
%files
%{_javadir}/helloworld.jar
%{_bindir}/helloworld

%changelog
* Tue Mar 19 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1.0-1
- This is first version

RPM spec files contain several basic sections:

Header, which contains:
  • Package metadata such as its name, version, release, license…​

  • A Summary with basic one-line summary of package contents

  • Package source URLs denoted with Source0 to SourceN directives

    • Source files can then be referenced by %SOURCE0 to %SOURCEn, which expand to actual paths to given files

    • In practice, the source URL shouldn’t point to a file in our filesystem, but to an upstream release on the web

  • Patches - using Patch0 to PatchN

  • Project dependencies

    • Build dependencies specified by BuildRequires, which need to be determined manually.

    • Run time dependencies will be detected automatically. If it fails, you have to specify them with Requires.

    • More information on this topic can be found in Dependency handling section.

Description
  • Few sentences about the project and its uses. It will be displayed by package management software.

Prep section
  • Unpacks the sources using setup -q or manually if needed

  • If a source file doesn’t need to be extracted, it can be copied to build directory by cp -p %SOURCE0 .

  • Apply patches with %patchX, where X is the number of patch you want to apply. (You usually need the patch index, so it would be: %patch0 -p1)

Build section
  • Contains project compilation instructions. Usually consists of calling the projects build system such as Ant, Maven or Make.

Optional %check section
  • Runs projects integration tests. Unit test are usually run in %build section, so if there are no integration tests available, this section is omitted

Install section
  • Copies built files that are supposed to be installed into %{buildroot} directory, which represents target filesystem’s root

Files section
  • Lists all files, that should be installed from %{buildroot} to target system.

  • Documentation files are prefixed by %doc and are taken from build directory instead of buildroot

  • Directories that this package should own are prefixed with %dir

Changelog
  • Contains changes to this spec file (not upstream)

  • Has prescribed format. To prevent mistakes in format, it is advised to use tool such as rpmdev-bumpspec from package rpmdevtools to append new changelog entries instead of editing it by hand

To build RPM from this spec file save it in your current directory and run rpmbuild:

$ rpmbuild -bb helloworld.spec

If everything worked OK, this should produce RPM file ~/rpmbuild/RPMS/x86_64/helloworld-1.0-1.fc18.x86_64.rpm. You can use rpm -i or dnf install commands to install this package and it will add /usr/share/java/helloworld.jar and /usr/bin/helloworld wrapper script to your system. Please note that this specfile is simplified and lacks some additional parts, such as license installation.

Paths and filenames might be slightly different depending on your architecture and distribution. Output of the commands will tell you exact paths

As you can see to build RPM files you can use rpmbuild command. It has several other options, which we will cover later on.

Except building binary RPMs (-bb), rpmbuild can also be told to:

  • build only source RPMs (SRPMs), the packages containing source files which can be later build to RPMs (option -bs)

  • build all, both binary and source RPMs (option -ba)

See `rpmbuild’s manual page for all available options.

Querying repositories

Fedora comes with several useful tools which can provide great assistance in getting information from RPM repositories.

dnf repoquery is a tool for querying information from RPM repositories. Maintainer of Java packages might typically query the repository for information like "which package contains Maven artifact `groupId:artifactId`".

Find out which package provides given artifact
$ dnf repoquery --whatprovides 'mvn(commons-io:commons-io)'
apache-commons-io-1:2.4-9.fc19.noarch

The example above shows that one can get to commons-io:commons-io artifact by installing apache-commons-io package.

By default, dnf repoquery uses all enabled repositories in DNF configuration, but it is possible to explicitly specify any other repository. For example following command will query only Rawhide repository:

$ dnf repoquery --available --disablerepo \* --enablerepo rawhide --whatprovides 'mvn(commons-io:commons-io)'
apache-commons-io-1:2.4-10.fc20.noarch

Sometimes it may be useful to just list all the artifacts provided by given package:

$ dnf repoquery --provides apache-commons-io
apache-commons-io = 1:2.4-9.fc19
jakarta-commons-io = 1:2.4-9.fc19
mvn(commons-io:commons-io) = 2.4
mvn(org.apache.commons:commons-io) = 2.4
osgi(org.apache.commons.io) = 2.4.0

Output above means that package apache-commons-io provides two Maven artifacts: previously mentioned commons-io:commons-io and org.apache.commons:commons-io. In this case the second one is just an alias for same JAR file. See section about artifact aliases for more information on this topic.

Another useful tool is rpm. It can do a lot of stuff, but most importantly it can replace dnf repoquery if one only needs to query local RPM database. Only installed packages, or local .rpm files, can be queried with this tool.

Common use case could be checking which Maven artifacts provide locally built packages.

$ rpm -qp --provides simplemaven-1.0-2.fc21.noarch.rpm
mvn(com.example:simplemaven) = 1.0
mvn(simplemaven:simplemaven) = 1.0
simplemaven = 1.0-2.fc21

Quiz for Java Developers

  1. How would you build a binary RPM if you were given a source RPM?

  2. What is most common content of Source0 spec file tag?

  3. What is the difference between Version and Release tags?

  4. How would you apply a patch in RPM?

  5. Where on filesystem should JAR files go?

  6. What is the format of RPM changelog or what tool would you use to produce it?

  7. How would you install an application that needs certain layout (think ANT_HOME) while honoring distribution filesystem layout guidelines?

  8. How would you generate script for running a application with main class org.project.MainClass which depends on commons-lang jar?