.. _build-system-java: ================= Java Build System ================= .. tip:: You can :download:`download the entire example project in one archive `. Required Software ================= In order to use the described build system for |java| you need the following software on your computer: * |java| compiler * `Apache ant`_ build tool * `JUnit`_ unit testing library * :ref:`RSB ` as an exemplary dependency Brief Overview ============== We will demonstrate how to set up a |java| project using the build tool `Apache Ant`_, which is a standard tool in the |java| world. :program:`ant` is configured through an XML file in the project root directory, called :file:`build.xml`. In this tutorial we will build a library with some reusable functions, which results in a ``jar`` file in |java|. Additionally, we will create an executable class which uses the functions provided in the library. As java does not distinguish between libraries and executables and each jar can contain as many executable classes as desired, we will only build a single `jar` file containing all of theses classes. We assume knowledge of the Java language, the concept of `jar` files and the classpath for finding these files. Additionally, you should have a very basic understanding of how to use the `apache ant`_ command line program. In case you need advice on this, please consult e.g. `Tutorial: Hello World with Apache Ant `_. Folder Layout ============= The folder layout of our tutorial project will look as follows. * **build-system-essentials-java**: top-level folder of our project * :file:`build.xml`: Build declarations and dependency handling * :file:`README.txt`, :file:`COPYING.txt`, ...: Files with additional textual information like license, contact information etc. * **src**: Contains the source code of our project using the typical |java| convention of a folder per package and a file per class. * **test**: Contain the unit tests for the project in a parallel hierarchy to the **src** folder. Declaring an Ant Project and Finding Upstream Dependencies ========================================================== .. seealso:: `Ant online documentation `_ Verbose explanation of every ant feature and task. We start by declaring the basic project in the :file:`build.xml`: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::project-info :end-before: mark-end::project-info :linenos: The project is declared with the ``project`` tag and needs a name. Additionally, we declare the default target :program:`ant` shall execute if a target is not explicitly specified on the command line. For this project, it makes sense to call ``dist``, which will generate a jar-file with our code. Afterwards, we declare some basic information about the project, namely version, a name for the generated jar-file and a longer description about it. All ``property`` definitions like the ones above can be changed by the caller of :program:`ant` using ``-D`` flags on the command line. This is almost always necessary, since :program:`ant` has no built-in mechanism for finding dependencies such as executable programs or jar-files automatically. Therefore, the user needs to pass in all required locations if the defaults provided in the :file:`build.xml` do not match the system's configuration. As this usually results in a lengthy command line, we will describe a second way of defining these properties using a configuration file: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::basic-properties :end-before: mark-end::basic-properties :linenos: With this declaration, :program:`ant` parses a the file :file:`build.properties` which consists or ``key=value`` definitions. :program:`ant` properties set in this way are persistent. A user of our project would typically create such a :file:`build.properties` file, specify all customizations in that file, and then just call :program:`ant` without specifying any properties. .. note:: The property file definition in :file:`build.xml` has to precede all properties that should be changeable, because: .. epigraph:: Properties are immutable: whoever sets a property first freezes it for the rest of the build -- `Ant online documentation`_ The ant command line flags always precede everything and hence can still be used to override settings from :file:`build.properties`. Now that it is possible to modify properties in a persistent manner, we can start with defining some of them for locations of external libraries: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::dependency-handling :end-before: mark-end::dependency-handling :linenos: The defaults we give here are valid for a recent |ubuntu| system. .. note:: Hamcrest is a dependency of `JUnit`_ in the version installed by |ubuntu|. Other distributions may include a standalone jar of `JUnit`_ where hamcrest is include. After fixing the locations of external libraries we declare several locations for our own source code and the folders where compilations results will go to: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::source-paths :end-before: mark-end::source-paths :linenos: As explained above, production source code and test source code are separated in different folders. It is also a good idea to separate the compilation results of the build process in different folders so that the final jar-file of the production code can easily be generated by globbing over the generated ``*.class`` files. Therefore, ``dir.build.src`` and ``dir.build.test`` are separated folders. We furthermore define where the jar-file will be generated, test reports in XML format for automatic parsing are created, and where the API documentation is generated. All location definitions in :program:`ant` are relative to the location of the :file:`build.xml` in the filesystem. Finally, we define the installation locations for our project: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::installation-paths :end-before: mark-end::installation-paths :linenos: As mentioned before, :program:`ant` does not provide any dependency checking. Even if a library location is given explicitly and does not point to a valid jar-file, e.g. for :ref:`RSB `, there will be no warning about this. Instead, compilation will fail and it may not be easy to spot what went wrong. Therefore, it is advisable to check at least the minimal dependencies manually: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::dependency-checks :end-before: mark-end::dependency-checks :linenos: If all dependencies are available, we need to construct classpaths from the collected file locations for the compiler invocation: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::classpaths :end-before: mark-end::classpaths :linenos: We define two separate classpaths for the compilation of the production source code and the unit tests. This has the advantage that unit tests accidentally leaked into the production tree will cause a compilation error. As visible in line 7, it is possible to use an existing classpath and extend it to form a new one, in this case the test classpath. Please note that for compiling the unit tests the compilation results of the production source code must be available to the compiler. This is the reason why ``dir.build.src`` is in the test classpath. Additionally, to actually execute the unit tests, also the compiled unit tests (``*.class`` files) need to be available. Therefore, also ``dir.build.test`` is listed here. Building Production Source Code =============================== In order to build a jar-file, we will first make sure that the folders where build artifacts will be generated in exists. For this purpose we define a special ``init`` target: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::init :end-before: mark-end::init :linenos: As :program:`ant` does not automatically provide a ``clean`` target, it is also a good habit to define such a target, symmetrical to the ``init`` target: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::clean :end-before: mark-end::clean :linenos: Having ensured that all necessary folders exist, we can define a target to compile the production source code: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::compile-source :end-before: mark-end::compile-source :linenos: Our target ``compile`` depends on ``init`` to have folders available. Inside ``compile`` we invoke the |java| compiler using the ``javac`` :program:`ant` task. ``javac`` uses the production source code found in ``${dir.test}`` and generates ``class`` files in ``${dir.build.test}``. Furthermore, we instruct the compiler to include debug information in the generated classes for easy debugging, enable warnings for the use of deprecated functions, and prevent :program:`ant` from providing its libraries to the compiler as recommended in the documentation. With these instructions we are able to compile our production source code. Building Unit Tests =================== In order to build the unit tests we will add a ``compile-tests`` target comparable to the ``compile`` target: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::compile-tests :end-before: mark-end::compile-tests :linenos: Instead of depending on ``init``, this target also requires that the production source code was compiled before and hence depends on ``compile``. Afterwards, we need to define another target which actually executes the compiled unit tests: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::run-tests :end-before: mark-end::run-tests :linenos: :program:`ant` provides a ``junit`` task for this purpose. We instruct the task to output test results as XML files for automatic processing, e.g. by a continuous integration server, and as output on the console for manual checks. After setting the test classpath, we instruct the task that during the execution of the unit tests, |java| assertions shall be turned on, so that assertion errors will get visible with the unit tests. Finally, we instruct the task to execute all tests found in files containing the pattern ``*Test*.java`` in their name, inside the test folder. Now, unit tests can be executed using ``ant test``. Exposing the Project to Downstream Projects =========================================== First, we have to build a jar-file from the compiled classes using the ``jar`` task provided by :program:`ant`: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::dist :end-before: mark-end::dist :linenos: Afterwards, we can install the jar-file and the license: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::install :end-before: mark-end::install :linenos: .. note:: Installing the copyright follows the conventions of Debian-based Linux distributions like |ubuntu|. Finally, we build the API documentation using :program:`javadoc`, so that :term:`downstream` developers have a human-readable reference: .. literalinclude:: example-projects/build-system-essentials-java/build.xml :language: xml :start-after: mark-start::documentation :end-before: mark-end::documentation :linenos: How to Build and Use the Project ================================ .. code-block:: sh cd path/to/build-system-essentials-java # fill build.properties with the required variables ant # build ant test # run tests ant doc # build documentation ant dist # create a jar file in dist/ subdirectory ant install # install the jar file and license ant clean # delete generated files