Build Managers

Steven J Zeil

Last modified: Sep 15, 2020
Contents:

Abstract

A build manager is a tool for scripting the automated steps required to produce a software artifact.

We will start this module by looking at what types of services we would like to obtain from build managers.

These will be motivated by looking at some sample projects to consider the steps required to build them. An important lesson will be that builds often involve more that the “obvious” reuqirement of compiling and linking the code.

We will then survey some of the options for build managers, including scripting, IDE project managers, and dependency-based and task-based build management tools.


What Should a Build Manager Do?

A good build manager should be

1 Structural Architecture of a Development Project

Let’s talk about how development projects are typically organized into files, directories, etc.

1.1 Projects and Sub-projects

A project consists of one or more more sub-projects.


What Constitutes a Sub-project?

A sub-project is generally defined as the code and data that yields a single deliverable.

Examples of deliverables include

Example: The AlgAE project has sub-projects

sub-project deliverable
algae-client-server algae-4.1.jar
algae-cppserver libalgaecpp.a
algae-referenceManual referenceManual.pdf
demos/FordToppBST FordToppBST.zip
demos/ReferenceManualJava algae-jrefman.jar

Why divide a project into multiple sub-projects rather than into multiple smaller independent projects?

1.2 The Project Directory

Typically contains

1.2.1 Example: AlgAE

Top-level directory contains:

1.3 Sub-Project Directory (Java)

Contains:

1.3.1 Apache Project Directories

The Apache Foundation hosts many open source projects, which organize their projects & sub-projects like this:

src/     # anything supplied/edited by the programmers
target/  # initially empty, holds products of the compilation/build

The src/ directory is split into separate directories for the "real’ code and for the test code.

src/
|  main/   # things that contribute directly to the deliverable
|  test/   # things used for testing but not delivered
target/

“Deliverables” are usually an archive of some kind.

The division of the source files into separate main/ and test/ makes it easier to eventually construct those deliverable archives because we won’t treat entire directories worth of stuff uniformly, rather than having to select desired materials on a file-by-file basis.


src/main/ is further subdivided:

src/
|  main/
|  |  java/      # Java source code, compiled into target/classes
|  |  resources/ # data files that will be included in the deliverable archive
|  |  data/      # data files required during build but not part of deliverable
|  test/
target/
|  classes/ # data and compiled code that are packed into the .jar deliverable
|  project.jar # the deliverable

(These directories can be omitted if they are empty.)

Java libraries and applications can read data from files within their own distribution archive with only slightly more difficulty than reading from an ordinary file. To do so, the Java code is written to search the Java CLASSPATH, the same path used to hunt for the compiled Java code.

They cannot, however, write to those data files. The data access is read-only.


The src/test/ directory is split in an analogous fashion:

src/
|  main/
|  |  java/
|  |  resources/
|  |  data/
|  test/
|  |  java/      # Java source code, compiled into target/test-classes
|  |  resources/ # data files, available during testing via CLASSPATH
|  |  data/      # test data
target/
|  classes/
|  test-classes/ # data and compiled code for unit testing
|  project.jar

Test resources are intended to be accessible during testing via the code already written for accessing main (deliverable) resources. One way to support this is to copy the src/test/resources contents into target/test-classes, so that the same CLASSPATH-based mechanisms to locate the compiled test code will also find the test resources.

1.3.2 Android/Gradle Project Directories

A similar directory structure is employed for Android projects. The Gradle build manager, which we will cover later in this section, has made the Android structure its default for Java projects, making it a popular organization for non-Apache projects.

The most obvious difference is that the products of the build are stored in build instead of target.

src/     # anything supplied/edited by the programmers
build/  # initially empty, holds products of the compilation/build

The src/ directory is laid out identically to the Apache organization:

src/
|  main/
|  |  java/      # Java source code. After compilation, is part of the deliverable.
|  |  resources/ # Data files that will be included in the deliverable, accessible via CLASSPATH
|  |  data/      # Data files needed for the build, but not part of the deliverable.
|  test/
|  |  java/      # Java source code for testing, will not be part of the deliverable
|  |  resources/ # data files, available during testing via CLASSPATH
|  |  data/      # test data
build/

Example: see this structure in the Code Annotation project

1.4 Sub-Project Directory (C/C++)

Much more variation exists. One possibility is:

include/ # header files
|
src/ # compilation units (.c and .cpp files)
|
bin/ # executables and .o files produced by compiling src/
|
lib/ # libraries produced by combining object files

1.4.1 Android-ish structure

Increasingly common is this approach, inspired by the Apache/Android Java styles:

