25. Too Many Source Files!

Part of CS:2820 Object Oriented Software Development Notes, Fall 2015
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

Introduction

Over the past lectures, we've taken a single source file and broken it into multiple source files, then used Javadoc to create documentation. The result has one key downside, the huge number of files that choke the directory for the project. Here's what you get if you do an ls command on the directory for our road network simulation:

[dwjones@fastx08 ~/AAA]$ ls
allclasses-frame.html                    RoadNetwork.java
allclasses-noframe.html                  RoadNetwork.new
classes                                  ScanSupport.class
constant-values.html                     ScanSupport.html
deprecated-list.html                     ScanSupport.java
Errors.class                             'ScanSupport$Message.class'
Errors.html                              ScanSupport.Message.html
Errors.java                              'ScanSupport$NotFound.class'
example                                  ScanSupport.NotFound.html
exampleAB                                script.js
help-doc.html                            serialized-form.html
index-all.html                           'Simulator$1.class'
index.html                               'Simulator$Action.class'
Intersection.class                       Simulator.Action.html
'Intersection$ConstructorFailure.class'  Simulator.class
Intersection.ConstructorFailure.html     'Simulator$Event.class'
Intersection.html                        Simulator.Event.html
Intersection.java                        Simulator.html
'NoStop$1.class'                         Simulator.java
'NoStop$2.class'                         Sink.class
NoStop.class                             'Source$1.class'
overview-tree.html                       'Source$2.class'
package-frame.html                       'Source$3.class'
package-list                             Source.class
package-summary.html                     'StopLight$1.class'
package-tree.html                        'StopLight$2.class'
PRNG.class                               'StopLight$3.class'
PRNG.html                                'StopLight$4.class'
PRNG.java                                'StopLight$5.class'
'Road$1.class'                           StopLight.class
Road.class                               stylesheet.css
'Road$ConstructorFailure.class'          testfiles
Road.ConstructorFailure.html             testscript
Road.html                                testscripterrs
Road.java                                testscriptout
RoadNetwork.class                        testscripttemp
RoadNetwork.html
[dwjones@fastx08 ~/AAA]$ 

If you use a GUI to look at the directory (as a "folder"), the clutter is no better. We can break this hodgepodge of files down a bit by using selective ls commands. Here is the list of just the java source files:

[dwjones@fastx08 ~/AAA]$ ls *.java
Errors.java        PRNG.java  RoadNetwork.java  Simulator.java
Intersection.java  Road.java  ScanSupport.java
[dwjones@fastx08 ~/AAA]$ 

Here is the list of just the files created as output from the Java compiler:

[dwjones@fastx08 ~/AAA]$ ls *.class
Errors.class                             'Simulator$Action.class'
Intersection.class                       Simulator.class
'Intersection$ConstructorFailure.class'  'Simulator$Event.class'
'NoStop$1.class'                         Sink.class
'NoStop$2.class'                         'Source$1.class'
NoStop.class                             'Source$2.class'
PRNG.class                               'Source$3.class'
'Road$1.class'                           Source.class
Road.class                               'StopLight$1.class'
'Road$ConstructorFailure.class'          'StopLight$2.class'
RoadNetwork.class                        'StopLight$3.class'
ScanSupport.class                        'StopLight$4.class'
'ScanSupport$Message.class'              'StopLight$5.class'
'ScanSupport$NotFound.class'             StopLight.class
'Simulator$1.class'
[dwjones@fastx08 ~/AAA]$ 

Here is the list of just the HTML files created by Javadoc:

[dwjones@fastx08 ~/AAA]$ ls *.html
allclasses-frame.html                 package-tree.html
allclasses-noframe.html               PRNG.html
constant-values.html                  Road.ConstructorFailure.html
deprecated-list.html                  Road.html
Errors.html                           RoadNetwork.html
help-doc.html                         ScanSupport.html
index-all.html                        ScanSupport.Message.html
index.html                            ScanSupport.NotFound.html
Intersection.ConstructorFailure.html  serialized-form.html
Intersection.html                     Simulator.Action.html
overview-tree.html                    Simulator.Event.html
package-frame.html                    Simulator.html
package-summary.html
[dwjones@fastx08 ~/AAA]$ 

Nowhere in this mess is there a file explaining what all the files are. This is a bit of a problem.

README files

