Building and managing jars in Java presents different issues from C/C++ libraries. There are three reasons for this. First, the members of a jar include a relative path , so the precise filenames passed to the jar program must be carefully controlled. Second, in Java there is a tendency to merge jars so that a single jar can be released to represent a program. Finally, jars include other files than classes, such as manifests , property files, and XML.
The basic command to create a jar in GNU make is:
JAR := jar JARFLAGS := -cf $(FOO_JAR): prerequisites... $(JAR) $(JARFLAGS) $@ $^
The jar program can accept directories instead of filenames, in which case, all the files in the directory trees are included in the jar. This can be very convenient , especially when used with the -C option for changing directories:
JAR := jar JARFLAGS := -cf .PHONY: $(FOO_JAR) $(FOO_JAR): $(JAR) $(JARFLAGS) $@ -C $(OUTPUT_DIR) com
Here the jar itself is declared .PHONY . Otherwise subsequent runs of the makefile would not recreate the file, because it has no prerequisites. As with the ar command described in an earlier chapter, there seems little point in using the update flag, -u , since it takes the same amount of time or longer as recreating the jar from scratch, at least for most updates.
A jar often includes a manifest that identifies the vendor, API and version number the jar implements. A simple manifest might look like:
Name: JAR_NAME Specification-Title: SPEC_NAME Implementation-Version: IMPL_VERSION Specification-Vendor: Generic Innovative Company, Inc.
This manifest includes three placeholders, JAR_NAME , SPEC_NAME , and IMPL_VERSION , that can be replaced at jar creation time by make using sed , m4 , or your favorite stream editor. Here is a function to process a manifest:
MANIFEST_TEMPLATE := src/manifests/default.mf TMP_JAR_DIR := $(call make-temp-dir) TMP_MANIFEST := $(TMP_JAR_DIR)/manifest.mf # $(call add-manifest, jar, jar-name, manifest-file-opt) define add-manifest $(RM) $(dir $(TMP_MANIFEST)) $(MKDIR) $(dir $(TMP_MANIFEST)) m4 --define=NAME="$(notdir )" \ --define=IMPL_VERSION=$(VERSION_NUMBER) \ --define=SPEC_VERSION=$(VERSION_NUMBER) \ $(if ,,$(MANIFEST_TEMPLATE)) \ > $(TMP_MANIFEST) $(JAR) -ufm $(TMP_MANIFEST) $(RM) $(dir $(TMP_MANIFEST)) endef
The add-manifest function operates on a manifest file similar to the one shown previously. The function first creates a temporary directory, then expands the sample manifest. Next, it updates the jar, and finally deletes the temporary directory. Notice that the last parameter to the function is optional. If the manifest file path is empty, the function uses the value from MANIFEST_TEMPLATE .
The generic makefile bundles these operations into a generic function to write an explicit rule for creating a jar:
# $(call make-jar,jar-variable-prefix) define make-jar .PHONY: $$(_name) : $(_name) $$(_name): cd $(OUTPUT_DIR); \ $(JAR) $(JARFLAGS) $$(notdir $$@) $$(_packages) $$(call add-manifest, $$@, $$(_name), $$(_manifest)) endef
It accepts a single argument, the prefix of a make variable, that identifies a set of variables describing four jar parameters: the target name, the jar name, the packages in the jar, and the jar's manifest file. For example, for a jar named ui.jar , we would write:
ui_jar_name := $(OUTPUT_DIR)/lib/ui.jar ui_jar_manifest := src/com/company/ui/manifest.mf ui_jar_packages := src/com/company/ui \ src/com/company/lib $(eval $(call make-jar,ui_jar))
By using variable name composition, we can shorten the calling sequence of our function and allow for a very flexible implementation of the function.
If we have many jar files to create, we can automate this further by placing the jar names in a variable:
jar_list := server_jar ui_jar .PHONY: jars $(jar_list) jars: $(jar_list) $(foreach j, $(jar_list),\ $(eval $(call make-jar,$j)))
Occasionally, we need to expand a jar file into a temporary directory. Here is a simple function to do that:
# $(call burst-jar, jar-file, target-directory) define burst-jar $(call make-dir,) cd ; $(JAR) -xf endef