VerySillyMUD: Adding Autotools
Series [ VerySillyMUD ]
This post is part of my “VerySillyMUD” series, chronicling an attempt to refactor an old MUD into working condition [ 1 ].
In our
last episode,
we got all the remaining compiler warnings fixed. While I
was merging the pull request for it, I noticed GitHub was prompting me
to get continuous integration set up. I had run across
Travis CI, and knew that it
was free to use for open source projects.
A quick peek around their documentation shows that
they support C,
but that they assume the use of
GNU autotools
for building. Since a friend had already identified
weirdness from the runs of makedepend
I had done on my own
computer and checked in, I actually already had
an issue open for
this. Seems like the universe is trying to tell me something!
Conveniently, autotools
comes with a sample project and a
pretty good step-by-step tutorial. We also have a working
Makefile
that we can use for reference–for now I’m just
going to make a temporary copy of it as Makefile.orig
so that
I can have it for easy reference, and then clean it up later during
commit/PR structuring. Since automake
is going to overwrite
the Makefile
, this will be convenient, even though I know a
copy of the Makefile is safely tucked away in version control. Ok,
let’s start with the toplevel Makefile.am
, which for now just
has to point to the src/
directory:
Then we need another Makefile.am
for the src/
directory. In this case, it looks like the bare minimum is to identify
the final executable name and then identify the .c
source
files. Not sure if we need to add the .h
ones or not yet; it
could be that autoconf
will find those later. Anyway, let’s try
this:
As for the configure.ac
in the source directory, we can adapt
the sample one from
here
and try this:
Now, per the instructions, we’re supposed to run
autoreconf --install
:
Hmm, I had thought that all the CFLAGS
would go in the
AM_INIT_AUTOMAKE
macro, but I guess not. Let’s just put
-Werror
in there for now and try again:
Ok, this is much closer. Looks like we just have some missing
files. For now, I’ll create empty versions of NEWS
,
README
, AUTHORS
, and ChangeLog
, and
remember to create an issue to fill those in. As for COPYING
,
that’s traditionally the license file, so we’ll just make a copy of
doc/license.doc
and use that. Now when we run
autoreconf --install
it completes successfully! Ok, let’s try running
./configure
:
Wow, that worked. Ok, let’s try doing a make
:
Ah, failure. A quick look at the output shows we’re missing some
CFLAGS
; this might be the source of this compilation error,
since one of the compilation flags was -DGROUP_NAMES
and that
might be what the gname
field is for. A quick look at the
declaration of struct char_special_data
in structs.h
confirms this is the case. Ok, so we just need to figure out how to
get the CFLAGS
declared properly. According to
this answer
on StackOverflow, it seems we can just add them in the
configure.ac
file.
CFLAGS+=" -DIMPL_SECURITY -DNEW_RENT -DLEVEL_LOSS -DNEWEXP -DGROUP_NAMES"
In the process of looking for advice on adding the CFLAGS
, I
ran across a description of the autoscan
tool that will scan
your source code and suggest missing macro invocations for your
configure.ac
. A quick run shows that we’re mostly missing
detection of the header files we include and the library functions we
call, so we’ll just add that in too:
AC_CHECK_HEADERS([arpa/inet.h fcntl.h malloc.h netdb.h netinet/in.h
stdlib.h string.h strings.h sys/socket.h sys/time.h unistd.h])
AC_CHECK_HEADER_STDBOOL
AC_TYPE_SIZE_T
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([bzero gethostbyaddr gethostbyname gethostname
getpagesize gettimeofday inet_ntoa isascii memset select socket
strchr strdup strrchr strstr])
And…wow! It builds without errors. Now, there’s still a few things
missing here, like actually using the defined macros in the
config.h
that the configure
script generates; we
also haven’t gotten the tests running yet, or looked at what
make install
wants to do. So let’s get
started with the tests. The first thing we’re going to want to do is
pull the common source code files out into their own variable:
common_sources = comm.c act.comm.c act.info.c act.move.c act.obj1.c \
act.obj2.c act.off.c act.other.c act.social.c act.wizard.c handler.c \
db.c interpreter.c utility.c spec_assign.c shop.c limits.c mobact.c \
fight.c modify.c weather.c spells1.c spells2.c spell_parser.c \
reception.c constants.c spec_procs.c signals.c board.c magic.c \
magic2.c skills.c Opinion.c Trap.c magicutils.c multiclass.c hash.c \
Sound.c Heap.c spec_procs2.c magic3.c security.c spec_procs3.c \
create.c bsd.c parser.c intrinsics.c
dmserver_SOURCES = $(common_sources) main.c
At this point, while perusing through the automake
manual to
figure out how to do tests, I discovered there was a better way to
define the symbols instead of adding them to CFLAGS
in
configure.ac
; there’s an automake
variable for this
called AM_CFLAGS
, so we just
move the flags over
to Makefile.am
instead. But in the
meantime, the next step towards tests would be to correctly find the
header files and library for Criterion in the configure
script, so that the generated Makefile looks for them in the right
place. We can do this by
adding the following
to configure.ac
:
AC_CHECK_HEADERS([criterion/criterion.h],[],[
echo "***WARNING***: unit tests will not be runnable without Criterion library"
echo " See https://github.com/Snaipe/Criterion/"
])
AC_CHECK_LIB(criterion,extmatch,[],[
echo "***WARNING***: unit tests will not be runnable without Criterion library"
echo " See https://github.com/Snaipe/Criterion/"
])
After a little more perusing through the autotools
manual, it
turns out instead of the echo
command there’s a canonical way
to do this
using the AC_MSG_WARN
macro,
as in:
AC_CHECK_HEADERS([criterion/criterion.h],[],[
AC_MSG_WARN(unit tests will not be runnable without Criterion library)
AC_MSG_WARN(See https://github.com/Snaipe/Criterion/)
])
Now, when we then run make
, we find the Criterion library,
but the final dmserver
executable gets linked with
-lcriterion
, which we don’t want, because as you may recall,
that library has a main()
function in it that is going to try
to run test suites, so we don’t actually want the default action of
AC_CHECK_LIB
. Instead, we need to fake it out:
AC_CHECK_LIB(criterion,extmatch,[
AC_MSG_RESULT(yes)
],[
AC_MSG_WARN(unit tests will not be runnable without Criterion library)
AC_MSG_WARN(See https://github.com/Snaipe/Criterion/)
])
And now we can go ahead and build the unit test program and indicate
that’s what should run during make check
by
adding the following
to src/Makefile.am
:
# unit tests
check_PROGRAMS = tests
tests_SOURCES = $(common_sources) test.act.wizard.c
tests_LDADD = -lcriterion
TESTS = tests
Sure enough, we can run the tests:
Nice. Ok, now these hardcoded AM_CFLAGS
are still bothering
me. Really, we ought to be able to opt into and out of them via
feature enabling/disabling from configure
. My friend Dan
would probably, at this point, say “Why?” in incredulity, but this is
not an exercise in practicality, per se… The way we do that is to
add these flags to configure.ac
,
which will cause
configure
to output them into config.h
. We can do
that with stanzas like the following:
AH_TEMPLATE([IMPL_SECURITY],
[Define as 1 to restrict each level 58+ god to one site or set of
sites])
AC_ARG_ENABLE([impl-security],
[AS_HELP_STRING([--disable-impl-security],
[turn off site-locking for gods])],
[],
[AC_DEFINE([IMPL_SECURITY])])
Ok, then we need to just go around and include config.h
in
all the
.c
and .h
files, and then we can
remove the AM_CFLAGS
from Makefile.am
. Cleaner! At this
point, the last thing to do is to get make install
to
work. It turns out the default action for the MUD server itself is the
right one, but we also need to collect the data files and install
them. This can be done by creating Makefile.am
s in various
subdirectories. For example, here’s the one for the top-level
lib/
directory:
Then the last thing to is to make the compiled server look there by
default. The configure
script takes a
--localstatedir
argument to customize where those data
directories get created; we really want the game to have that path
compiled into it as a default path. After much noodling through
StackOverflow and the automake manuals, it looks like the best way to
do this is a multi-step process in order to get the right amount of
variable expansion. First, we have to bring our friend
AM_CFLAGS
back and pass the Makefile
level variable
$(localstatedir)
in as a symbol definition:
AM_CFLAGS = -DLOCAL_STATE_DIR=\"$(localstatedir)\"
Then, we can add the following to configure.ac
:
AC_DEFINE([DEFAULT_LIBDIR],[LOCAL_STATE_DIR "/sillymud"],
[Default location to look for game data files])
…which results in the following showing up in config.h
:
/* Default location to look for game data files */
#define DEFAULT_LIBDIR LOCAL_STATE_DIR "/sillymud"
This makes whatever got passed in to the --localstatedir
argument of configure
, which defaults to
/usr/local/var
, to show up as a string literal
LOCAL_STATE_DIR
. In C, two string literals adjacent to one
another get concatenated, so this results in DEFAULT_LIBDIR
being something like /usr/local/var/sillymud
. At this point,
we’re able to run make install
and run
/usr/local/bin/dmserver
. I think for our
next episode,
it’s time to do a little play testing to see how well
things are working and what else needs fixing!
[ 1 ] SillyMUD was a derivative of DikuMUD, which was originally created by Sebastian Hammer, Michael Seifert, Hans Henrik Stærfeldt, Tom Madsen, and Katja Nyboe.