How do I implement the make installcheck
target when building a library? That is, I have a check
target that creates a test program that links to the created library, called by some script in order to check if the library code functions properly, and I wish to perform the same checks but linking to the library after it has been installed; how can I implement this?
Double dollar sign If you want a string to have a dollar sign, you can use $$ . This is how to use a shell variable in bash or sh . Note the differences between Makefile variables and Shell variables in this next example.
A Makefile.am has the same syntax as an ordinary Makefile . When automake processes a Makefile.am it copies the entire file into the output Makefile.in (that will be later turned into Makefile by configure ) but will react to certain variable definitions by generating some build rules and other variables.
By default, the goal is the first target in the makefile (not counting targets that start with a period). Therefore, makefiles are usually written so that the first target is for compiling the entire program or programs they describe.
Since I can't find any guides on this, I will present the method that I have come up with, which has been patched together from reading the GNU automake manual and some general trial and error. It might be ugly, but it works...
If the check routines for building a library involve linking the library to a test program and seeing if that program works correctly, then to installcheck the library we need only do the same thing but link the test program to the installed library rather than the locally built library.
Let's call the library alpha (so we'll be creating libalpha.so and/or libalpha.a). Supposing alpha's source code is in the file alpha.cpp in the src directory, we'll create src/Makefile.am as usual:
# src/Makefile.am
lib_LTLIBRARIES = libalpha.la
libalpha_la_SOURCES = alpha.cpp
include_HEADERS = alpha.h
The check routine involves creating a binary beta that links to alpha. The source code for beta is in the file beta.cpp in the directory tests. The automake file tests/Makefile.am looks like this:
# tests/Makefile.am
check_PROGRAMS = beta
beta_SOURCES = beta.cpp
beta_CPPFLAGS = -I$(top_srcdir)/src
beta_LDADD = $(top_builddir)/src/libalpha.la
We will create our check
and installcheck
routines by creating "local" targets in tests/Makefile.am like so:
# tests/Makefile.am
# ...
check-local:
# recipe to run when 'make check' is called.
installcheck-local:
# recipe to run when 'make installcheck' is called.
The check
and installcheck
targets conflict because use of either target prevents the other target from executing properly (one target "taints" the build tree for the other target); in order for the other target to execute properly we need to remove beta and its object files and have the target recompile and re-link as it sees fit according to its nature (installcheck
to installed files, check
to local files).
We can solve this issue of tainted build trees by simply running make clean
in the recipe of both targets. This will clearly remove the tainted builds, but is overzealous because we don't need to rebuild whenever we run the same target again. The build tree is only tainted whenever we run the other target.
We can only solve this complication by remembering which of the two targets had been called previously, which we can do via the creation/destruction of an intermediary file (let's call it taint). The check
target is tainted whenever the file taint exists, which it resolves by cleaning, rebuilding and removing taint; and the installcheck
target is tainted when the file taint does not exist, which it resolves by cleaning, rebuilding and creating taint.
Our targets will take the form:
# tests/Makefile.am
# ...
check-local:
# First, check to see if the build tree is tainted and rebuild if so
test ! -f taint || $(MAKE) $(AM_MAKEFLAGS) check_rebuild
# Then, run our check tests. Substitute with your shell scripts or testsuite files as appropriate
./beta
installcheck-local:
# First, check to see if the build tree is tainted and rebuild if so
test -f taint || $(MAKE) $(AM_MAKEFLAGS) installcheck_rebuild
# Then, run our installcheck tests. Substitute with your shell scripts or testsuite files as appropriate
./beta
The target check_rebuild
needs to rebuild according to how check
will run, and will look like this:
# tests/Makefile.am
# ...
.PHONY: check_rebuild
check_rebuild:
$(MAKE) $(AM_MAKEFLAGS) clean # remove tainted build tree
$(MAKE) $(AM_MAKEFLAGS) beta$(EXEEXT) # rebuild beta
rm -f taint # mark build tree as untainted
The target installcheck_rebuild
likewise looks like this:
# tests/Makefile.am
# ...
.PHONY: installcheck_rebuild
installcheck_rebuild:
$(MAKE) $(AM_MAKEFLAGS) clean # remove tainted build tree
$(MAKE) $(AM_MAKEFLAGS) beta$(EXEEXT) \
beta_CPPFLAGS="-I$(DESTDIR)$(includedir)" \
beta_LDADD="$(DESTDIR)$(libdir)/libalpha.la" \
beta_DEPENDENCIES="$(DESTDIR)$(libdir)/libalpha.la"
echo 1 > taint # mark build tree as untainted
Note that rebuilding beta in installcheck_rebuild
now requires the overriding of the automake variables so that they point to the installed library.
Finally, we need to add the taint file to DISTCLEANFILES
so that running distcheck
does not fail with "files left in build directory after distclean" errors.
And that should be it. The final tests/Makefile.am should look like this:
# tests/Makefile.am
check_PROGRAMS = beta
beta_SOURCES = beta.cpp
beta_CPPFLAGS = -I$(top_srcdir)/src
beta_LDADD = $(top_builddir)/src/libalpha.la
taint_file = .taint
check-local:
test ! -f $(taint_file) || $(MAKE) $(AM_MAKEFLAGS) check_rebuild
./beta # substitute with your actual test routines
installcheck-local:
test -f $(taint_file) || $(MAKE) $(AM_MAKEFLAGS) installcheck_rebuild
./beta # substitute with your actual test routines
.PHONY: check_rebuild
check_rebuild:
$(MAKE) $(AM_MAKEFLAGS) clean
$(MAKE) $(AM_MAKEFLAGS) beta$(EXEEXT)
rm -f $(taint_file)
.PHONY: installcheck_rebuild
installcheck_rebuild:
$(MAKE) $(AM_MAKEFLAGS) clean
$(MAKE) $(AM_MAKEFLAGS) beta$(EXEEXT) \
beta_CPPFLAGS="-I$(DESTDIR)$(includedir)" \
beta_LDADD="$(DESTDIR)$(libdir)/libalpha.la" \
beta_DEPENDENCIES="$(DESTDIR)$(libdir)/libalpha.la"
echo 1 > $(taint_file)
DISTCLEANFILES = $(taint_file)
This has been checked on Linux for a "standard" build, but it may not work on other build environments or if you are trying to do something "exotic". Hopefully it should, but it is not something that I have bothered to check. If there are errors, the problem will likely be a missing or misused variable in one of my targets above.
For example, the check_rebuild
target has the line:
check_rebuild:
# ...
$(MAKE) $(AM_MAKEFLAGS) beta$(EXEEXT)
# ...
The variables $(AM_MAKEFLAGS)
and $(EXEEXT)
are a part of how automake itself creates routines to populate in the Makefiles it creates, and had I neglected them in my targets above, then it might have caused the build to fail (or at least not function as expected).
I have tried to make sure the targets that I have suggested above are likewise canonically constructed, but I may have missed something out. Your best bet in case of build errors is to open the Makefiles generated by automake itself during the build, seeing how it is creating objects and mimicking those constructs in the corresponding Makefile.am files, as I have tried to do.
The other likely issue may be in the "hackish" way I've built the beta binary in the installcheck_rebuild
target. Again, your best bet in diagnosing problems will be to see how automake is doing things in the Makefiles it generates, and trying to mimic that. Otherwise, a read of the automake manual will be in order, and failing that, you will likely need the help of people more knowledgeable than me. Good luck.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With