Any time a large project reaches the point where there are too many files and no roadmap, we need to do something. In the UNIX world, the first solution to emerge was the README file (always given an upper-case name to make it stand out).

Simply add a file to the directory named README that explains what the directory contains and how to use it.

Unfortunately, there is no standard for the structure or contents of a README file, but obvious things to include are:

Some software distributions split these apart, so you have a file called COPYRIGHT and a file called AUTHORSHIP. Sometimes, there's a file called READMEFIRST that explains all the component "read me" files.

Here is a README file for the road network example:

A road network simulator

By Douglas W. Jones

This is freeware, you get what you pay for, so don't expect much

SOURCE FILES IN THIS DIRECTORY 
Errors.java       -- general purpose error reporting package
PRNG.java         -- general purpose pseudo-random-number generator
ScanSupport.java  -- support for Java's Scanner class
Simulator.java    -- general purpose discrete-event simulation framework

Intersection.java -- intersections in the road network
Road.java         -- roads in the road network

RoadNetwork.java  -- main program

classes           -- the @classes file for the javac command

TEST FILES IN THIS DIRECTORY
example           -- a small road network
exampleAB         -- a smaller one
testscript        -- a abandoned attempt at building a test script
testfiles:        -- a subdirectory used by the test script
  empty           -- the empty file, used in the script
  test1-1errors   -- the expecte errors from the first test

The rest of the files in this directory were created automatically by
the following shell commands:
  javac @classes
  javadoc @classes

Makefiles

The problem of building large applications is complex enough that people began to build tools to automate this in the 1970s. Perhaps the most general of these tools is the Make utility that grew out of the C and C++ communities. The thing that makes Make special is that it is not tied to C and C++, but can be applied to any programming language or mix of languages.

Traditionally, the makefile for a project should be called Makefile, assuming that there is only one project in the current directory. Makefiles allow comments with a # (pound sign) prefix, so all of the usual rules for commenting apply.

Here is a useful makefile for our project:

# Makefile
# tools for maintaining the road network simulator

# example uses
#   make clean  -- deletes automatically generated files from the directory

clean:
        rm -f *.class *.html package-list script.js stylesheet.css

The basic structure of a make command is illustrated by the above. Each make command begins with the name of a target. To make the target clean, the make command will execute the shell command indented on the following line or lines. Classical versions of make require that all indenting in the makefile be done with tabs, spaces do not work.

By convention, most large projects have a make clean mechanism to delete everything that is automatically generated so that you can package up the contents of the directory for export, and so that you can clean out all the secondary files before you make a backup, and so that you can clean up the directory before you list it at the start of a day's work doing development.

Our project is intended to be used with Javadoc, so we can use the makefile to document how this is done as well:

# Makefile
#   input to make command to make things related to road network simulator
#   author Douglas W. Jones

# example uses
#   make clean -- deletes automatically generated files from the directory
#   make html  -- makes all the HTML documentation using javadoc

html: classes
	javadoc @classes

clean:
	rm -f *.class *.html package-list script.js stylesheet.css

We've added a new make target to our example uses, so if you type make html, it will run the command javadoc @classes. The added reference to classes after the colon on the target line tells make that, in order to make the target html, there must be a file called classes.

We can also tell make that it needs all the source files to make the html target by listing them on the same line. Such file lists get long, but we can use make variables to clean this up and add additional documentation:

# Makefile
#   input to make command to make things related to road network simulator
#   author Douglas W. Jones

# example uses
#   make clean -- deletes automatically generated files from the directory
#   make html  -- makes all the HTML documentation using javadoc

# all the source files, by category
supportFiles = Errors.java PRNG.java ScanSupport.java Simulator.java
modelFiles = Intersection.java Road.java
mainFile = RoadNetwork.java

JavaSourceFiles = $(supportFiles) $(modelFiles) $(mainFile)

# make targets

html: classes $(JavaSourceFiles)
	javadoc @classes

clean:
	rm -f *.class *.html package-list script.js stylesheet.css

Note that the variable names we've used for each group of file names documents what the names in that group do. Note also that make uses a really miserable notation for references to named variables, a $ (dollar sign) prefix on a parenthesized variable name. This makes variable names stand out where they are used, but it is hardly convenient or intuitive.

All make variables are character strings, typically just lists of file names separated by blanks. Variables must be defined before use.

Now, we can add a make target for the whole application:

RoadNetwork.class: classes $(JavaSourceFiles)
	javac @classes

Since this is the primary make target for our application, we should list it first. By default, if you just type make, it will make the first target, so by listing this one first, typing make is equivalent to typing make RoadNetwork.class.

Whicever one you use, make is smart enough to check if any of the files on which the target depends have been modified after the date of last modification of the target. If they have been modified, it will run the shell command. If not, it will just output a message saying that the target is already up to date.

Dependencies

At this point, we aren't really using the inter-file dependencies that exist in our collection of files. To start using these, note that our makefile already provides better documentation of the source files than the classes file because we've grouped the files by category and used variable names that identify the category. As a result, we can use this as our primary list of file names and let make automatically generate the classes file:

classes:
	echo $(JavaSourceFiles) > classes

This says that to make the file classes, if this file does not already exist, generate it using the a shell command that creates the file from the list of java source files inside this makefile.

For each make target, make includes a check to see if the target is up to date, so if the date of last modification of the file classes is after the date of modification of all of the files on which it depends, it will not re-make it, while if any of those files have been changed since classes was last modified, make will re-make it.

Since the contents of classes was created from the makefile, we should really include Makefile in the list of dependencies. This way, if someone edits the makefile, then classes will be automatically regenerated the next time it is needed.

We can now include classes in the list of files deleted by make clean, giving us this makefile:

JavaSourceFiles = $(supportFiles) $(modelFiles) $(mainFile)

# make targets

classes: Makefile
	echo $(JavaSourceFiles) > classes

html: classes $(JavaSourceFiles)
	javadoc @classes

clean:
	rm -f *.class *.html package-list script.js stylesheet.css

We are still missing something: Make can also do the primary job of compiling our source files. When we run the program, we run it as RoadNetwork, which is taken to be a reference to the file RoadNetwork.class, so that is the make target for the application. Make treats the first target as the default, so if you put RoadNetwork.class as the first make target in your list of targets, just typing make becomes equivalent to typing make RoadNetwork.

It is useful to separate the make targets into categories: The primary target, subsidiary make targets needed in order to make the primary target, and secondary targets you may never need to use. That is done in the following version of the makefile:

# Makefile
#   input to make command to make things related to road network simulator
#   author Douglas W. Jones

# primary use
#   make       -- make RoadNetwork.class, the Java executable for the simulation
#   make RoadNetwork.class

# secondary uses
#   make clean -- deletes automatically generated files from the directory
#   make html  -- makes all the HTML documentation using javadoc

# all the source files, by category
supportFiles = Errors.java PRNG.java ScanSupport.java Simulator.java
modelFiles = Intersection.java Road.java
mainFile = RoadNetwork.java

JavaSourceFiles = $(supportFiles) $(modelFiles) $(mainFile)

# primary make target

RoadNetwork.class: classes $(JavaSourceFiles)
	javac @classes

# subsidiary make targets

classes: Makefile
	echo $(JavaSourceFiles) > classes

# secondary make targets

html: classes $(JavaSourceFiles)
	javadoc @classes

clean:
	rm -f *.class *.html classes package-list script.js stylesheet.css

The first time you type make, it will begin by creating the classes file and then it will run javac @classes. If you do nothing but type make again, it will tell you that RoadNetwork.java is up to date and not remake it. If you edit any of the source files and then type make, it will notice that you changed something on which RoadNetwork.java depends and it will recompile everything.

The behavior is not as nice for regenerating the HTML files. The reason is, the make command for target html doesn't create a file with that name, so every time you ask it to make html, it will repeat the job. We can fix this as follows:

index.html: classes $(JavaSourceFiles)
	javadoc @classes
html:index.html

This explicitly names the primary output of Javadoc so that make can compare the date of last modification of index.html with the dates on all the files it depends on. If you just type make html, there are no shell commands to execute but because it depends on index.html, you asked make to make sure that is up to date.

The top level documentation at the head of the makefile doesn't need to list secondary make targets, but the structure of the file dependencies between the secondary targets is documented by the makefile.

Testing

We can easily add test scripts to the makefile as secondary make targets:

tests: RoadNetwork.java exampleAB example
	java RoadNetwork exampleAB
	java RoadNetwork example

Now, we can run all of our tests by typing make tests. If we'd followed through on our original development plan by creating and maintaining test scripts, this could launch those test scripts instead of directly running the tests.