Up, up, and away: deploying your projects
First version: 11-Feb-2010 by Rick Manning
- How a software project is packaged and delivered to those who will use it.
- The easiest way, if done wrong, to annoy your graders.
Sometimes comp314 students (especially those with little experience beyond classroom projects) have difficulty turning in their projects in a way that makes them easy to use by the graders. This tutorial is meant to help prevent that. We'll cover the basics of Java project structure, Java classpaths, Eclipse classpath files, libraries, and READMEs. Use this knowledge and you'll make your graders very happy!
Good deployment starts with good directory structure. Directories exist so that you can divide your files into smaller groups and find things more easily---a good directory layout will make it easy for you and those who use your project to find the files they're looking for.
Many Java projects use the following conventional layout:
+-[project root] +-bin/ (compiled .class files, not kept under version control) +-doc/ (specification, javadocs, etc.) +-lib/ (any external libraries [see below]) +-src/ (project source code, subdivided by package) +-README +-[...]
In a slightly broader scope, here's the way we recommend arranging your Subversion folder:
+-projects/ +-p1/ +-g01/ +-branches/ +-tags/ +-spec/ (created by tagging the trunk at spec deadline) +-beta/ (created by tagging the trunk at beta deadline) +-final/ (created by tagging the trunk at final deadline) +-trunk/ (your group's project root - all work is done here) +-doc/ +-lib/ +-[etc.]
Notice that the project root is the
trunk directory. This allows your group to submit your spec, beta, and final milestones very easily: in Eclipse, just right-click your project and select "Branch/Tag..." from the Team menu, then specify the appropriate folder (e.g.
/tags/spec), enter a commit message, and hit "Finish":
Remember that these directory layouts are merely suggestions---the only strict requirements here are that you have a tags folder with your submissions and that your project root is reasonably navigable.
In order to execute a Java program, the Java Runtime Environment (JRE) needs to access compiled (.class) versions of any and all classes referenced by the program. Many of these will be provided by the program itself; that is, your compiled project will contain a .class file for each of the classes you define. These generally reside in the
As an example, consider a simple one-class Java program called "Blarg." This program has one class, Blarg, whose
main() method simply prints out a message. Suppose our project root contains a class folder and a source folder, containing Blarg.class and Blarg.java, respectively:
$ ls bin src
From here, an attempt to run Blarg with a plain command will fail:
$ java Blarg Exception in thread "main" java.lang.NoClassDefFoundError: Blarg Caused by: java.lang.ClassNotFoundException: Blarg at java.net.URLClassLoader$1.run(URLClassLoader.java:200) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:315) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:330) at java.lang.ClassLoader.loadClass(ClassLoader.java:250) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:398)
This is because, by default, the Java interpreter looks only in the current directory (i.e. the directory in which the
java command was executed) for the class files it needs. Had we
cd'ed into the
bin folder and executed that same command from there, it would have succeeded.
However, we generally do not want to require users to be in the
bin folder to run our program; it is more convenient to run it from the project root directory. Running it from the project root also allows the program easier access to files not in the
bin folder, such as test input files (which could be, for example, in a
resources subdirectory of the project root).
So, how do we invoke the Java interpreter from one directory while giving it access to class files in another directory? By passing it a so-called classpath (AKA build path) with the
-classpath option. Since our class files are in the
bin directory, the following command is what we need:
$ java -classpath bin Blarg blarg!
At this point you may wonder how the Java interpreter can access classes like
java.lang.String, even though your project does not include class files for them. Such classes are part of the Java Platform and as such, the interpreter automatically knows where to find them on your machine.
The above example used a class with no package---this is rare in actual Java programs and also bad Java style. To provide a more realistic example, suppose we have a Java project called Flarg, in which there is a top-level package
flarg that contains all subpackages for the application. There are two subpackages:
app, which contains the main application class, and
util, which contains two support classes. Since Java is very strict about the relationship between classes and their locations on the filesystem, this package structure means we must have the following file structure (supposing that all our compiled class files are stored in
+-bin/ +-flarg/ +-app/ +-FlargMain.class +-util/ +-Flarginator.class +-DeFlarginator.class
(The Java compiler automatically puts your class files into the correct hierarchy as long as your source files are organized correctly.)
Since we have packages, the
java command to execute our program (which again we run from the project root) will look a little different:
$ java -classpath bin flarg.app.FlargMain flarg!
Note that we specified the package path to the main class (using periods), not the file path (which would use slashes).
For comp314, your READMEs should include a command like the above which runs the main method of your application (see Writing a README).
An external library is any chunk of code your project uses that is not strictly part of your project nor part of the Java Platform itself. For example, if my Flarg project makes use of the JDOM XML parser, all the JDOM code (which is probably wrapped into a single .jar file) would be considered an external library.
The term dependency crops up a lot when discussing libraries. A dependency is anything your project requires (depends on) to run. In the above example, we could list the JDOM library as one of Flarg's dependencies. Technically speaking, all Java programs depend on the JRE and the standard classes it uses. However, external dependencies, which are not part of the project or the Java Platform (like the JDOM .jar file) are generally the trickiest to use correctly.
Let's return to my Flarg project. Suppose I want to use FlargChecker, a collection of classes that somebody else wrote, in my project. Typically, I would download the library (in this case the code is distributed as FlargChecker.jar) and place it in my projects directory (in my case, under the
+-[project root] +-bin/ +-[class files] +-lib/ +-FlargChecker.jar +-src/ +-[source files] +-[...]
I can now make reference to the classes in FlargChecker.jar in my own code, but my project will not compile if I don't tell
javac where to find FlargChecker.jar. I do so by passing the
classpath argument to
$ javac -classpath lib/FlargChecker.jar -d bin [source files]
-d option tells
javac where to place the generated class files.)
Now that our project is compiled, we can run it. However, we still have to tell the Java interpreter the location of our external library! The above step does not, for example, copy all the .class files from the dependencies into our project (otherwise projects with many dependencies would become vast, and any projects that used those projects would become even more vast, and so on). Since we now have to tell the interpreter where to find our class files as well as where to find the FlargChecker class files, we have to specify two paths on the classpath. We use a colon to separate paths:
$ java -classpath bin:lib/FlargChecker.jar flarg.app.FlargMain flarg! (this message verified by FlargChecker)
And there you have it! You can now compile and run programs with external dependencies on the command line. Read on to discover how Eclipse manages most of this for you, and how you should configure Eclipse to do it right.
IDEs like Eclipse are wonderful tools. They let the programmer ignore some of the nitty-gritty details about classpaths and the command line and get straight to the programming. You can click a button and your program just runs. But this convenience comes at a cost: it's harder to learn those nitty-gritty details in the first place, so when you need them, you're forced to resort to Googling (or posting on IRC!). So let's see how Eclipse manages all this for you.
If you have a project open in Eclipse, that means you've already done two things: you've created a workspace, and you've created a project within that workspace. For these examples, our workspace is a folder called
ECLIPSE_TEMP, and our projects are, as required by Eclipse, direct subfolders of that folder. In particular, we have an Eclipse project called Flarg, laid out as described above (with packages
flarg.util, but without the
lib folder at this point).
Eclipse keeps the most important settings about a particular Java project in two files:
.project. Both of these will be in the project root:
$ ls -a . .classpath .settings src .. .project bin
(You'll notice there is also a
.settings directory. This contains other Eclipse settings, but I won't talk about them here.)
Let's look at the
.classpath file for this project:
$ cat .classpath <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/ org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> <classpathentry kind="output" path="bin"/> </classpath>
What does this XML file tell Eclipse? Three things:
kind="src"line tells Eclipse to look in the
srcfolder for the project's source code.
kind="output"line tells Eclipse to put all compiled output (class files) into the
kind="con"line tells Eclipse where to find the files it will use to launch that project's run configurations.
Suppose we again want to add the FlargChecker.jar library to our project so our own classes can use it. We can make a
lib directory to the project and put the .jar file into it, but that's not enough to let our classes reference it. Recall that before, we had to tell
javac where to find every library we wanted to use; likewise we have to tell Eclipse where to find each library. We can do this by modifying our project's class path (also known as the build path) from within Eclipse.
To add a library to an Eclipse project, right-click the project in Eclipse's "Package Explorer" panel, and select "Properties". Select the "Java Build Path" item on the left side, and go to the "Libraries" tab in the middle. Click the "Add JARs..." button. This will bring up your project directory and let you navigate to any JAR files in it that are not already on the build path. Select one, and it will appear in the list of JARs and class folders on the build path, like so:
Caution: Absolute vs. Relative Paths
Even though we call your library an "external library," be sure not to select the "Add External JARs..." option! Doing so will configure an absolute path to the library (e.g.
C:/Documents and Settings/Users/Rick/.../Flarg/lib/FlargChecker.jar) instead of a relative path (just
/lib/FlargChecker.jar) in your project's settings. An absolute path will work on your computer, but probably not on anyone else's. (This setting is to allow Eclipse projects to reference .jar files not located within an Eclipse project; you shouldn't need to use it.)
If you see an absolute path in your
.classpathfile, fix it by removing the library from the build path (using the same "Java Build Path" panel in Eclipse) and then re-adding it in the manner described above.
Modifying our project's build path has added another line to our
<classpathentry kind="lib" path="lib/FlargChecker.jar"/>
Congratulations! Our Eclipse project can now use that library. Important: If you use any libraries, be sure to commit them to the SVN repository. Also make sure your .classpath file is under version control, so that your partner's copy of Eclipse will know about the library as well.
Including a good README with your project is one of the most helpful things you can do for your graders. A good README will probably touch on all the following topics:
- Project layout: You should outline the structure of your project directory, pointing out where others can find different files. Take special care to describe where your documentation is (your Javadocs, your spec, and anything else you might have included).
- Running instructions: You should include instructions to run your program from the command-line. Do not assume your graders will use Eclipse. You do not need to include compilation instructions unless your project requires special steps to compile. (You should, however, mention any dependencies your project has, so that the graders will know to include them in the build path when they compile your project.)
- Implemented functionality: What features are complete (or at least runnable)? This will help others determine what parts of your program they should run, and what parts they should avoid.
- Known bugs: For a final submission, it might be useful to warn the grader about any bugs you know to exist. For a beta submission, there are probably still too many for a section like this to be useful.
- Lateness: If your project was late, tell us how late it was. Be honest; the graders will know if you're not. :-)