src/
|  main/
|  |  cpp/       # C++ source code. .cpp files and local headers
|  |  headers/   # Header (.h) files that need to be visible to main code and
|  |             #   to tests.
|  |  public/    # For library projects, the header files that will be exported
|  |             #   as part of the delivered library.
|  test/
|  |  cpp/       # Unit test code
|  |  data/      # test data
build/
|  exe/         # Executables
|  |  main/     #   - from main/cpp
|  |  test/     #   - from test/cpp
|  lib/         # libraries constructed from object code
|  |  main/     #   - from obl/main
|  obj/         # Compiled object code
|  |  main/     #   - from main/cpp
|  |  test/     #   - from test/cpp
|  tmp/         # Work area for general temporary files

2 First-Generation – Dependency-Based

 
Early build managers are based on the idea of a file dependency graph:

Analysis of such a graph facilitates

make is the canonical example of a build manager of this type.

2.1 make

make is a command/program that enacts builds according to a dependency graph expressed in a makefile.

2.2 makefiles

At its heart, a makefile is a collection of rules.

2.2.1 Rules


The Components of a Rule

where


Rule Examples

 

codeAnnotation.jar: code2HTML.class CppJavaScanner.class
        jar tvf codeAnnotation.jar code2HTML.class CppJavaScanner.class

CppJavaScanner.class: CppJavaScanner.java
        javac CppJavaScanner.java

code2HTML.class: code2HTML.java CppJavaScanner.java
        javac code2HTML.java

CppJavaScanner.java: code2html.flex
        java -cp JFlex.jar JFlex.Main code2html.flex


Pros & Cons

3 Second Generation – Task-Based

 
Other managers are based on the idea of interdependent tasks.

This approach facilitates

ant is based on this approach.

3.1 ant

3.1.1 ant Features

3.1.2 Targets

At its heart, a build file is a collection of targets.


Example of Targets

simplebuild.xml.listing
<project name="JavaBuild" default="deploy"> ➀
  <description>
    Example of a simple project build
  </description>

  <target name="compile" description="Compile src/.../*.java into bin/"> ➁
    <mkdir dir="bin" />                    ➂
    <javac srcdir="src" destdir="bin"
           debug="true" includeantruntime="false"/>
    <echo>compiled </echo>
  </target>

  <target name="unittest" depends="compile" unless="test.skip"> ➃
    <mkdir dir="test-reports" />
    <junit printsummary="on" haltonfailure="true" 
           fork="true" forkmode="perTest">
      <formatter type="plain" />
      <batchtest todir="test-reports">
        <fileset dir="bin">
          <include name="**/Test*.class" />
          <exclude name="**/Test*$*.class" />
        </fileset>
      </batchtest>
    </junit>
  </target>

  <target name="deploy" depends="unittest" description="Create project's Jar file">
    <jar destfile="myProject.jar">
      <fileset dir="bin"/>
    </jar>
  </target>
</project>

The project has a name and default target

A basic target. It is named “compile” and has a description (which may be picked up by some IDEs)

This target has 3 tasks. It creates a directory, compiles Java source code, and prints a message when completed.

This target illustrates both a dependency and a condition.

3.2 maven

Another Apache project, Maven came well after Ant had come to dominate the Java open source landscape.

3.2.1 Motivations for Maven

Grew out of an observation that many supposedly cooperative, related Apache projects had inconsistent and incompatible ant build structures.

Stated goals are

3.2.2 pom.xml

The build file for maven is also in XML:

pom.xml.listing
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>edu.odu.cs</groupId>                  ➀
  <artifactId>codeAnnotation</artifactId>        ➁
  <packaging>jar</packaging>
  <version>1.0</version>
  <name>codeAnnotation</name>
  <url>https://www.cs.odu.edu/~zeil/cs795SD/s13/Directory/topics.html</url>
  <description>
    This is a tool used to parse code listings and to 
    generate syntax-highlighted C++/Java listings in both
    HTML and LaTeX.
  </description>

  <!-- site generation:
       mvn test
       mvn surefire-report:report
       mvn site
  -->


  <repositories>
  </repositories>


  <dependencies>                                 ➂
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
    	<groupId>de.jflex</groupId>
    	<artifactId>jflex</artifactId>
    	<version>1.4.3</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>                                  ➃
      <plugin>
        <groupId>de.jflex</groupId>
        <artifactId>maven-jflex-plugin</artifactId>
        <version>1.4.3</version>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins> 
  </pluginManagement>
  </build>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
</project>

3.2.3 Observations

3.3 Maven and 3rd Party Libraries

One of the most important innovations introduced by Maven.

3.3.1 Dependencies

3.3.2 Fetching Dependencies

3.3.3 Maven Repositories

3.3.4 Transitive Dependencies

How does Maven know whether junit itself depends on other libraries?

3.3.5 Too Good of an Idea to Let Go

ant got jealous…

4 Third Generation Build Managers

Combine

For this we, will look to gradle.