Google App Engine

Using Apache Ant

If you are not using Eclipse and the Google Plugin for Eclipse, you'll probably want some other way to manage the process of building and testing your App Engine application. Apache Ant makes it easy to manage your project from the command line, or from other IDEs that work with Ant. The Java SDK includes a set of Ant macros to perform common App Engine development tasks, including starting the development server and uploading the app to App Engine.

This article describes an Ant build file useful for most projects. To skip the description and go directly to the complete file for copying and pasting, see The Complete Build File.

Note: If you are using Apache Ant with JDO/JPA, please note that this article applies to JDO version 2.3 and JPA version 1.0. Upgrading to the latest (experimental) versions requires changes to the build.xml file that are not documented on this page. For more information, see JDO upgrade instructions and JPA upgrade instructions.

  1. Installing Ant
  2. The Project Directory
  3. Creating the Build File
  4. Defining the Class Path
  5. Enhancing JDO Classes
  6. Running the Development Server
  7. Uploading and Other AppCfg Tasks
  8. The Complete Build File

Installing Ant

If you do not already have Ant installed on your system, visit the Apache Ant web site to download and install it.

When Ant is installed and the ant command is on your command path, you can run the following command to verify that it works, and see which version is installed:

ant -version

The Project Directory

As described in the Getting Started Guide, your App Engine project must produce a directory structure using the WAR standard layout for Java web applications. (WAR archive files are not yet supported by the SDK.)

For these instructions, we will put all project-related files in a single directory named Guestbook/ (for the guest book project described in the Getting Started Guide), with a subdirectory named src/ for Java source code and a subdirectory named war/ for the finished application files. The build process will compile the source code, and put the compiled Java bytecode in the appropriate location under war/. You will create other files for the WAR directly in the war/ directory.

The complete project directory should look like this:

Guestbook/
  src/
    ...Java source code...
    META-INF/
      ...other configuration...
  war/
    ...JSPs, images, data files...
    WEB-INF/
      ...app configuration...
      classes/
        ...compiled classes...
      lib/
        ...JARs for libraries...

To create this directory structure from the command prompt, use the following commands. If you are using Windows, change the forward slashes / in these paths to backslashes \. (Within the Ant file itself, paths using forward slashes / work for all operating systems.)

mkdir Guestbook
cd Guestbook
mkdir -p src/META-INF
mkdir -p war/WEB-INF/classes
mkdir -p war/WEB-INF/lib

Creating the Build File

In the Guestbook/ directory, create a file named build.xml with the following contents:

<project>
  <property name="sdk.dir" location="../appengine-java-sdk" />

  <import file="${sdk.dir}/config/user/ant-macros.xml" />

</project>

This build file doesn't do anything yet: it doesn't contain a "target" with instructions to perform tasks. The file defines two Ant properties, which we will use when we define targets and paths later.

The sdk.dir property is the path to the App Engine Java SDK, the appengine-java-sdk directory created when you unpacked the SDK Zip file. "../appengine-java-sdk" assumes this directory is in the parent directory above the project directory. Adjust this value, if necessary. (Paths are relative to the location of the build.xml file.)

The <import> element loads a set of Ant macros for App Engine development, included in the App Engine Java SDK. We will use several of these macros later.

Defining the Class Path

When you compile your classes, the Java class path must contain the JARs for the App Engine API, and any other JARs you may have added to your project's war/WEB-INF/lib/ directory (such as the DataNucleus JDO/JPA JARs). The application can access classes from JARs added to this directory. Additionally, the JARs in the SDK's lib/shared/ directory will be on the class path.

Edit build.xml and add the following lines above the closing </project> tag to define a class path containing these JARs:

  <path id="project.classpath">
    <pathelement path="war/WEB-INF/classes" />
    <fileset dir="war/WEB-INF/lib">
      <include name="**/*.jar" />
    </fileset>
    <fileset dir="${sdk.dir}/lib">
      <include name="shared/**/*.jar" />
    </fileset>
  </path>

