Donnerstag, 28. November 2013

Reading Subversion information in scripts

Sometimes it is useful to know something about a Subversion repository. The "svn diff" command for example works only with URLs. If you have to write a wrapper for "svn diff" to perform some standard tasks it is necessary to get the repository URL for a directory. This is possible with the "svn info" command. Some people propose to use grep and sed to parse the output of "svn info". But parsing with grep and sed is often not very reliable, because they search only for string patterns. This makes it for example hard to distinguish a revision number of a commit from a revision number of a entry, because they share the same label.

But Subversion provides a far better option, which makes parsing much easier. The "svn info" command can generate XML output, which can be parsed with "xmllint" and an appropriate XPath expression to get the exact information. The following Bash script is a small wrapper around "svn info --xml" and "xmllint --xpath".

#! /bin/bash
if [ "$2" ] ; then
    TARGET=$1
    shift
else
    TARGET=.
fi
svn info --xml "$TARGET" | 
if [ "$1" ] ; then
    if [ '@' = $(cut -c 1 <<<$(basename "$1")) ] ; then
        xmllint --xpath 'string('"$1"')' -
    else
        xmllint --xpath "$1"'/text()' -
    fi
    echo
else
    xmllint --format -
fi

The script makes it quite easy to get the right information:

trunk$ svninfo /info/entry/@revision
2647
trunk$ svninfo /info/entry/commit/@revision
2646
Running the script without any argument pretty prints the raw XML date. This is useful to write the exact XPath expression.

Freitag, 22. November 2013

Defining macros in Ant to read files

Ant is a scary build tool to compile Java code. It aims to be a better Make but it is just a sludge of everything without doing anything right. It implements a programming language with XML syntax, which is simply pain to write. But sometimes you have to use it, because Sun failed to design something better, when they designed Java.

Make files typically read some values or lists from other files. Because Make does not try to be a Shell like Ant, the Shell is used for this:

version = $(shell cat VERSION)
This reads the contents of the file "VERSION" into the variable "version". Although Ant tries to be a Shell it has no build-in function to do the same. Instead it provides you a way to define macros in XML syntax, to define a function. The following code defines an Ant function, which reads the contents of a file into a property without any new lines.
<macrodef name="loadfilevalue">
  <attribute name="property"/>
  <attribute name="file"/>
  <sequential>
    <loadfile property="@{property}" srcFile="@{file}">
      <filterchain><striplinebreaks/></filterchain>
    </loadfile>
  </sequential>
</macrodef>
With this macro it is possible to read the "VERSION" file:
<loadfilevalue property="version" file="VERSION"/>
Something similar can be done for lists of values. Ant provides a way to modify the contents of a file with regular expressions while reading it. This makes it possible to write a macro, which reads some lines from a file into a coma delimited property, which is usually required as the input to specify a list of files in Ant. This is the macro.
<macrodef name="loadfilelist">
  <attribute name="property"/>
  <attribute name="file"/>
  <sequential>
    <loadfile property="@{property}" srcFile="@{file}">
      <filterchain>
        <tokenfilter>
          <replaceregex pattern="$" replace=","/>
        </tokenfilter>
        <striplinebreaks/>
        <tokenfilter>
          <replaceregex pattern=",$" replace=""/>
        </tokenfilter>
      </filterchain>
    </loadfile>
  </sequential>
</macrodef>
And this shows how to use it.
<loadfilelist property="files" file="FILES"/>

Donnerstag, 14. November 2013

Create classpath while compiling Java with GNU Make

Compiling Java files with GNU Make can be a bit tricky. The problem is, that a class path in the command line must be specified with colons while a class path specified in a manifest file has to be specified with spaces. This makes it necessary to convert either colons to spaces or spaces to colons. The following shows how to convert spaces to colons.

The main problem is that is quite hard the specify a single space in a GNU Make file. This requires the following trick. First you have to define a variable with nothing in and after that you can define a space by delimiting it with the variable for nothing. After that you have a variable with a single space. The following part of a Make file shows how the class path conversion can be done with the previously defined space.

# Compile Java source
e :=
space := $(e) $(e)
$(CLASSES): $(BUILD)/%.class: src/%.java | $(BUILD)
	@$(ECHO) JAVAC: $@
	@$(JAVAC) -Xlint:unchecked \
		-cp .:$(subst $(space),:,$(addprefix jar/,$(LIBS))) \
		-sourcepath src:$(subst $(space),:,$(addprefix jar/,$(SRCS))) \
		-d $(BUILD) $(JFLAGS) $< \
		|| { $(RM) $@ && $(EXIT) 1 ; }

GNU Make wrapper for Ant.

The following is a GNU make wrapper for Apache Ant. Those targets, which must be implemented in GNU Make can be implemented in the Make file and all other targets are passed to Ant. And it is possible to mix different Ant and Make targets.
all:
 @ant -e jar

# The following prevents the execution of "cat" on systems, which do not
# support it.
_import:
 $(eval PACKAGE := $(shell cat PACKAGE))
 $(eval VERSION := $(shell cat VERSION))

DEVELOPMENT := 
PRODUCTION := 
BIN_JAR = build/jar/$(PACKAGE)-$(VERSION).jar

install: _import binjar
 scp $(BIN_JAR) $(PRODUCTION)/.
 cp $(BIN_JAR) $(DEVELOPMENT)/.

tag: _import
 $(eval TRUNC_URL := $(shell svn info --xml | xmllint --xpath '/info/entry/url/text()' -))
 svn -m "TAG: $(VERSION)" cp $(TRUNC_URL) $(subst trunk,tags/$(VERSION),$(TRUNC_URL))

# Pass all other targets to ant
%:
 @ant -e "$@"
In this case the Subversion tagging is not done in Ant. It is easier to do it in the Make file.