diff --git a/configure/CONFIG_BASE b/configure/CONFIG_BASE
index 8934a8e5d..8db74bac7 100644
--- a/configure/CONFIG_BASE
+++ b/configure/CONFIG_BASE
@@ -68,11 +68,11 @@ DBTOMENUH = $(PERL) $(TOOLS)/dbdToMenuH.pl
REGISTERRECORDDEVICEDRIVER = $(PERL) $(TOOLS)/registerRecordDeviceDriver.pl
CONVERTRELEASE = $(PERL) $(call FIND_TOOL,convertRelease.pl)
FULLPATHNAME = $(PERL) $(TOOLS)/fullPathName.pl
+GENVERSIONHEADER = $(PERL) $(TOOLS)/genVersionHeader.pl $(QUIET_FLAG)
#-------------------------------------------------------
# tools for installing libraries and products
-INSTALL_QUIETLY := $(if $(findstring s,$(MAKEFLAGS)),-q,)
-INSTALL = $(PERL) $(TOOLS)/installEpics.pl $(INSTALL_QUIETLY)
+INSTALL = $(PERL) $(TOOLS)/installEpics.pl $(QUIET_FLAG)
INSTALL_PRODUCT = $(INSTALL)
INSTALL_LIBRARY = $(INSTALL)
diff --git a/configure/CONFIG_COMMON b/configure/CONFIG_COMMON
index fcdadb7bf..e74f05e20 100644
--- a/configure/CONFIG_COMMON
+++ b/configure/CONFIG_COMMON
@@ -85,6 +85,7 @@ IOCS_APPL_TOP = $(shell $(FULLPATHNAME) $(INSTALL_LOCATION))
# Make echo output - suppress echoing if make's '-s' flag is set
NOP = :
ECHO = @$(if $(findstring s,$(patsubst T_A=%,,$(MAKEFLAGS))),$(NOP),echo)
+QUIET_FLAG := $(if $(findstring s,$(MAKEFLAGS)),-q,)
#-------------------------------------------------------
ifdef T_A
@@ -334,6 +335,14 @@ COMPILE.cpp = $(CCC) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES)
# C preprocessor command
PREPROCESS.cpp = $(CPP) $(CPPFLAGS) $(INCLUDES) $< > $@
+#--------------------------------------------------
+# genVersion header defaults
+
+# C macro name
+GENVERSIONMACRO = VCSVERSION
+# C macro default value (empty to use date+time)
+GENVERSIONDEFAULT =
+
#--------------------------------------------------
# Header dependency file generation
diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD
index a0f9537fe..7bc4c77d9 100644
--- a/configure/RULES_BUILD
+++ b/configure/RULES_BUILD
@@ -346,6 +346,14 @@ tapfiles: $(TESTSCRIPTS) $(TAPFILES)
@$(RM) $@
$(PERL) $(TOOLS)/makeTestfile.pl $@ $<
+#---------------------------------------------------------------
+# Generate header with version number from VCS
+
+ifneq ($(GENVERSION),)
+$(COMMON_DIR)/$(GENVERSION): FORCE
+ $(GENVERSIONHEADER) -t $(TOP) -N $(GENVERSIONMACRO) -V "$(GENVERSIONDEFAULT)" $@
+endif
+
#---------------------------------------------------------------
# Install rules for BIN_INSTALLS and LIB_INSTALLS
@@ -479,7 +487,7 @@ $(INSTALL_TEMPLATES_SUBDIR)/%: %
.PRECIOUS: $(COMMON_INC)
.PHONY: all host inc build install clean rebuild buildInstall build_clean
-.PHONY: runtests tapfiles checkRelease warnRelease noCheckRelease
+.PHONY: runtests tapfiles checkRelease warnRelease noCheckRelease FORCE
endif # BASE_RULES_BUILD
# EOF RULES_BUILD
diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
index d90a5847a..43838dcf6 100644
--- a/documentation/RELEASE_NOTES.html
+++ b/documentation/RELEASE_NOTES.html
@@ -20,6 +20,16 @@
-->
+
Generate Version Header
+
+A Perl script and Makefile rules have been added to allow modules to generate
+a C header file with a macro defined with an automatically updated identifier.
+This is a VCS revision ID (Darcs, Git, Mercurial Subversion and Bazaar are all
+supported) or the date/time of the build if no VCS system is in use.
+
+The makeBaseApp example template has been updated with a new device support
+which makes this identifier visible via a lsi (long string input) record.
+
epicsTime API return status
The epicsTime routines that used to return epicsTimeERROR now return a specific
diff --git a/src/template/base/Makefile b/src/template/base/Makefile
index 865d95c4a..bfc6de244 100644
--- a/src/template/base/Makefile
+++ b/src/template/base/Makefile
@@ -28,9 +28,12 @@ TEMPLATES += top/exampleApp/Makefile
TEMPLATES += top/exampleApp/Db/Makefile
TEMPLATES += top/exampleApp/Db/dbExample1.db
TEMPLATES += top/exampleApp/Db/dbExample2.db
+TEMPLATES += top/exampleApp/Db/_APPNAME_Version.db
TEMPLATES += top/exampleApp/Db/dbSubExample.db
TEMPLATES += top/exampleApp/Db/user.substitutions
TEMPLATES += top/exampleApp/src/Makefile
+TEMPLATES += top/exampleApp/src/dev_APPNAME_Version.c
+TEMPLATES += top/exampleApp/src/dev_APPNAME_Version.dbd
TEMPLATES += top/exampleApp/src/xxxRecord.dbd
TEMPLATES += top/exampleApp/src/xxxRecord.c
TEMPLATES += top/exampleApp/src/devXxxSoft.c
diff --git a/src/template/base/top/exampleApp/Db/Makefile b/src/template/base/top/exampleApp/Db/Makefile
index 667845ecc..679bba60c 100644
--- a/src/template/base/top/exampleApp/Db/Makefile
+++ b/src/template/base/top/exampleApp/Db/Makefile
@@ -6,6 +6,7 @@ include $(TOP)/configure/CONFIG
# Install databases, templates & substitutions like this
DB += dbExample1.db
DB += dbExample2.db
+DB += _APPNAME_Version.db
DB += dbSubExample.db
DB += user.substitutions
diff --git a/src/template/base/top/exampleApp/Db/_APPNAME_Version.db b/src/template/base/top/exampleApp/Db/_APPNAME_Version.db
new file mode 100644
index 000000000..1b03f08ba
--- /dev/null
+++ b/src/template/base/top/exampleApp/Db/_APPNAME_Version.db
@@ -0,0 +1,6 @@
+record(lsi, "$(user):_APPNAME_:version") {
+ field(DTYP, "_APPNAME_ version")
+ field(DESC, "Version string")
+ field(SIZV, "$(SIZV=200)")
+ field(PINI, "YES")
+}
diff --git a/src/template/base/top/exampleApp/src/Makefile b/src/template/base/top/exampleApp/src/Makefile
index f3b262c77..024726e24 100644
--- a/src/template/base/top/exampleApp/src/Makefile
+++ b/src/template/base/top/exampleApp/src/Makefile
@@ -13,19 +13,26 @@ DBD += xxxSupport.dbd
# Build an IOC support library
LIBRARY_IOC += _APPNAME_Support
-# Compile and add the code to the support library
+# Compile and add code to the support library
_APPNAME_Support_SRCS += xxxRecord.c
_APPNAME_Support_SRCS += devXxxSoft.c
# Link locally-provided code into the support library,
-# rather than directly into the IOC application.
-# This is required for Windows DLL builds.
+# rather than directly into the IOC application, that
+# causes problems on Windows DLL builds
_APPNAME_Support_SRCS += dbSubExample.c
+_APPNAME_Support_SRCS += dev_APPNAME_Version.c
_APPNAME_Support_SRCS += _APPNAME_Hello.c
_APPNAME_Support_SRCS += initTrace.c
_APPNAME_Support_LIBS += $(EPICS_BASE_IOC_LIBS)
+# Auto-generate a header file containing a version string.
+# Version comes from the VCS if available, else date+time.
+GENVERSION = _APPNAME_Version.h
+# Macro name
+GENVERSIONMACRO = _APPNAME_VERSION
+
# Build the IOC application
PROD_IOC = _APPNAME_
@@ -36,6 +43,7 @@ DBD += _APPNAME_.dbd
_APPNAME__DBD += base.dbd
_APPNAME__DBD += xxxSupport.dbd
_APPNAME__DBD += dbSubExample.dbd
+_APPNAME__DBD += dev_APPNAME_Version.dbd
_APPNAME__DBD += _APPNAME_Hello.dbd
_APPNAME__DBD += initTrace.dbd
@@ -77,3 +85,5 @@ include $(TOP)/configure/RULES
#----------------------------------------
# ADD EXTRA GNUMAKE RULES BELOW HERE
+# Explicit dependency needed for generated header file
+dev_APPNAME_Version$(DEP): $(COMMON_DIR)/$(GENVERSION)
diff --git a/src/template/base/top/exampleApp/src/dev_APPNAME_Version.c b/src/template/base/top/exampleApp/src/dev_APPNAME_Version.c
new file mode 100644
index 000000000..4f2c28f67
--- /dev/null
+++ b/src/template/base/top/exampleApp/src/dev_APPNAME_Version.c
@@ -0,0 +1,38 @@
+/* dev_APPNAME_Version.c */
+/* Example device support for the lsi (long string input) record
+ * providing the module version string as the value
+ */
+
+#include
+#include
+#include
+
+#include "devSup.h"
+#include "lsiRecord.h"
+
+#include "_APPNAME_Version.h"
+
+/* must be last include */
+#include "epicsExport.h"
+
+const char const version[] = _APPNAME_VERSION;
+
+static long read_string(lsiRecord *prec)
+{
+ size_t N = sizeof version;
+ char *buf = prec->val;
+
+ if (N > prec->sizv)
+ N = prec->sizv;
+ prec->len = N;
+
+ memcpy(buf, version, N);
+ buf[N - 1] = '\0';
+
+ return 0;
+}
+
+static lsidset dev_CSAFEAPPNAME_Version = {
+ 5, NULL, NULL, NULL, NULL, read_string
+};
+epicsExportAddress(dset,dev_CSAFEAPPNAME_Version);
diff --git a/src/template/base/top/exampleApp/src/dev_APPNAME_Version.dbd b/src/template/base/top/exampleApp/src/dev_APPNAME_Version.dbd
new file mode 100644
index 000000000..67295f3f0
--- /dev/null
+++ b/src/template/base/top/exampleApp/src/dev_APPNAME_Version.dbd
@@ -0,0 +1 @@
+device(lsi,INST_IO,dev_CSAFEAPPNAME_Version,"_APPNAME_ version")
diff --git a/src/template/base/top/exampleBoot/ioc/st.cmd@Common b/src/template/base/top/exampleBoot/ioc/st.cmd@Common
index 76360dd08..a2d018e3a 100644
--- a/src/template/base/top/exampleBoot/ioc/st.cmd@Common
+++ b/src/template/base/top/exampleBoot/ioc/st.cmd@Common
@@ -13,6 +13,7 @@ _CSAFEAPPNAME__registerRecordDeviceDriver pdbbase
## Load record instances
dbLoadTemplate "db/user.substitutions"
+dbLoadRecords "db/_APPNAME_Version.db", "user=_USER_"
dbLoadRecords "db/dbSubExample.db", "user=_USER_"
## Set this to see messages from mySub
diff --git a/src/template/base/top/exampleBoot/ioc/st.cmd@RTEMS b/src/template/base/top/exampleBoot/ioc/st.cmd@RTEMS
index 4addc5c29..cc96f84ab 100644
--- a/src/template/base/top/exampleBoot/ioc/st.cmd@RTEMS
+++ b/src/template/base/top/exampleBoot/ioc/st.cmd@RTEMS
@@ -11,6 +11,7 @@ _CSAFEAPPNAME__registerRecordDeviceDriver(pdbbase)
## Load record instances
dbLoadTemplate("db/user.substitutions")
+dbLoadRecords("db/_APPNAME_Version.db", "user=_USER_")
dbLoadRecords("db/dbSubExample.db", "user=_USER_")
## Set this to see messages from mySub
diff --git a/src/template/base/top/exampleBoot/ioc/st.cmd@vxWorks b/src/template/base/top/exampleBoot/ioc/st.cmd@vxWorks
index 27ad0572b..44a9afc67 100644
--- a/src/template/base/top/exampleBoot/ioc/st.cmd@vxWorks
+++ b/src/template/base/top/exampleBoot/ioc/st.cmd@vxWorks
@@ -20,6 +20,7 @@ _CSAFEAPPNAME__registerRecordDeviceDriver pdbbase
## Load record instances
dbLoadTemplate "db/user.substitutions"
+dbLoadRecords "db/_APPNAME_Version.db", "user=_USER_"
dbLoadRecords "db/dbSubExample.db", "user=_USER_"
## Set this to see messages from mySub
diff --git a/src/tools/Makefile b/src/tools/Makefile
index 580e32cb6..6b70b01cf 100644
--- a/src/tools/Makefile
+++ b/src/tools/Makefile
@@ -46,6 +46,7 @@ PERL_SCRIPTS += mkmf.pl
PERL_SCRIPTS += munch.pl
PERL_SCRIPTS += replaceVAR.pl
PERL_SCRIPTS += useManifestTool.pl
+PERL_SCRIPTS += genVersionHeader.pl
PERL_SCRIPTS += dbdToMenuH.pl
PERL_SCRIPTS += dbdToRecordtypeH.pl
diff --git a/src/tools/genVersionHeader.pl b/src/tools/genVersionHeader.pl
new file mode 100644
index 000000000..5851ea831
--- /dev/null
+++ b/src/tools/genVersionHeader.pl
@@ -0,0 +1,171 @@
+#!/usr/bin/env perl
+#*************************************************************************
+# Copyright (c) 2014 Brookhaven National Laboratory.
+# EPICS BASE is distributed subject to a Software License Agreement found
+# in file LICENSE that is included with this distribution.
+#*************************************************************************
+#
+# Generate a C header file which
+# defines a macro with a string
+# describing the VCS revision
+#
+
+use FindBin qw($Bin);
+use lib "$Bin/../../lib/perl";
+
+use EPICS::Getopts;
+use POSIX qw(strftime);
+
+use strict;
+
+# RFC 8601 date+time w/ zone (eg "2014-08-29T09:42:47-0700")
+my $tfmt = '%Y-%m-%dT%H:%M:%S';
+$tfmt .= '%z' unless $^O eq 'MSWin32'; # %z returns zone name on Windows
+my $now = strftime($tfmt, localtime);
+
+our ($opt_h, $opt_v, $opt_q);
+our $opt_t = '.';
+our $opt_N = 'VCSVERSION';
+our $opt_V = $now;
+
+my $vcs;
+
+getopts('hvqt:N:V:') && @ARGV == 1
+ or HELP_MESSAGE();
+
+my ($outfile) = @ARGV;
+
+if (!$vcs && -d "$opt_t/_darcs") { # Darcs
+ print "== Found /_darcs directory\n" if $opt_v;
+ # v1-4-dirty
+ # is tag 'v1' plus 4 patches
+ # with uncommited modifications
+ my $result = `cd "$opt_t" && echo "\$(darcs show tags | head -1)-\$((\$(darcs changes --count --from-tag .)-1))"`;
+ chomp $result;
+ print "== darcs show tags, changes:\n$result\n==\n" if $opt_v;
+ if (!$? && $result ne '') {
+ $opt_V = $result;
+ $vcs = 'Darcs';
+ # see if working copy has modifications, additions, removals, or missing files
+ my $hasmod = `darcs whatsnew --repodir="$opt_t" -l`;
+ $opt_V .= '-dirty' unless $?;
+ }
+}
+if (!$vcs && -d "$opt_t/.hg") { # Mercurial
+ print "== Found /.hg directory\n" if $opt_v;
+ # v1-4-abcdef-dirty
+ # is 4 commits after tag 'v1' with short hash abcdef
+ # with uncommited modifications
+ my $result = `hg tip --template '{latesttag}-{latesttagdistance}-{node|short}'`;
+ print "== hg tip:\n$result\n==\n" if $opt_v;
+ if (!$? && $result ne '') {
+ $opt_V = $result;
+ $vcs = 'Mercurial';
+ # see if working copy has modifications, additions, removals, or missing files
+ my $hasmod = `hg status -m -a -r -d`;
+ chomp $hasmod;
+ $opt_V .= '-dirty' if $hasmod ne '';
+ }
+}
+if (!$vcs && -d "$opt_t/.git") { # Git
+ print "== Found /.git directory\n" if $opt_v;
+ # v1-4-abcdef-dirty
+ # is 4 commits after tag 'v1' with short hash abcdef
+ # with uncommited modifications
+ my $result = `git describe --always --tags --dirty --abbrev=20`;
+ chomp $result;
+ print "== git describe:\n$result\n==\n" if $opt_v;
+ if (!$? && $result ne '') {
+ $opt_V = $result;
+ $vcs = 'Git';
+ }
+}
+if (!$vcs && -d "$opt_t/.svn") { # Subversion
+ print "== Found /.svn directory\n" if $opt_v;
+ # 12345-dirty
+ my $result = `cd "$opt_t" && svn info --non-interactive`;
+ chomp $result;
+ print "== svn info:\n$result\n==\n" if $opt_v;
+ if (!$? && $result =~ /^Revision:\s*(\d+)/m) {
+ $opt_V = $1;
+ $vcs = 'Subversion';
+ # see if working copy has modifications, additions, removals, or missing files
+ my $hasmod = `cd "$opt_t" && svn status --non-interactive`;
+ chomp $hasmod;
+ $opt_V .= '-dirty' if $hasmod ne '';
+ }
+}
+if (!$vcs && -d "$opt_t/.bzr") { # Bazaar
+ print "== Found /.bzr directory\n" if $opt_v;
+ # 12444-anj@aps.anl.gov-20131003210403-icfd8mc37g8vctpf-dirty
+ my $result = `bzr version-info -q --custom --template="{revno}-{revision_id}-{clean}"`;
+ print "== bzr version-info:\n$result\n==\n" if $opt_v;
+ if (!$? && $result ne '') {
+ $result =~ s/-([01])$/$1 ? '' : '-dirty'/e;
+ $opt_V = $result;
+ $vcs = 'Bazaar';
+ }
+}
+if (!$vcs) {
+ print "== No VCS directories\n" if $opt_v;
+ if ($opt_V eq '') {
+ $vcs = 'build date/time';
+ $opt_V = $now;
+ }
+ else {
+ $vcs = 'Makefile';
+ }
+}
+
+my $output = << "__END";
+/* Generated file, do not edit! */
+
+/* Version determined from $vcs */
+
+#ifndef $opt_N
+ #define $opt_N \"$opt_V\"
+#endif
+__END
+
+print "== Want:\n$output==\n" if $opt_v;
+
+my $DST;
+if (open($DST, '+<', $outfile)) {
+ my $actual = join('', <$DST>);
+ print "== Current:\n$actual==\n" if $opt_v;
+
+ if ($actual eq $output) {
+ print "Keeping VCS header $outfile\n $opt_N = \"$opt_V\"\n"
+ unless $opt_q;
+ exit 0;
+ }
+ print "Updating VCS header $outfile\n $opt_N = \"$opt_V\"\n"
+ unless $opt_q;
+} else {
+ print "Creating VCS header $outfile\n $opt_N = \"$opt_V\"\n"
+ unless $opt_q;
+ open($DST, '>', $outfile)
+ or die "Can't create $outfile: $!\n";
+}
+
+seek $DST, 0, 0;
+truncate $DST, 0;
+print $DST $output;
+close $DST;
+
+sub HELP_MESSAGE {
+ print STDERR <