Copying JARs

Your app must include appengine-api-*.jar (where * represents a version number of the API and SDK), a JAR included with the SDK, in the directory war/WEB-INF/lib/. App Engine checks for classes from this JAR to determine which version of the API your app is using. If your application uses the JDO or JPA interfaces to the App Engine datastore, the app must include the JDO/JPA implementation in war/WEB-INF/lib/. All of these JARs are in the SDK's lib/user/ directory.

You could copy these JARs into the WAR manually. However, it's useful to make copying these JARs part of the build process. This ensures that your app is using the same versions of these interfaces as the ones included in the SDK, even if you upgrade the SDK at a later date.

All of the JARs must be in the war/WEB-INF/lib/ directory, they cannot be in subdirectories. This Ant target will copy JARs recursively from ${sdk.dir}/lib/user/ using the copy task's flatten="true" attribute so all JARs end up in the same directory.

Edit build.xml and add the following lines above the closing </project> tag:

  <target name="copyjars"
      description="Copies the App Engine JARs to the WAR.">
    <copy
        todir="war/WEB-INF/lib"
        flatten="true">
      <fileset dir="${sdk.dir}/lib/user">
        <include name="**/*.jar" />
      </fileset>
    </copy>
  </target>

This creates a build target named "copyjars" that copies the JARs to the appropriate location. To test this target, make sure the current working directory is the project directory (Guestbook/), then run the following command:

ant copyjars

Check the contents of war/WEB-INF/lib/ to make sure the JARs are there.

Compiling Source Files

Edit build.xml and add the following lines above the closing </project> tag:

  <target name="compile" depends="copyjars"
      description="Compiles Java source and copies other source files to the WAR.">
    <mkdir dir="war/WEB-INF/classes" />
    <copy todir="war/WEB-INF/classes">
      <fileset dir="src">
        <exclude name="**/*.java" />
      </fileset>
    </copy>
    <javac
        srcdir="src"
        destdir="war/WEB-INF/classes"
        classpathref="project.classpath"
        debug="on" />
  </target>

This defines a build target named "compile" that compiles all Java source files found in the src/ directory and stores the compiled classes in war/WEB-INF/classes/. All other files found in src/, such as the META-INF/ directory, are copied verbatim to war/WEB-INF/classes/. The target depends on "copyjars", so both targets are built, if necessary.

To compile your project's source files, run the following command:

ant compile

Enhancing JDO Classes

This section describes how to use Ant to build your project with support for the Java Data Objects (JDO) interface to the datastore. If your project does not use the JDO interface, you can proceed to the next section.

As described in the Getting Started Guide and the datastore documentation, the JDO interface allows you to store instances of your classes in the datastore, and retrieve them later as objects. You describe how instances are to be stored and retrieved using Java annotations in the class definition.

In order for JDO to see the annotations, you must process the bytecode for your data classes after they have been compiled using a tool included with DataNucleus Access Platform, the JDO implementation distributed with App Engine. DataNucleus describes this process as "enhancing" the classes.

The App Engine SDK includes Ant macros for enhancing JDO data classes. The <enhance_war> macro enhances every JDO data class in the project, using the correct class path for the project. For more precise control, you can use the <enhance> macro.

Edit build.xml and add the following lines above the closing </project> tag:

  <target name="datanucleusenhance" depends="compile"
      description="Performs JDO enhancement on compiled data classes.">
    <enhance_war war="war" />
  </target>

This target depends on "compile", so building this target ensures that the classes have been compiled and are up to date before performing the enhancement task.

Running the Development Server

You can run the development web server using an Ant macro. By making this target depend on the "datanucleusenhance" target (or the "compile" target if not using DataNucleus), you can build your project and start the server all with one easy command.

Edit build.xml and add the following lines above the closing </project> tag:

  <target name="runserver" depends="datanucleusenhance"
      description="Starts the development server.">
    <dev_appserver war="war" />
  </target>

