az-tags
¶The next step is to compile the program. We shall use Autotools, beginning
with the simplest configure.ac
we can:
AC_PREREQ([2.69]) AC_INIT([az-tags], [0.1], [sp1ff@pobox.com]) AC_CONFIG_MACRO_DIR([macros]) AC_CONFIG_SRCDIR([src/main.cc]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([-Wall -Werror]) LT_INIT AC_RROG_CXX AC_CONFIG_FILES([Makefile src/Makefile])
AC_PREREQ
just asserts that Autoconf 2.69 is required to build
a configure
script from this template. AC_INIT
is the
Autoconf initialization macro. We’re going to need some custom macros
for this project, so AC_CONFIG_MACRO_DIR
tells Autoconf where
to find them. AC_CONFIG_SRCDIR
is just a sanity check– when
running configure
users will sometimes pass an incorrect value
for --srcdir
– this macro equips the generated configure
script to catch that. AC_CONFIG_AUX_DIR
tells Autoconf to place
auxilliary scripts (missing
& ionstall-sh
, e.g.) in a
sub-directory named build-aux.
AC_CONFIG_HEADERS
tells Autoconf to generate a header file
named config.h containing C preprocessor #define
s
for the project. Note that we need to generate a template file
config.h.in via autoheader
.
Finally, we initialize Automake, libtool, check for a C++ compiler & produce Makefile templates.
The Autmake template for the root makefile is trivial:
SUBDIRS = src
Let us begin the Makefile template in src:
bin_PROGRAMS = az-tags az_tags_SOURCES = main.cc AM_CXXFLAGS = -std=c++17
We will need to perform some one-time setup:
mkdir build-aux touch NEWS README AUTHORS ChangeLog autoheader aclocal autoconf automake --add-missing
At this point, we can run ./configure
, but make
will
fail miserably. Our program needs to be able to find scribbu
,
openssl
and boost
includes, along with the corresponding
libraries. All the required libraries other than libscribbu
provide pre-built macros which we can copy from the scribbu source
distro into macros. Let us add the following lines to
configure.ac, just before the call to AC_CONFIG_FILES
:
PKG_CHECK_MODULES([GUILE], [guile-2.2]) AX_BOOST_BASE([1.58], [], [AC_MSG_ERROR([Scribbu requires boost_base 1.58 or later.])]) echo "Checkpoint 3: BOOST_LDFLAGS is $BOOST_LDFLAGS;" >&AS_MESSAGE_LOG_FD AX_BOOST_IOSTREAMS AX_BOOST_FILESYSTEM AX_BOOST_SYSTEM AX_CHECK_OPENSSL([],[AC_MSG_ERROR([Scribbu requires openssl.])])
Each of these will define Automake variables describing where we can find headers & libraries which we can add to src/Makefile.am, which now reads:
bin_PROGRAMS = az-tags az_tags_SOURCES = main.cc AM_CPPFLAGS = $(BOOST_CPPFLAGS) AM_CXXFLAGS = -std=c++17 $(GUILE_CFLAGS) AM_LDFLAGS = $(BOOST_LDFLAGS) LDADD = $(GUILE_LIBS) \ $(BOOST_SYSTEM_LIB) \ $(BOOST_FILESYSTEM_LIB) \ $(BOOST_IOSTREAMS_LIB) \ $(OPENSSL_LIBS)
This just leaves the question of where to find libscribbu
. scribbu,
at the time of this writing, provides no Autoconf macros (however, this
sample provided the author the opportunity to prototype one).
We add the following code to configure.ac, just after the call
to AC_PROG_CXX
(it’s a lot of code; step-by-step explanation to
follow):
AC_ARG_WITH([scribbu], AS_HELP_STRING([--with-scribbu=DIR], [root directory of scribbu installation]), [ case "$withval" in "" | y | ye | yes | n | no) AC_MSG_ERROR([--with-scribbu takes a root directory]);; *) scribbu_dirs="$withval";; esac ], [ # Just use the defaults scribbu_dirs="/usr/local /usr /opt/local /sw" ]) dnl One way or another, we have one or more candidates in ${scribbu_dirs} found=no for scribbu_home in ${scribbu_dirs}; do AC_MSG_CHECKING([for scribbu/scribbu.h under ${scribbu_home}]) if test -f "${scribbu_home}/include/scribbu/scribbu.hh"; then SCRIBBU_INCLUDES="-I${scribbu_home}/include/scribbu" SCRIBBU_LDFLAGS="-L${scribbu_home}/lib" SCRIBBU_LIBS="-lscribbu" found=yes AC_MSG_RESULT([yes]) break else AC_MSG_RESULT([no]) fi done if test "$found" != "yes"; then AC_MSG_ERROR([couldn't find scribbu]) fi # try the preprocessor and linker with our new flags, # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS AC_MSG_CHECKING([whether compiling and linking against scribbu will work]) save_LIBS="$LIBS" save_LDFLAGS="$LDFLAGS" save_CPPFLAGS="$CPPFLAGS" LIBS="$SCRIBBU_LIBS $LIBS" LDFLAGS="$SCRIBBU_LDFLAGS $LDFLAGS" CPPFLAGS="$SCRIBBU_CPPFLAGS $CPPFLAGS" AC_LANG_PUSH([C++]) AC_CHECK_HEADER([scribbu/scribbu.hh], [scribbu_hh=yes], [scribbu_hh=no]) # I'd like to do AC_CHECK_LIB here, but I can't link against libscribbu # in a test because it, in turn depends on a bunch of other libs AC_CHECK_FILE([${scribbu_home}/lib/libscribbu.la], [scribbu_la=yes], [scribbu_la=no]) AC_LANG_POP([C++]) LIBS="$save_LIBS" LDFLAGS="$save_LDFLAGS" CPPFLAGS="$save_CPPFLAGS" if test "yes" = "$scribbu_hh" && test "yes" = "$scribbu_la"; then AC_DEFINE([HAVE_SCRIBBU], [1], [Define to 1 if you have libscribbu]) else AC_MSG_ERROR([az-tags requires scribbu]) fi AC_SUBST([SCRIBBU_CPPFLAGS]) AC_SUBST([SCRIBBU_LIBS]) AC_SUBST([SCRIBBU_LDFLAGS])
The first step is to locate libscribbu
. We will form the
variable scribbu_dirs
containing one or more directories to
check. Now, the user could always just tell us where it is. That is
the reason we begin with AC_ARG_WITH
: if the user invokes
configure
with --with-scribbu=...
we will just use
that. Otherwise, we will examine a default set of locations.
That’s what the for look does; for each location in
scribbu_dirs
, it checks for scribbu.hh in a
sub-directory named include/scribbu of the current location. On
success, we set a few variables recording that result & break. If we
check all locations without success, then we fail.
Now, just because we found a header file at a given place doesn’t mean
we can biuld against it or its associated library. The typical idiom
is to execute the macros AC_CHECK_HEADER
and
AC_CHECK_LIB
to make sure we can include the header and link
against the library, respectively.
The problem in my case is that AC_CHECK_LIB
will fail, not
through any fault of libscribbu
, but because it depends on a
number of other libraries; the test will fail with unresolved
externals & I can’t see how to add the relevant link flags in the macro.
Instead, I settle for AC_CHECK_FILE
.
If both these pass, we know we’re good to go; the question remains: how
to record the information we’ve just discovered? The Autoconf manual
states that one should never add options to user variables such as
CPPFLAGS
. The idiom seems to be to define new variables that
the Automake author can add to their rules. In this case, create
three new variables:
SCRIBBU_CPPFLAGS
to hold the -I
option that will enable
the build to find the libscribbu
headers
SCRIBBU_LIBS
to hold the the -L
options that will
enable the build to link against libscribbu
SCRIBBU_LDLAGS
to hold any linker required flags
This lets us augment src/Makefile.am to:
bin_PROGRAMS = az-tags az_tags_SOURCES = main.cc AM_CPPFLAGS = $(BOOST_CPPFLAGS) $(SCRIBBU_CPPFLAGS) AM_CXXFLAGS = -std=c++17 $(GUILE_CFLAGS) AM_LDFLAGS = $(SCRIBBU_LDFLAGS) $(BOOST_LDFLAGS) LDADD = $(SCRIBBU_LIBS) \ $(GUILE_LIBS) \ $(BOOST_SYSTEM_LIB) \ $(BOOST_FILESYSTEM_LIB) \ $(BOOST_IOSTREAMS_LIB) \ $(OPENSSL_LIBS)
With that, we can configure
:
$>: autoreconf -vfi autoreconf: Entering directory `.' autoreconf: configure.ac: not using Gettext autoreconf: running: aclocal --force ... $>: ./configure --prefix=$HOME checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... /bin/mkdir -p ... config.status: creating Makefile config.status: creating src/Makefile config.status: creating config.h config.status: executing depfiles commands config.status: executing libtool commands $>: make make all-recursive make[1]: Entering directory '/tmp/az-tags' Making all in src make[2]: Entering directory '/tmp/az-tags/src' g++ -DHAVE_CONFIG_H -I. -I/home/mgh/doc/code/projects/az-tags/src -I.. -I/usr/include -std=c++17 -pthread -I/usr/local/include/guile/2.2 -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o /home/mgh/doc/code/projects/az-tags/src/main.cc ...
We have a build! Let us take a look at a file downloaded from Amazon.com:
$>: scribbu dump lorca.mp3 "lorca.mp3": ID3v2.3(.0) Tag: 452951 bytes, synchronised ... COMM (<no description>): Amazon.com Song ID: 203558254 ... 9425708 bytes of track data: MD5: 48ff9cadea7d842e9059db25159d2daa ID3v1.1: The Pogues - Lorca's Novena Hell's Ditch [Expanded] (US Ve (track 5), 1990 Amazon.com Song ID: 20355825 unknown genre 255 $>: src/az-tags lorca.mp3 lorca.mp3 has 1 ID3v2 tags, and an ID3v1 tag updating the comment frame containing Amazon.com Song ID: 203558254 all tags processed; emplacing new tagset... emplacing new tagset...done. clearing ID3v1 comment $>: scribbu dump lorca.mp3 "lorca.mp3": ID3v2.3(.0) Tag: 452951 bytes, synchronised ... COMM (amazon.com song id): Amazon.com Song ID: 203558254 ... 9425708 bytes of track data: MD5: 48ff9cadea7d842e9059db25159d2daa ID3v1.1: The Pogues - Lorca's Novena Hell's Ditch [Expanded] (US Ve (track 5), 1990 unknown genre 255