Read a version number from a file in configure.ac

Try:

AC_INIT([my program], m4_esyscmd([tr -d '\n' < VERSION]))

Edited with fixes suggested in the comments.

I was also able to remove the non-portable tr invocation using:

AC_INIT([my program], [m4_translit(m4_esyscmd([cat VERSION]),m4_newline)])

which seems to work just as well, as does the solution suggested by Enrico in the comments below:

AC_INIT([my program], [m4_esyscmd_s([cat VERSION])])

You can simply use the native macro m4_include() (instead of invoking tr or cat via m4_esyscmd_s() as suggested by ldav1s),

AC_INIT([foo], m4_normalize(m4_include([VERSION])))

which is also what the official guide of GNU M4 suggests fo similar cases:

$ cat examples/incl.m4
⇒Include file start
⇒foo
⇒Include file end

[…]

The fact that include and sinclude expand to the contents of the file can be used to define macros that operate on entire files. Here is an example, which defines ‘bar’ to expand to the contents of incl.m4:

$ m4 -I examples
define(`bar', include(`incl.m4'))
⇒
This is `bar':  >>bar<<
⇒This is bar:  >>Include file start
⇒foo
⇒Include file end
⇒<<

Advantages:

  • By using m4_normalize(m4_include([VERSION])) instead of m4_esyscmd_s([cat VERSION]) the VERSION file is added to the DIST_COMMON variable in the Makefile – which means that the VERSION file is automatically redistributed when the user launches make dist
  • By using m4_normalize(m4_include([VERSION])) instead of m4_esyscmd_s([cat VERSION]) the VERSION file is added to the prerequisites of make aclocal.m4 (which means that each time you modify the VERSION file the configure script gets automatically updated)

GNU M4 offers also support for regular expressions, so if you want to make sure that the version string always follows a particular pattern – or if the VERSION file contains more text than just the version string – you can use m4_bregexp() to find what you are looking for:

AC_INIT([foo], m4_bregexp(m4_quote(m4_include([VERSION])), [[0-9]+\.[0-9]+\.[0-9]+], [\&]))

This is also the safest approach, since if the regular expression above cannot be found in the VERSION file the second argument of AC_INIT() simply expands to an empty string and the following error message is thrown by Autoconf:

error: AC_INIT should be called with package and version arguments

A typical case where it can be useful to invoke m4_bregexp() to process the content of a VERSION file is when this contains a three-number version string (MAJOR.MINOR.REVISION) but you only want a two-number version string (MAJOR.MINOR) as the expansion of your AC_PACKAGE_VERSION macro.

If you are familiar with regular expressions and capturing parentheses and you want to be able to do more complex tasks, I had written this general purpose variadic macro (you can paste it at the beginning of your configure.ac),

dnl  n4_define_substrings_as(string, regexp, macro0[, macro1[, ... macroN ]])
dnl  ***************************************************************************
dnl
dnl  Searches for the first match of `regexp` in `string` and defines custom
dnl  macros accordingly
dnl
dnl  For both the entire regular expression `regexp` (`\0`) and each
dnl  sub-expression within capturing parentheses (`\1`, `\2`, `\3`, ... , `\N`)
dnl  a macro expanding to the corresponding matching text will be created,
dnl  named according to the argument `macroN` passed. If a `macroN` argument is
dnl  omitted or empty, the corresponding parentheses in the regular expression
dnl  will be considered as non-capturing. If `regexp` cannot be found in
dnl  `string` no macro will be defined. If `regexp` can be found but some of
dnl  its capturing parentheses cannot, the macro(s) corresponding to the latter
dnl  will be defined as empty strings.
dnl
dnl  Source: https://github.com/madmurphy/not-autotools
dnl
dnl  ***************************************************************************
m4_define([n4_define_substrings_as],
    [m4_bregexp([$1], [$2],
        m4_ifnblank([$3],
            [[m4_define(m4_normalize([$3]), [m4_quote(\&)])]])[]m4_if(m4_eval([$# > 3]), [1],
            [m4_for([_idx_], [4], [$#], [1],
                [m4_ifnblank(m4_quote(m4_argn(_idx_, $@)),
                    [[m4_define(m4_normalize(m4_argn(]_idx_[, $@)), m4_quote(\]m4_eval(_idx_[ - 3])[))]])])]))])

which can be used for doing:

n4_define_substrings_as(

    m4_include([VERSION]),

    [\([0-9]+\)\s*\.\s*\([0-9]+\)\s*\.\s*\([0-9]+\)],

    [FOO_VERSION_STRING], [FOO_VERSION_MAJOR], [FOO_VERSION_MINOR], [FOO_VERSION_REVISION]

)

AC_INIT([foo], FOO_VERSION_MAJOR[.]FOO_VERSION_MINOR[.]FOO_VERSION_REVISION)

so that the macros FOO_VERSION_MAJOR, FOO_VERSION_MINOR and FOO_VERSION_REVISION are always available within configure.ac.

Note: The n4_ prefix in the n4_define_substrings_as() macro name stands for “Not m4sugar”.

If the regular expression above cannot be found in the VERSION file, n4_define_substrings_as() safely does not define the corresponding macro names. This allows to generate an error for this particular case (the following line must be pasted immediately after AC_INIT()):

m4_ifndef([FOO_VERSION_STRING], [AC_MSG_ERROR([invalid version format in `VERSION` file])])

For as trivial as it can look to read a simple VERSION file, things get trickier if you want to retrive the version string from a package.json file. Here the n4_define_substrings_as() macro can come very much in handy:

n4_define_substrings_as(

    m4_quote(m4_include([package.json])),

    ["?version"?:\s*"?\s*\(\([0-9]+\)\s*\.\s*\([0-9]+\)\s*\.\s*\([0-9]+\)\)\s*"?],

    [JSON_ENTRY], [FOO_VERSION_STRING], [FOO_VERSION_MAJOR], [FOO_VERSION_MINOR], [FOO_VERSION_REVISION]

)

AC_INIT([foo], FOO_VERSION_MAJOR[.]FOO_VERSION_MINOR[.]FOO_VERSION_REVISION)

The n4_define_substrings_as() macro accepts also empty arguments, so if you prefer you may replace the [JSON_ENTRY] argument with [], since probably you are never going to use the JSON source string "version": "999.9.9".

If you only need to retrieve the full version string from a package.json file but you don't need to use FOO_VERSION_MAJOR, FOO_VERSION_MINOR and FOO_VERSION_REVISION, you can get rid of some of the capturing parentheses in the regular expression above, as in the following example:

n4_define_substrings_as(

    m4_quote(m4_include([package.json])),

    ["?version"?:\s*"?\s*\([0-9]+\.[0-9]+\.[0-9]+\)\s*"?],

    [], [FOO_VERSION_STRING]

)

AC_INIT([foo], FOO_VERSION_STRING)

For completeness, since the last example has only one string to capture, it can also be rewritten without using n4_define_substrings_as() as:

AC_INIT([foo],
    m4_bregexp(m4_quote(m4_include([package.json])),
        ["?version"?:\s*"?\s*\([0-9]+\.[0-9]+\.[0-9]+\)\s*"?],
        [\1]))

Tags:

Autoconf