You can give the development server arguments using attributes and an <options> element. For example, the following target starts the server using the port 8888, and enables remote Java debugging on port 9999:

  <target name="runserver" depends="datanucleusenhance"
      description="Starts the development server.">
    <dev_appserver war="war" port="8888" >
      <options>
        <arg value="--jvm_flag=-Xdebug"/>
        <arg value="--jvm_flag=-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9999"/>
      </options>
    </dev_appserver>
  </target>

To build the project and run the server, use the following command:

ant runserver

To stop the server, hit Control-C.

Uploading and Other AppCfg Tasks

You can define Ant tasks to upload the application to App Engine and perform other actions provided by AppCfg.

The <appcfg> macro takes the name of the action as the action attribute, and the path to the project's WAR as the war attribute. The element can have optional <options> and <args> elements.

Edit build.xml and add the following lines:

  <target name="update" depends="datanucleusenhance"
      description="Uploads the application to App Engine.">
    <appcfg action="update" war="war" />
  </target>

  <target name="update_indexes" depends="datanucleusenhance"
      description="Uploads just the datastore index configuration to App Engine.">
    <appcfg action="update_indexes" war="war" />
  </target>

  <target name="rollback" depends="datanucleusenhance"
      description="Rolls back an interrupted application update.">
    <appcfg action="rollback" war="war" />
  </target>

  <target name="request_logs"
      description="Downloads log data from App Engine for the application.">
    <appcfg action="request_logs" war="war">
      <options>
        <arg value="--num_days=5"/>
      </options>
      <args>
        <arg value="logs.txt"/>
      </args>
    </appcfg>
  </target>

The Complete Build File

Here is the complete build.xml file described by these instructions:

<project>
  <property name="sdk.dir" location="../appengine-java-sdk" />

  <import file="${sdk.dir}/config/user/ant-macros.xml" />

  <path id="project.classpath">
    <pathelement path="war/WEB-INF/classes" />
    <fileset dir="war/WEB-INF/lib">
      <include name="**/*.jar" />
    </fileset>
    <fileset dir="${sdk.dir}/lib">
      <include name="shared/**/*.jar" />
    </fileset>
  </path>

  <target name="copyjars"
      description="Copies the App Engine JARs to the WAR.">
    <copy
        todir="war/WEB-INF/lib"
        flatten="true">
      <fileset dir="${sdk.dir}/lib/user">
        <include name="**/*.jar" />
      </fileset>
    </copy>
  </target>

  <target name="compile" depends="copyjars"
      description="Compiles Java source and copies other source files to the WAR.">
    <mkdir dir="war/WEB-INF/classes" />
    <copy todir="war/WEB-INF/classes">
      <fileset dir="src">
        <exclude name="**/*.java" />
      </fileset>
    </copy>
    <javac
        srcdir="src"
        destdir="war/WEB-INF/classes"
        classpathref="project.classpath"
        debug="on" />
  </target>

  <target name="datanucleusenhance" depends="compile"
      description="Performs JDO enhancement on compiled data classes.">
    <enhance_war war="war" />
  </target>

  <target name="runserver" depends="datanucleusenhance"
      description="Starts the development server.">
    <dev_appserver war="war" />
  </target>

  <target name="update" depends="datanucleusenhance"
      description="Uploads the application to App Engine.">
    <appcfg action="update" war="war" />
  </target>

  <target name="update_indexes" depends="datanucleusenhance"
      description="Uploads just the datastore index configuration to App Engine.">
    <appcfg action="update_indexes" war="war" />
  </target>

  <target name="rollback" depends="datanucleusenhance"
      description="Rolls back an interrupted application update.">
    <appcfg action="rollback" war="war" />
  </target>

  <target name="request_logs"
      description="Downloads log data from App Engine for the application.">
    <appcfg action="request_logs" war="war">
      <options>
        <arg value="--num_days=5"/>
      </options>
      <args>
        <arg value="logs.txt"/>
      </args>
    </appcfg>
  </target>

</project>

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.