tags:

views:

236

answers:

4

I'm trying to embed my Subversion revision number in a C++ project and am having problems setting up GNU make to do so. My makefile currently looks something like this:

check-svnversion:
  ../shared/update-svnversion-h.pl

../shared/svnversion.h: check-svnversion

shared/svnversion.o: ../shared/svnversion.h

.PHONY: check-svnversion

svnversion.o depends on svnversion.cpp (via a pattern rule) and svnversion.h (listed explicitly because dependency checking isn't picking it up for some reason). svnversion.h is created and maintained by the update-svnversion-h.pl script (which basically just runs svnversion and munges the output into a C++ file).

Currently, I have to run make twice to get the file up to date. The first time, make runs update-svnversion-h.pl (since it's listed as a prerequisite) but does not check the timestamp of svnversion.h afterwards to see that it was changed by update-svnversion-h.pl, so it does not remake svnversion.o. The second time, it does check the timestamp, runs update-svnversion-h.pl anyway (which doesn't do anything this time, since svnversion.h is up to date), then recompiles svnversion.cpp to make svnversion.o.

Is there a way to tell GNU make to evaluate a single prerequisite twice or to delay checking the timestamp on a prerequisite until after that prerequisite's commands are finished?

Alternatively, is there a better way to embed a revision number in my source code? (For the sake of speed, I'm trying to avoid solutions that would require recompilation on every build.)

+1  A: 

You can use Subversion's keyword substitution to put the version number into your code.

It's detailed [in the book], but in short you will have to set the svn:keywords property for the file to include Rev and then put $Rev$ somewhere in your code. SVN will automatically replace this with $Rev: #$, where # is the revision number. You mat have to parse the resulting string to get your actual number, but it should be an easy parse (just drop the first six characters and the last character).

Note that this is the last changed revision of that file, not the repo's last changed revision. That is slightly more complex and detailed on the book page.

coppro
I want the repo's last changed revision, not the last changed revision of the file. The book says to use svnversion for that "plus some additional tooling". I'm using svnversion; my question is how to get the additional tooling to do what I want it to.
Josh Kelley
+1  A: 

This seems similar to what autotools does for its config.h header.

A quick look at automake's remake-hdr.am shows how it does the trick:

%CONFIG_H%: %STAMP%
## Recover from removal of CONFIG_HEADER
    @if test ! -f $@; then \
      rm -f %STAMP%; \
      $(MAKE) $(AM_MAKEFLAGS) %STAMP%; \
    else :; fi


%STAMP%: %CONFIG_H_DEPS% $(top_builddir)/config.status
    @rm -f %STAMP%
    cd $(top_builddir) && $(SHELL) ./config.status %CONFIG_H_PATH%

And config.status creates the stamp file.

The main difference between this automake example and your example seems to be that automake touches the stamp file, while in your example it is not even a real file.

CesarB
A: 

I solved this by having my check-svnversion rule delete svnversion.o to force it to be recompiled if needed. This isn't exactly elegant, but it works; it looks similar to the autotools solution described in CesarB's answer.

../shared/svnversion.h: check-svnversion

check-svnversion:
    ../shared/update-svnversion-h.pl
    @#Force recreation of svnversion.o.  Otherwise, make won't notice that
    @#svnversion.h has changed until the next time it's invoked.
    @if [ ../shared/svnversion.h -nt shared/svnversion.o ] ; then rm shared/svnversion.o ; fi

shared/svnversion.o: ../shared/svnversion.h

.PHONY: check-svnversion
Josh Kelley
+1  A: 

Here is a target I put in my make files. It creates a couple of functions that return various build telemetreis as strings. The nice thing being that the telemetries represent the last time linked, not when an object is built.

##
## on every build, record the working copy revision string
##
svn_version.c: $(C_SRC:.c=.o) Makefile
    @echo -n 'const char* build_date(void);\n'  > $@
    @echo -n 'const char* build_date(void)\n{ static const char* build_date = __DATE__ ; '  >> $@
    @echo 'return build_date; }\n'                                                          >> $@
    @echo -n 'const char* build_time(void);\n'                                              >> $@
    @echo -n 'const char* build_time(void)\n{ static const char* build_time = __TIME__ ; '  >> $@
    @echo 'return build_time; }\n'                                                          >> $@
    @echo -n 'const char* svnid_build(void);\n'                                             >> $@
    @echo -n 'const char* svnid_build(void)\n{ static const char* SVN_Version = "\\n<SVN_PID>'    >> $@
    @svnversion -cn .                                                                       >> $@
    @echo '</svn_pid>\\n"; return SVN_Version; }\n'                                         >> $@
    @echo -n 'const char* svnid_build_str_only(void);\n'                                    >> $@
    @echo -n 'const char* svnid_build_str_only(void)\n{ static const char* SVN_Version = "' >> $@
    @svnversion -cn .                                                                       >> $@
    @echo '"; return SVN_Version; }\n'                                                      >> $@
    @$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $(@:.c=.o) $@

Be sure to replace the dependancy on the your object files in your final target with simply 'svn_version.c'. Ex.

final-target: svn_version.c
    $(CC) $(C_SRC:.c=.o) $(<:.c=.o) -o $@
Jamie
Very nice. It forces a recompilation, which I'm trying to avoid, but it's very elegant, and adding a timestamp is useful too.
Josh Kelley