[Git][NTPsec/ntpsec][master] 2 commits: NEWS - add notices on dodged CVEs.
Eric S. Raymond
gitlab at mg.gitlab.com
Tue Mar 13 17:41:46 UTC 2018
Eric S. Raymond pushed to branch master at NTPsec / ntpsec
Commits:
c6256c2e by Eric S. Raymond at 2018-03-13T17:37:49Z
NEWS - add notices on dodged CVEs.
- - - - -
3aae297a by Eric S. Raymond at 2018-03-13T17:38:36Z
Revert "Use python in wscript instead of autorevision.sh"
Too close to release. Maybe I need to start using caffeine...
- - - - -
19 changed files:
- .gitignore
- INSTALL
- NEWS
- VERSION
- devel/TODO
- devel/hacking.txt
- devel/ifdex-ignores
- devel/make-tarball
- devel/release.nix
- ntpd/ntpd.c
- ntpd/wscript
- ntptime/ntptime.c
- pylib/util.py.in → pylib/util.py
- pylib/wscript
- tests/pylib/test_util.py.in → tests/pylib/test_util.py
- tests/wscript
- + wafhelpers/autorevision.sh
- wafhelpers/options.py
- wscript
Changes:
=====================================
.gitignore
=====================================
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
+wafhelpers/.autorevision-cache
.lock-waf*
.waf*
*.pyc
+ntpd/version.h
build
=====================================
INSTALL
=====================================
--- a/INSTALL
+++ b/INSTALL
@@ -166,6 +166,28 @@ Linux File Hierarchy Standard.
You should have neither issue if you install from an OS distribution's
binary package.
+== Caveat for Crossbuilders ==
+
+If you see a build failure with the message "error: No repo or cache
+detected.", you have collided with an unusual feature of our build recipe.
+
+To generate the version.h file, the uses a script called
+'autorevision.sh' which needs one of two preconditions. Either (1)
+you are building in a local repository clone, or (2) you are building
+from an unpacked release tarball containing a file
+wafhelpers/.autorevision-cache containing version information.
+
+The motivation for this behavior is to have a version string that
+makes sense on every build from either the repository or a release
+tarball. It means, however, that if you try to build when waf can see
+neither of these things, you'll get the "No repo or cache detected"
+error. This can happen when atrempting to use a remote buildroot for
+cross-compilation or other purposes
+
+There are a couple of possible workarounds. One is to do your remote build
+from a release tarball. Another is to do a ./waf configure and build in the
+source directory before attempting a buildroot build.
+
== Installation Names ==
By default, `waf install' will install the package's files in
=====================================
NEWS
=====================================
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,14 @@ Digests longer then 20 bytes will be truncated.
We have merged Classic's fix for CVE-2018-7182.
+The following NTP Classic CVEs announced in February 2018 do not affect NTPsec:
+
+* CVE-2016-1549: Sybil vulnerability: ephemeral association attack
+* CVE-2018-7170: Multiple authenticated ephemeral associations
+* CVE-2018-7184: Interleaved symmetric mode cannot recover from bad state
+* CVE-2018-7185: Unauthenticated packet can reset authenticated interleaved association
+* CVE-2018-7183: ntpq:decodearr() can write beyond its buffer limit
+
We have dropped support for Broadcast servers.
It is now possible to unpeer refclocks using a type/unit specification
=====================================
VERSION
=====================================
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0
+1.0.1
=====================================
devel/TODO
=====================================
--- a/devel/TODO
+++ b/devel/TODO
@@ -104,6 +104,10 @@ Waf detect need configure:
after a git pull. (From email on 23 May 2017)
Version string cleanup:
+ This is tangled up with EPOCH
+ configure has --build-version-tag=
+ configure sets up NTPSEC_VERSION_STRING
+ ntptime is the only useage.
The version string should change if I make an edit and rebuild
currently it only changes when something in git changes
We need to be sure not to break the stable checksum feature.
=====================================
devel/hacking.txt
=====================================
--- a/devel/hacking.txt
+++ b/devel/hacking.txt
@@ -377,7 +377,7 @@ a large common include file. These includes live in docs/includes
and are probably what you need to edit if you're updating anything
that appears on a man page.
-=== Version number ===
+=== Version string ===
We use a variant of three part Semantic Versioning, of the form X.Y.Z.
X, Y, and Z are non-negative decimal integers.
@@ -411,20 +411,6 @@ Note that this is a different numbering system from NTP Classic. In
their A.B.C numbers, A was the protocol version, B was the major, and
C was the minor. They also use release-candidate suffixes.
-== Version string ==
-
-We use the BUILD_EPOCH as described in packaging/packaging.txt for the
-timestamp (converted to RFC 3339 format) part of the version string reported in
-ntpd and other binaries.
-
-Release builds have version strings of the form:
-
-<base version number> <BUILD_EPOCH in RFC 3339 format>
-
-Development builds have version strings of the form:
-
-<base version number>+ <BUILD_EPOCH in RFC 3339 format> (git rev <git shorthash>)
-
== Contribution workflow and conventions ==
Please work on one piece of conceptual work at a time.
=====================================
devel/ifdex-ignores
=====================================
--- a/devel/ifdex-ignores
+++ b/devel/ifdex-ignores
@@ -87,6 +87,7 @@ TEST_LIBPARSE
# Purely internal symbols
CONFIG_H
+AUTOREVISION_H
BITSPERCHAR # Only used in the Arcron refclock
BACKTRACE_MAXFRAME # Internal to the ISC backtrace code
BACKTRACE_LIBC
=====================================
devel/make-tarball
=====================================
--- a/devel/make-tarball
+++ b/devel/make-tarball
@@ -5,6 +5,9 @@
# a file named ntpsec-$V.tar.gz. The name of this tarball is echoed to
# standard output as part of the success message.
#
+# The tricky part is that it has to include the autorevision cache file,
+# which can't be checked into the repo or there'd be an infinite loop.
+#
# Do not try running this outside of devel/
#
# *Do* configure and test-build before running it.
@@ -15,6 +18,12 @@ then
exit 1
fi
+if [ ! -r ../wafhelpers/.autorevision-cache ]
+then
+ echo "Autorevision cache file does not exist, waf build and try again"
+ exit 1
+fi
+
set -e
if [ "$1" != "" ]
@@ -26,7 +35,7 @@ fi
# Build the tarball
rm -fr .tmp
-(cd ..; git ls-files; find build -print | grep '\.[0-9]$') >MANIFEST
+(cd ..; git ls-files; find build -print | grep '\.[0-9]$'; echo "wafhelpers/.autorevision-cache") >MANIFEST
(cd ..; tar --transform="s:^:ntpsec-${V}/:" -T devel/MANIFEST -czf ntpsec-${V}.tar.gz)
rm MANIFEST
mv ../ntpsec-${V}.tar.gz .
=====================================
devel/release.nix
=====================================
--- a/devel/release.nix
+++ b/devel/release.nix
@@ -28,6 +28,10 @@ in with derivationOptions; rec {
distPhase = ''
runHook preDist
+ if [ -n "''${versionSuffix}" ]; then
+ distFlags="--build-version-tag=$versionSuffix $distFlags"
+ fi
+
echo "dist flags: $distFlags ''${distFlagsArray[@]}"
python waf dist $distFlags "''${distFlagsArray[@]}"
@@ -56,6 +60,10 @@ in with derivationOptions; rec {
configureFlags="''${prefixKey:---prefix=}$prefix $configureFlags"
fi
+ if [ -n "$versionSuffix" ]; then
+ configureFlags="--build-version-tag=$versionSuffix $configureFlags"
+ fi
+
echo "configure flags: $configureFlags ''${configureFlagsArray[@]}"
python waf configure $configureFlags "''${configureFlagsArray[@]}"
=====================================
ntpd/ntpd.c
=====================================
--- a/ntpd/ntpd.c
+++ b/ntpd/ntpd.c
@@ -32,6 +32,8 @@
#include "recvbuff.h"
+#include "version.h"
+
void catchQuit (int sig);
static volatile int signo = 0;
/* In an ideal world, 'finish_safe()' would declared as noreturn... */
@@ -483,7 +485,7 @@ const char *ntpd_version(void)
{
static char versionbuf[64];
snprintf(versionbuf, sizeof(versionbuf),
- "ntpd ntpsec-%s", NTPSEC_VERSION);
+ "ntpd ntpsec-%s+%d %s", VERSION, VCS_TICK, VCS_DATE);
return versionbuf;
}
=====================================
ntpd/wscript
=====================================
--- a/ntpd/wscript
+++ b/ntpd/wscript
@@ -2,6 +2,8 @@
def build(ctx):
srcnode = ctx.srcnode.abspath()
bldnode = ctx.bldnode.abspath()
+ target3 = ctx.srcnode.make_node('ntpd/version.h')
+ target4 = ctx.srcnode.make_node('wafhelpers/.autorevision-cache')
if ctx.variant == "host":
bison_source = ["ntp_parser.y"]
@@ -15,7 +17,15 @@ def build(ctx):
target="bison_obj",
)
- # Generate Bison file first.
+ ctx(
+ cwd=srcnode,
+ rule='VCS_EXTRA=`cat ${SRC[0]}` wafhelpers/autorevision.sh '
+ '-o ${TGT[1].abspath()} -e VERSION -t h >${TGT[0].abspath()}',
+ source=["../VERSION", '../wafhelpers/autorevision.sh'],
+ target=[target3, target4],
+ )
+
+ # Generate Bison and version.h files first.
ctx.add_group()
keyword_gen_source = ["keyword-gen.c", ]
=====================================
ntptime/ntptime.c
=====================================
--- a/ntptime/ntptime.c
+++ b/ntptime/ntptime.c
@@ -410,7 +410,7 @@ main(
}
if (json)
/* hack to avoid trailing comma - not semantically needed */
- printf("\"version\":\"ntpsec-%s\"}\n", NTPSEC_VERSION);
+ fputs("\"version\":\"" NTPSEC_VERSION_STRING "\"}\n", stdout);
exit(EXIT_SUCCESS);
}
=====================================
pylib/util.py.in → pylib/util.py
=====================================
--- a/pylib/util.py.in
+++ b/pylib/util.py
@@ -13,6 +13,7 @@ import socket
import sys
import time
import ntp.ntpc
+import ntp.version
import ntp.magic
import ntp.control
@@ -106,7 +107,9 @@ def safeargcast(arg, castfunc, errtext, usage):
def stdversion():
"Returns the NTPsec version string in a standard format"
- return "ntpsec-%s" % "@NTPSEC_VERSION@"
+ return "ntpsec-%s+%s %s" % (ntp.version.VERSION,
+ ntp.version.VCS_TICK,
+ ntp.version.VCS_DATE)
def rfc3339(t):
=====================================
pylib/wscript
=====================================
--- a/pylib/wscript
+++ b/pylib/wscript
@@ -16,21 +16,15 @@ def build(ctx):
bldnode = ctx.bldnode.make_node('pylib')
target1 = bldnode.make_node('control.py')
target2 = bldnode.make_node('magic.py')
-
- ctx(
- features="subst",
- source='util.py.in',
- target='util.py'
- )
- # Force early creation of util.py
- ctx.add_group()
-
+ target3 = bldnode.make_node('version.py')
+ target4 = ctx.srcnode.make_node('wafhelpers/.autorevision-cache')
sources = srcnode.ant_glob('*.py')
builds = [x.get_bld() for x in sources]
# Remove generated files to ensure they are properly updated
ctx.exec_command("rm -f %s" % target1.abspath())
ctx.exec_command("rm -f %s" % target2.abspath())
+ ctx.exec_command("rm -f %s" % target3.abspath())
# Make sure Python sees .py as well as .pyc/.pyo
ctx(
@@ -55,12 +49,21 @@ def build(ctx):
target=target2,
)
+ ctx(
+ before=['pyc', 'pyo'],
+ cwd=srcnode,
+ rule='VCS_EXTRA=`cat ${SRC[0]}` ../wafhelpers/autorevision.sh '
+ '-o ${TGT[1].abspath()} -e VERSION -t python >${TGT[0].abspath()}',
+ source=["../VERSION", '../wafhelpers/autorevision.sh'],
+ target=[target3, target4],
+ )
+
# Force early creation of generated files
ctx.add_group()
ctx(
features='py',
- source=builds+[target1, target2],
+ source=builds+[target1, target2, target3],
install_from=bldnode,
install_path='${PYTHONDIR}/ntp'
)
=====================================
tests/pylib/test_util.py.in → tests/pylib/test_util.py
=====================================
--- a/tests/pylib/test_util.py.in
+++ b/tests/pylib/test_util.py
@@ -96,8 +96,10 @@ class TestPylibUtilMethods(unittest.TestCase):
def test_stdversion(self):
f = ntp.util.stdversion
- ver = "@NTPSEC_VERSION@"
- self.assertEqual(f(), "ntpsec-" + ver)
+ ver = str(ntp.version.VERSION)
+ tick = str(ntp.version.VCS_TICK)
+ date = str(ntp.version.VCS_DATE)
+ self.assertEqual(f(), "ntpsec-" + ver + "+" + tick + " " + date)
def test_rfc3339(self):
f = ntp.util.rfc3339
=====================================
tests/wscript
=====================================
--- a/tests/wscript
+++ b/tests/wscript
@@ -126,14 +126,6 @@ def build(ctx):
pass
os.symlink(pypath.abspath(), linkpath.abspath())
- ctx(
- features="subst",
- source="pylib/test_util.py.in",
- target="pylib/test_util.py"
- )
- # Force early creation of pylib/test_util.py
- ctx.add_group()
-
pytests = ["pylib/test_util.py",
"pylib/test_agentx_packet.py",
"pylib/test_packet.py",
=====================================
wafhelpers/autorevision.sh
=====================================
--- /dev/null
+++ b/wafhelpers/autorevision.sh
@@ -0,0 +1,1341 @@
+#!/bin/sh
+
+# Copyright (c) 2012 - 2016 dak180 and contributors. See
+# https://opensource.org/licenses/mit-license.php or the included
+# COPYING.md for licence terms.
+#
+# autorevision - extracts metadata about the head version from your
+# repository.
+
+# shellcheck disable=SC2154
+
+# Usage message.
+arUsage() {
+ tee >&2 << EOF
+usage: autorevision {-t output-type | -s symbol} [-o cache-file [-f] ] [-e name] [-U] [-V]
+ Options include:
+ -t output-type = specify output type
+ -s symbol = specify symbol output
+ -o cache-file = specify cache file location
+ -f = force the use of cache data
+ -e name = set a different output name for VCS_EXTRA
+ -U = check for untracked files in svn
+ -V = emit version and exit
+ -? = help message
+
+The following are valid output types:
+ c = C/C++ file
+ clojure = clojure file
+ cmake = CMake script file
+ csharp = CSharp properties file
+ h = Header for use with c/c++
+ hpp = Alternate C++ header strings with namespace
+ ini = INI file
+ java = Java file
+ javaprop = Java properties file
+ js = javascript file
+ json = JSON file
+ lua = Lua file
+ m4 = m4 file
+ matlab = matlab file
+ octave = octave file
+ php = PHP file
+ pl = Perl file
+ py = Python file
+ rpm = rpm file
+ scheme = scheme file
+ sh = Bash sytax
+ swift = Swift file
+ tex = (La)TeX file
+ xcode = Header useful for populating info.plist files
+
+
+The following are valid symbols:
+ VCS_TYPE
+ VCS_BASENAME
+ VCS_UUID
+ VCS_NUM
+ VCS_DATE
+ VCS_BRANCH
+ VCS_TAG
+ VCS_TICK
+ VCS_EXTRA
+ VCS_FULL_HASH
+ VCS_SHORT_HASH
+ VCS_WC_MODIFIED
+ VCS_ACTION_STAMP
+EOF
+ exit 1
+}
+
+# Config
+ARVERSION="1.19"
+while getopts ":t:o:s:e:VfU" OPTION; do
+ case "${OPTION}" in
+ t)
+ AFILETYPE="${OPTARG}"
+ ;;
+ o)
+ CACHEFILE="${OPTARG}"
+ ;;
+ f)
+ CACHEFORCE="1"
+ ;;
+ s)
+ VAROUT="${OPTARG}"
+ ;;
+ e)
+ EXTRA_NAME="${OPTARG}"
+ ;;
+ U)
+ UNTRACKEDFILES="1"
+ ;;
+ V)
+ echo "autorevision ${ARVERSION}"
+ exit 0
+ ;;
+ ?)
+ # If an unknown flag is used (or -?):
+ arUsage
+ ;;
+ esac
+done
+
+if [ ! -z "${VAROUT}" ] && [ ! -z "${AFILETYPE}" ]; then
+ # If both -s and -t are specified:
+ echo "error: Improper argument combination." 1>&2
+ exit 1
+elif [ -z "${VAROUT}" ] && [ -z "${AFILETYPE}" ]; then
+ # If neither -s or -t are specified:
+ arUsage
+elif [ -z "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
+ # If -f is specified without -o:
+ arUsage
+elif [ ! -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
+ # If we are forced to use the cache but it does not exist.
+ echo "error: Cache forced but no cache found." 1>&2
+ exit 1
+fi
+
+# Only use the local keyword if it is there (ksh we are looking at
+# you).
+
+if [ "$(command -v local 2> /dev/null)" = "local" ]; then
+ LOCAL="local"
+elif command -v typeset > /dev/null 2>&1; then
+ LOCAL="typeset"
+else
+ LOCAL=""
+fi
+
+# Make sure that the path we are given is one we can source
+# (dash, we are looking at you).
+if [ ! -z "${CACHEFILE}" ] && ! echo "${CACHEFILE}" | grep -q '^\.*/'; then
+ CACHEFILE="./${CACHEFILE}"
+fi
+
+GENERATED_HEADER="Generated by autorevision - do not hand-hack!"
+: "${EXTRA_NAME:="VCS_EXTRA"}"
+
+# Functions to extract data from different repo types.
+# For git repos
+# shellcheck disable=SC2039,SC2164,SC2155
+gitRepo() {
+ ${LOCAL} oldPath="${PWD}"
+
+ cd "$(git rev-parse --show-toplevel)"
+
+ VCS_TYPE="git"
+
+ VCS_BASENAME="$(basename "${PWD}")"
+
+ ${LOCAL} currentRev="$(git rev-parse HEAD)"
+
+ VCS_UUID="$(git rev-list --max-parents=0 --date-order --reverse "${currentRev}" 2>/dev/null | sed -n 1p)"
+ if [ -z "${VCS_UUID}" ]; then
+ VCS_UUID="$(git rev-list --topo-order "${currentRev}" | tail -n 1)"
+ fi
+
+ # Is the working copy clean?
+ test -z "$(git status --untracked-files=normal --porcelain)"
+ VCS_WC_MODIFIED="${?}"
+
+ # Enumeration of changesets
+ VCS_NUM="$(git rev-list --count "${currentRev}" 2>/dev/null)"
+ if [ -z "${VCS_NUM}" ]; then
+ echo "warning: Counting the number of revisions may be slower due to an outdated git version less than 1.7.2.3. If something breaks, please update it." 1>&2
+ VCS_NUM="$(git rev-list HEAD | wc -l)"
+ fi
+
+ # This may be a git-svn remote. If so, report the Subversion revision.
+ if [ -z "$(git config svn-remote.svn.url 2>/dev/null)" ]; then
+ # The full revision hash
+ VCS_FULL_HASH="$(git rev-parse "${currentRev}")"
+
+ # The short hash
+ VCS_SHORT_HASH="$(git rev-parse --short "${currentRev}")"
+ else
+ # The git-svn revision number
+ VCS_FULL_HASH="$(git svn find-rev "${currentRev}")"
+ VCS_SHORT_HASH="${VCS_FULL_HASH}"
+ fi
+
+ # Current branch
+ VCS_BRANCH="$(git rev-parse --symbolic-full-name --verify "$(git name-rev --name-only --no-undefined "${currentRev}" 2>/dev/null)" 2>/dev/null | sed -e 's:refs/heads/::' | sed -e 's:refs/::')"
+
+ # Cache the description
+ ${LOCAL} DESCRIPTION="$(git describe --long --tags "${currentRev}" 2>/dev/null)"
+
+ # Current or last tag ancestor (empty if no tags)
+ VCS_TAG="$(echo "${DESCRIPTION}" | sed -e "s:-g${VCS_SHORT_HASH}\$::" -e 's:-[0-9]*$::')"
+
+ # Distance to last tag or an alias of VCS_NUM if there is no tag
+ if [ ! -z "${DESCRIPTION}" ]; then
+ VCS_TICK="$(echo "${DESCRIPTION}" | sed -e "s:${VCS_TAG}-::" -e "s:-g${VCS_SHORT_HASH}::")"
+ else
+ VCS_TICK="${VCS_NUM}"
+ fi
+
+ # Date of the current commit
+ VCS_DATE="$(TZ=UTC git show -s --date=iso-strict-local --pretty=format:%cd "${currentRev}" 2>/dev/null | sed -e 's|+00:00|Z|')"
+ if [ -z "${VCS_DATE}" ]; then
+ echo "warning: Action stamps require git version 2.7+." 1>&2
+ VCS_DATE="$(git log -1 --pretty=format:%ci "${currentRev}" | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')"
+ ${LOCAL} ASdis="1"
+ fi
+
+ # Action Stamp
+ if [ -z "${ASdis}" ]; then
+ VCS_ACTION_STAMP="${VCS_DATE}!$(git show -s --pretty=format:%cE "${currentRev}")"
+ else
+ VCS_ACTION_STAMP=""
+ fi
+
+ cd "${oldPath}"
+}
+
+# For hg repos
+# shellcheck disable=SC2039,SC2164
+hgRepo() {
+ ${LOCAL} oldPath="${PWD}"
+
+ cd "$(hg root)"
+
+ VCS_TYPE="hg"
+
+ VCS_BASENAME="$(basename "${PWD}")"
+
+ VCS_UUID="$(hg log -r "0" -l 1 --template '{node}\n')"
+
+ # Is the working copy clean?
+ test -z "$(hg status -duram)"
+ VCS_WC_MODIFIED="${?}"
+
+ # Enumeration of changesets
+ VCS_NUM="$(hg id -n | tr -d '+')"
+
+ # The full revision hash
+ VCS_FULL_HASH="$(hg log -r "${VCS_NUM}" -l 1 --template '{node}\n')"
+
+ # The short hash
+ VCS_SHORT_HASH="$(hg id -i | tr -d '+')"
+
+ # Current bookmark (bookmarks are roughly equivalent to git's branches)
+ # or branch if no bookmark
+ VCS_BRANCH="$(hg id -B | cut -d ' ' -f 1)"
+ # Fall back to the branch if there are no bookmarks
+ if [ -z "${VCS_BRANCH}" ]; then
+ VCS_BRANCH="$(hg id -b)"
+ fi
+
+ # Current or last tag ancestor (excluding auto tags, empty if no tags)
+ VCS_TAG="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttag}\n' 2>/dev/null | sed -e 's:qtip::' -e 's:tip::' -e 's:qbase::' -e 's:qparent::' -e "s:$(hg --config 'extensions.color=' --config 'extensions.mq=' --color never qtop 2>/dev/null)::" | cut -d ' ' -f 1)"
+
+ # Distance to last tag or an alias of VCS_NUM if there is no tag
+ if [ ! -z "${VCS_TAG}" ]; then
+ VCS_TICK="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttagdistance}\n' 2>/dev/null)"
+ else
+ VCS_TICK="${VCS_NUM}"
+ fi
+
+ # Date of the current commit
+ VCS_DATE="$(hg log -r "${VCS_NUM}" -l 1 --template '{date|isodatesec}\n' 2>/dev/null | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')"
+
+ # Action Stamp
+ VCS_ACTION_STAMP="$(TZ=UTC hg log -r "${VCS_NUM}" -l 1 --template '{date|localdate|rfc3339date}\n' 2>/dev/null | sed -e 's|+00:00|Z|')!$(hg log -r "${VCS_NUM}" -l 1 --template '{author|email}\n' 2>/dev/null)"
+
+ cd "${oldPath}"
+}
+
+# For bzr repos
+# shellcheck disable=SC2039,SC2164
+bzrRepo() {
+ ${LOCAL} oldPath="${PWD}"
+
+ cd "$(bzr root)"
+
+ VCS_TYPE="bzr"
+
+ VCS_BASENAME="$(basename "${PWD}")"
+
+ # Currently unimplemented because more investigation is needed.
+ VCS_UUID=""
+
+ # Is the working copy clean?
+ bzr version-info --custom --template='{clean}\n' | grep -q '1'
+ VCS_WC_MODIFIED="${?}"
+
+ # Enumeration of changesets
+ VCS_NUM="$(bzr revno)"
+
+ # The full revision hash
+ VCS_FULL_HASH="$(bzr version-info --custom --template='{revision_id}\n')"
+
+ # The short hash
+ VCS_SHORT_HASH="${VCS_NUM}"
+
+ # Nick of the current branch
+ VCS_BRANCH="$(bzr nick)"
+
+ # Current or last tag ancestor (excluding auto tags, empty if no tags)
+ VCS_TAG="$(bzr tags --sort=time | sed '/?$/d' | tail -n1 | cut -d ' ' -f1)"
+
+ # Distance to last tag or an alias of VCS_NUM if there is no tag
+ if [ ! -z "${VCS_TAG}" ]; then
+ VCS_TICK="$(bzr log --line -r "tag:${VCS_TAG}.." | tail -n +2 | wc -l | sed -e 's:^ *::')"
+ else
+ VCS_TICK="${VCS_NUM}"
+ fi
+
+ # Date of the current commit
+ VCS_DATE="$(bzr version-info --custom --template='{date}\n' | sed -e 's: :T:' -e 's: ::')"
+
+ # Action Stamp
+ # Currently unimplemented because more investigation is needed.
+ VCS_ACTION_STAMP=""
+
+ cd "${oldPath}"
+}
+
+# For svn repos
+# shellcheck disable=SC2039,SC2164,SC2155
+svnRepo() {
+ ${LOCAL} oldPath="${PWD}"
+
+ VCS_TYPE="svn"
+
+ case "${PWD}" in
+ /*trunk*|/*branches*|/*tags*)
+ ${LOCAL} fn="${PWD}"
+ while [ "$(basename "${fn}")" != 'trunk' ] && [ "$(basename "${fn}")" != 'branches' ] && [ "$(basename "${fn}")" != 'tags' ] && [ "$(basename "${fn}")" != '/' ]; do
+ ${LOCAL} fn="$(dirname "${fn}")"
+ done
+ ${LOCAL} fn="$(dirname "${fn}")"
+ if [ "${fn}" = '/' ]; then
+ VCS_BASENAME="$(basename "${PWD}")"
+ else
+ VCS_BASENAME="$(basename "${fn}")"
+ fi
+ ;;
+ *) VCS_BASENAME="$(basename "${PWD}")" ;;
+ esac
+
+ VCS_UUID="$(svn info --xml | sed -n -e 's:<uuid>::' -e 's:</uuid>::p')"
+
+ # Cache svnversion output
+ ${LOCAL} SVNVERSION="$(svnversion)"
+
+ # Is the working copy clean?
+ echo "${SVNVERSION}" | grep -q "M"
+ case "${?}" in
+ 0)
+ VCS_WC_MODIFIED="1"
+ ;;
+ 1)
+ if [ ! -z "${UNTRACKEDFILES}" ]; then
+ # `svnversion` does not detect untracked files and `svn status` is really slow, so only run it if we really have to.
+ if [ -z "$(svn status)" ]; then
+ VCS_WC_MODIFIED="0"
+ else
+ VCS_WC_MODIFIED="1"
+ fi
+ else
+ VCS_WC_MODIFIED="0"
+ fi
+ ;;
+ esac
+
+ # Enumeration of changesets
+ VCS_NUM="$(echo "${SVNVERSION}" | cut -d : -f 1 | sed -e 's:M::' -e 's:S::' -e 's:P::')"
+
+ # The full revision hash
+ VCS_FULL_HASH="${SVNVERSION}"
+
+ # The short hash
+ VCS_SHORT_HASH="${VCS_NUM}"
+
+ # Current branch
+ case "${PWD}" in
+ /*trunk*|/*branches*|/*tags*)
+ ${LOCAL} lastbase=""
+ ${LOCAL} fn="${PWD}"
+ while :
+ do
+ base="$(basename "${fn}")"
+ if [ "${base}" = 'trunk' ]; then
+ VCS_BRANCH='trunk'
+ break
+ elif [ "${base}" = 'branches' ] || [ "${base}" = 'tags' ]; then
+ VCS_BRANCH="${lastbase}"
+ break
+ elif [ "${base}" = '/' ]; then
+ VCS_BRANCH=""
+ break
+ fi
+ ${LOCAL} lastbase="${base}"
+ ${LOCAL} fn="$(dirname "${fn}")"
+ done
+ ;;
+ *) VCS_BRANCH="" ;;
+ esac
+
+ # Current or last tag ancestor (empty if no tags). But "current
+ # tag" can't be extracted reliably because Subversion doesn't
+ # have tags the way other VCSes do.
+ VCS_TAG=""
+ VCS_TICK=""
+
+ # Date of the current commit
+ VCS_DATE="$(svn info --xml | sed -n -e 's:<date>::' -e 's:</date>::p')"
+
+ # Action Stamp
+ VCS_ACTION_STAMP="${VCS_DATE}!$(svn log --xml -l 1 -r "${VCS_SHORT_HASH}" | sed -n -e 's:<author>::' -e 's:</author>::p')"
+
+ cd "${oldPath}"
+}
+
+
+# Functions to output data in different formats.
+# For bash output
+# First in list because it is used by autorevision
+shOutput() {
+ tee << EOF
+# ${GENERATED_HEADER}
+
+VCS_TYPE="${VCS_TYPE}"
+VCS_BASENAME="${VCS_BASENAME}"
+VCS_UUID="${VCS_UUID}"
+VCS_NUM="${VCS_NUM}"
+VCS_DATE="${VCS_DATE}"
+VCS_BRANCH="${VCS_BRANCH}"
+VCS_TAG="${VCS_TAG}"
+VCS_TICK="${VCS_TICK}"
+${EXTRA_NAME}="${VCS_EXTRA}"
+
+VCS_ACTION_STAMP="${VCS_ACTION_STAMP}"
+VCS_FULL_HASH="${VCS_FULL_HASH}"
+VCS_SHORT_HASH="${VCS_SHORT_HASH}"
+
+VCS_WC_MODIFIED="${VCS_WC_MODIFIED}"
+
+# end
+EOF
+}
+
+# For source C output
+cOutput() {
+ tee << EOF
+/* ${GENERATED_HEADER} */
+
+const char *VCS_TYPE = "${VCS_TYPE}";
+const char *VCS_BASENAME = "${VCS_BASENAME}";
+const char *VCS_UUID = "${VCS_UUID}";
+const int VCS_NUM = ${VCS_NUM};
+const char *VCS_DATE = "${VCS_DATE}";
+const char *VCS_BRANCH = "${VCS_BRANCH}";
+const char *VCS_TAG = "${VCS_TAG}";
+const int VCS_TICK = ${VCS_TICK};
+const char *${EXTRA_NAME} = "${VCS_EXTRA}";
+
+const char *VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}";
+const char *VCS_FULL_HASH = "${VCS_FULL_HASH}";
+const char *VCS_SHORT_HASH = "${VCS_SHORT_HASH}";
+
+const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+/* end */
+EOF
+}
+
+# For Cmake output
+cmakeOutput() {
+ tee << EOF
+# ${GENERATED_HEADER}
+
+set(VCS_TYPE ${VCS_TYPE})
+set(VCS_BASENAME ${VCS_BASENAME})
+set(VCS_UUID ${VCS_UUID})
+set(VCS_NUM ${VCS_NUM})
+set(VCS_DATE ${VCS_DATE})
+set(VCS_BRANCH ${VCS_BRANCH})
+set(VCS_TAG ${VCS_TAG})
+set(VCS_TICK ${VCS_TICK})
+set(${EXTRA_NAME} ${VCS_EXTRA})
+
+set(VCS_ACTION_STAMP ${VCS_ACTION_STAMP})
+set(VCS_FULL_HASH ${VCS_FULL_HASH})
+set(VCS_SHORT_HASH ${VCS_SHORT_HASH})
+
+set(VCS_WC_MODIFIED ${VCS_WC_MODIFIED})
+
+# end
+EOF
+}
+
+# For header output
+hOutput() {
+ tee << EOF
+/* ${GENERATED_HEADER} */
+#ifndef AUTOREVISION_H
+#define AUTOREVISION_H
+
+#define VCS_TYPE "${VCS_TYPE}"
+#define VCS_BASENAME "${VCS_BASENAME}"
+#define VCS_UUID "${VCS_UUID}"
+#define VCS_NUM ${VCS_NUM}
+#define VCS_DATE "${VCS_DATE}"
+#define VCS_BRANCH "${VCS_BRANCH}"
+#define VCS_TAG "${VCS_TAG}"
+#define VCS_TICK ${VCS_TICK}
+#define ${EXTRA_NAME} "${VCS_EXTRA}"
+
+#define VCS_ACTION_STAMP "${VCS_ACTION_STAMP}"
+#define VCS_FULL_HASH "${VCS_FULL_HASH}"
+#define VCS_SHORT_HASH "${VCS_SHORT_HASH}"
+
+#define VCS_WC_MODIFIED ${VCS_WC_MODIFIED}
+
+#endif
+
+/* end */
+EOF
+}
+
+# A header output for use with xcode to populate info.plist strings
+xcodeOutput() {
+ tee << EOF
+/* ${GENERATED_HEADER} */
+#ifndef AUTOREVISION_H
+#define AUTOREVISION_H
+
+#define VCS_TYPE ${VCS_TYPE}
+#define VCS_BASENAME ${VCS_BASENAME}
+#define VCS_UUID ${VCS_UUID}
+#define VCS_NUM ${VCS_NUM}
+#define VCS_DATE ${VCS_DATE}
+#define VCS_BRANCH ${VCS_BRANCH}
+#define VCS_TAG ${VCS_TAG}
+#define VCS_TICK ${VCS_TICK}
+#define ${EXTRA_NAME} ${VCS_EXTRA}
+
+#define VCS_ACTION_STAMP ${VCS_ACTION_STAMP}
+#define VCS_FULL_HASH ${VCS_FULL_HASH}
+#define VCS_SHORT_HASH ${VCS_SHORT_HASH}
+
+#define VCS_WC_MODIFIED ${VCS_WC_MODIFIED}
+
+#endif
+
+/* end */
+EOF
+}
+
+# For Swift output
+swiftOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ # For values that may not exist depending on the type of repo we
+ # have read from, set them to `nil` when they are empty.
+ if [ -z "${VCS_UUID}" ]; then
+ VCS_UUID="nil"
+ else
+ VCS_UUID="\"${VCS_UUID}\""
+ fi
+ if [ -z "${VCS_TAG}" ]; then
+ VCS_TAG="nil"
+ else
+ VCS_TAG="\"${VCS_TAG}\""
+ fi
+ : "${VCS_TICK:="nil"}"
+ if [ -z "${VCS_EXTRA}" ]; then
+ VCS_EXTRA="nil"
+ else
+ VCS_EXTRA="\"${VCS_EXTRA}\""
+ fi
+ if [ -z "${VCS_ACTION_STAMP}" ]; then
+ VCS_ACTION_STAMP="nil"
+ else
+ VCS_ACTION_STAMP="\"${VCS_ACTION_STAMP}\""
+ fi
+ tee << EOF
+/* ${GENERATED_HEADER} */
+
+let VCS_TYPE = "${VCS_TYPE}"
+let VCS_BASENAME = "${VCS_BASENAME}"
+let VCS_UUID: String? = ${VCS_UUID}
+let VCS_NUM: Int = ${VCS_NUM}
+let VCS_DATE = "${VCS_DATE}"
+let VCS_BRANCH: String = "${VCS_BRANCH}"
+let VCS_TAG: String? = ${VCS_TAG}
+let VCS_TICK: Int? = ${VCS_TICK}
+let ${EXTRA_NAME}: String? = ${VCS_EXTRA}
+
+let VCS_ACTION_STAMP: String? = ${VCS_ACTION_STAMP}
+let VCS_FULL_HASH: String = "${VCS_FULL_HASH}"
+let VCS_SHORT_HASH: String = "${VCS_SHORT_HASH}"
+
+let VCS_WC_MODIFIED: Bool = ${VCS_WC_MODIFIED}
+
+/* end */
+EOF
+}
+
+# For Python output
+pyOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) PY_VCS_WC_MODIFIED="False" ;;
+ 1) PY_VCS_WC_MODIFIED="True" ;;
+ esac
+ tee << EOF
+# -*- coding: utf-8 -*-
+# ${GENERATED_HEADER}
+
+VCS_TYPE = "${VCS_TYPE}"
+VCS_BASENAME = "${VCS_BASENAME}"
+VCS_UUID = "${VCS_UUID}"
+VCS_NUM = ${VCS_NUM}
+VCS_DATE = "${VCS_DATE}"
+VCS_BRANCH = "${VCS_BRANCH}"
+VCS_TAG = "${VCS_TAG}"
+VCS_TICK = ${VCS_TICK}
+${EXTRA_NAME} = "${VCS_EXTRA}"
+
+VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"
+VCS_FULL_HASH = "${VCS_FULL_HASH}"
+VCS_SHORT_HASH = "${VCS_SHORT_HASH}"
+
+VCS_WC_MODIFIED = ${PY_VCS_WC_MODIFIED}
+
+# end
+EOF
+}
+
+# For Perl output
+plOutput() {
+ tee << EOF
+# ${GENERATED_HEADER}
+
+\$VCS_TYPE = '${VCS_TYPE}';
+\$VCS_BASENAME = '${VCS_BASENAME}';
+\$VCS_UUID = '${VCS_UUID}';
+\$VCS_NUM = ${VCS_NUM};
+\$VCS_DATE = '${VCS_DATE}';
+\$VCS_BRANCH = '${VCS_BRANCH}';
+\$VCS_TAG = '${VCS_TAG}';
+\$VCS_TICK = ${VCS_TICK};
+\$${EXTRA_NAME} = '${VCS_EXTRA}';
+
+\$VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}';
+\$VCS_FULL_HASH = '${VCS_FULL_HASH}';
+\$VCS_SHORT_HASH = '${VCS_SHORT_HASH}';
+
+\$VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+# end
+1;
+EOF
+}
+
+# For lua output
+luaOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ tee << EOF
+-- ${GENERATED_HEADER}
+
+VCS_TYPE = "${VCS_TYPE}"
+VCS_BASENAME = "${VCS_BASENAME}"
+VCS_UUID = "${VCS_UUID}"
+VCS_NUM = ${VCS_NUM}
+VCS_DATE = "${VCS_DATE}"
+VCS_BRANCH = "${VCS_BRANCH}"
+VCS_TAG = "${VCS_TAG}"
+VCS_TICK = ${VCS_TICK}
+${EXTRA_NAME} = "${VCS_EXTRA}"
+
+VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"
+VCS_FULL_HASH = "${VCS_FULL_HASH}"
+VCS_SHORT_HASH = "${VCS_SHORT_HASH}"
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}
+
+-- end
+EOF
+}
+
+# For php output
+phpOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ tee << EOF
+<?php
+# ${GENERATED_HEADER}
+
+return array(
+ "VCS_TYPE" => "${VCS_TYPE}",
+ "VCS_BASENAME" => "${VCS_BASENAME}",
+ "VCS_UUID" => "${VCS_UUID}",
+ "VCS_NUM" => ${VCS_NUM},
+ "VCS_DATE" => "${VCS_DATE}",
+ "VCS_BRANCH" => "${VCS_BRANCH}",
+ "VCS_TAG" => "${VCS_TAG}",
+ "VCS_TICK" => ${VCS_TICK},
+ "${EXTRA_NAME}" => "${VCS_EXTRA}",
+ "VCS_ACTION_STAMP" => "${VCS_ACTION_STAMP}",
+ "VCS_FULL_HASH" => "${VCS_FULL_HASH}",
+ "VCS_SHORT_HASH" => "${VCS_SHORT_HASH}",
+ "VCS_WC_MODIFIED" => ${VCS_WC_MODIFIED}
+);
+
+# end
+?>
+EOF
+}
+
+# For ini output
+iniOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ tee << EOF
+; ${GENERATED_HEADER}
+[VCS]
+VCS_TYPE = "${VCS_TYPE}"
+VCS_BASENAME = "${VCS_BASENAME}"
+VCS_UUID = "${VCS_UUID}"
+VCS_NUM = ${VCS_NUM}
+VCS_DATE = "${VCS_DATE}"
+VCS_BRANCH = "${VCS_BRANCH}"
+VCS_TAG = "${VCS_TAG}"
+VCS_TICK = ${VCS_TICK}
+${EXTRA_NAME} = "${VCS_EXTRA}"
+VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"
+VCS_FULL_HASH = "${VCS_FULL_HASH}"
+VCS_SHORT_HASH = "${VCS_SHORT_HASH}"
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}
+; end
+EOF
+}
+
+# For javascript output
+jsOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ tee << EOF
+/** ${GENERATED_HEADER} */
+
+var autorevision = {
+ VCS_TYPE: "${VCS_TYPE}",
+ VCS_BASENAME: "${VCS_BASENAME}",
+ VCS_UUID: "${VCS_UUID}",
+ VCS_NUM: ${VCS_NUM},
+ VCS_DATE: "${VCS_DATE}",
+ VCS_BRANCH: "${VCS_BRANCH}",
+ VCS_TAG: "${VCS_TAG}",
+ VCS_TICK: ${VCS_TICK},
+ ${EXTRA_NAME}: "${VCS_EXTRA}",
+
+ VCS_ACTION_STAMP: "${VCS_ACTION_STAMP}",
+ VCS_FULL_HASH: "${VCS_FULL_HASH}",
+ VCS_SHORT_HASH: "${VCS_SHORT_HASH}",
+
+ VCS_WC_MODIFIED: ${VCS_WC_MODIFIED}
+};
+
+/** Node.js compatibility */
+if (typeof module !== 'undefined') {
+ module.exports = autorevision;
+}
+
+/** end */
+EOF
+}
+
+# For JSON output
+jsonOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ tee << EOF
+{
+ "_comment": "${GENERATED_HEADER}",
+ "VCS_TYPE": "${VCS_TYPE}",
+ "VCS_BASENAME": "${VCS_BASENAME}",
+ "VCS_UUID": "${VCS_UUID}",
+ "VCS_NUM": ${VCS_NUM},
+ "VCS_DATE": "${VCS_DATE}",
+ "VCS_BRANCH":"${VCS_BRANCH}",
+ "VCS_TAG": "${VCS_TAG}",
+ "VCS_TICK": ${VCS_TICK},
+ "${EXTRA_NAME}": "${VCS_EXTRA}",
+
+ "VCS_ACTION_STAMP": "${VCS_ACTION_STAMP}",
+ "VCS_FULL_HASH": "${VCS_FULL_HASH}",
+ "VCS_SHORT_HASH": "${VCS_SHORT_HASH}",
+
+ "VCS_WC_MODIFIED": ${VCS_WC_MODIFIED}
+}
+EOF
+}
+
+# For Java output
+javaOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ tee << EOF
+/* ${GENERATED_HEADER} */
+
+public class autorevision {
+ public static final String VCS_TYPE = "${VCS_TYPE}";
+ public static final String VCS_BASENAME = "${VCS_BASENAME}";
+ public static final String VCS_UUID = "${VCS_UUID}";
+ public static final long VCS_NUM = ${VCS_NUM};
+ public static final String VCS_DATE = "${VCS_DATE}";
+ public static final String VCS_BRANCH = "${VCS_BRANCH}";
+ public static final String VCS_TAG = "${VCS_TAG}";
+ public static final long VCS_TICK = ${VCS_TICK};
+ public static final String ${EXTRA_NAME} = "${VCS_EXTRA}";
+
+ public static final String VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}";
+ public static final String VCS_FULL_HASH = "${VCS_FULL_HASH}";
+ public static final String VCS_SHORT_HASH = "${VCS_SHORT_HASH}";
+
+ public static final boolean VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+}
+EOF
+}
+
+csharpOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ if [ "${EXTRA_NAME}" = "VCS_EXTRA" ]; then
+ EXTRA_NAME="VcsExtra"
+ fi
+ tee << EOF
+/* ${GENERATED_HEADER} */
+
+namespace AutoRevision
+{
+ public class VersionInfo
+ {
+ public static string VcsType = "${VCS_TYPE}";
+ public static string VcsBasename = "${VCS_BASENAME}";
+ public static string VcsUuid = "${VCS_UUID}";
+ public static string VcsNum = "${VCS_NUM}";
+ public static string VcsDate = "${VCS_DATE}";
+ public static string VcsBranch = "${VCS_DATE}";
+ public static string VcsTag = "${VCS_TAG}";
+ public static string VcsTick = "${VCS_TICK}";
+ public static string ${EXTRA_NAME} = "${VCS_EXTRA}";
+ public static string VcsActionStamp = "${VCS_ACTION_STAMP}";
+ public static string VcsFullHash = "${VCS_FULL_HASH}";
+ public static string VcsShortHash = "${VCS_SHORT_HASH}";
+ public static string VcsWcModified = "${VCS_WC_MODIFIED}";
+ }
+}
+EOF
+}
+
+# For Java properties output
+javapropOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ tee << EOF
+# ${GENERATED_HEADER}
+
+VCS_TYPE=${VCS_TYPE}
+VCS_BASENAME=${VCS_BASENAME}
+VCS_UUID=${VCS_UUID}
+VCS_NUM=${VCS_NUM}
+VCS_DATE=${VCS_DATE}
+VCS_BRANCH=${VCS_BRANCH}
+VCS_TAG=${VCS_TAG}
+VCS_TICK=${VCS_TICK}
+${EXTRA_NAME}=${VCS_EXTRA}
+
+VCS_ACTION_STAMP=${VCS_ACTION_STAMP}
+VCS_FULL_HASH=${VCS_FULL_HASH}
+VCS_SHORT_HASH=${VCS_SHORT_HASH}
+
+VCS_WC_MODIFIED=${VCS_WC_MODIFIED}
+EOF
+}
+
+# For m4 output
+m4Output() {
+ tee << EOF
+dnl ${GENERATED_HEADER}
+define(\`VCS_TYPE', \`${VCS_TYPE}')dnl
+define(\`VCS_BASENAME', \`${VCS_BASENAME}')dnl
+define(\`VCS_UUID', \`${VCS_UUID}')dnl
+define(\`VCS_NUM', \`${VCS_NUM}')dnl
+define(\`VCS_DATE', \`${VCS_DATE}')dnl
+define(\`VCS_BRANCH', \`${VCS_BRANCH}')dnl
+define(\`VCS_TAG', \`${VCS_TAG}')dnl
+define(\`VCS_TICK', \`${VCS_TICK}')dnl
+define(\`${EXTRA_NAME}', \`${VCS_EXTRA}')dnl
+define(\`VCS_ACTIONSTAMP', \`${VCS_ACTION_STAMP}')dnl
+define(\`VCS_FULLHASH', \`${VCS_FULL_HASH}')dnl
+define(\`VCS_SHORTHASH', \`${VCS_SHORT_HASH}')dnl
+define(\`VCS_WC_MODIFIED', \`${VCS_WC_MODIFIED}')dnl
+EOF
+}
+
+# For (La)TeX output
+texOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ if [ "${EXTRA_NAME}" = "VCS_EXTRA" ]; then
+ EXTRA_NAME="vcsExtra"
+ fi
+ tee << EOF
+% ${GENERATED_HEADER}
+\def \vcsType {${VCS_TYPE}}
+\def \vcsBasename {${VCS_BASENAME}}
+\def \vcsUUID {${VCS_UUID}}
+\def \vcsNum {${VCS_NUM}}
+\def \vcsDate {${VCS_DATE}}
+\def \vcsBranch {${VCS_BRANCH}}
+\def \vcsTag {${VCS_TAG}}
+\def \vcsTick {${VCS_TICK}}
+\def \\${EXTRA_NAME} {${VCS_EXTRA}}
+\def \vcsACTIONSTAMP {${VCS_ACTION_STAMP}}
+\def \vcsFullHash {${VCS_FULL_HASH}}
+\def \vcsShortHash {${VCS_SHORT_HASH}}
+\def \vcsWCModified {${VCS_WC_MODIFIED}}
+\endinput
+EOF
+}
+
+# For scheme output
+schemeOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="#f" ;;
+ 1) VCS_WC_MODIFIED="#t" ;;
+ esac
+ tee << EOF
+;; ${GENERATED_HEADER}
+(define VCS_TYPE "${VCS_TYPE}")
+(define VCS_BASENAME "${VCS_BASENAME}")
+(define VCS_UUID "${VCS_UUID}")
+(define VCS_NUM ${VCS_NUM})
+(define VCS_DATE "${VCS_DATE}")
+(define VCS_BRANCH "${VCS_BRANCH}")
+(define VCS_TAG "${VCS_TAG}")
+(define VCS_TICK ${VCS_TICK})
+(define ${EXTRA_NAME} "${VCS_EXTRA}")
+
+(define VCS_ACTION_STAMP "${VCS_ACTION_STAMP}")
+(define VCS_FULL_HASH "${VCS_FULL_HASH}")
+(define VCS_SHORT_HASH "${VCS_SHORT_HASH}")
+
+(define VCS_WC_MODIFIED ${VCS_WC_MODIFIED})
+;; end
+EOF
+}
+
+# For clojure output
+clojureOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ tee << EOF
+;; ${GENERATED_HEADER}
+(def VCS_TYPE "${VCS_TYPE}")
+(def VCS_BASENAME "${VCS_BASENAME}")
+(def VCS_UUID "${VCS_UUID}")
+(def VCS_NUM ${VCS_NUM})
+(def VCS_DATE "${VCS_DATE}")
+(def VCS_BRANCH "${VCS_BRANCH}")
+(def VCS_TAG "${VCS_TAG}")
+(def VCS_TICK ${VCS_TICK})
+(def ${EXTRA_NAME} "${VCS_EXTRA}")
+
+(def VCS_ACTION_STAMP "${VCS_ACTION_STAMP}")
+(def VCS_FULL_HASH "${VCS_FULL_HASH}")
+(def VCS_SHORT_HASH "${VCS_SHORT_HASH}")
+
+(def VCS_WC_MODIFIED ${VCS_WC_MODIFIED})
+;; end
+EOF
+}
+
+# For rpm spec file output
+rpmOutput() {
+ tee << EOF
+# ${GENERATED_HEADER}
+$([ "${VCS_TYPE}" ] && echo "%define vcs_type ${VCS_TYPE}")
+$([ "${VCS_BASENAME}" ] && echo "%define vcs_basename ${VCS_BASENAME}")
+$([ "${VCS_UUID}" ] && echo "%define vcs_uuid ${VCS_UUID}")
+$([ "${VCS_NUM}" ] && echo "%define vcs_num ${VCS_NUM}")
+$([ "${VCS_DATE}" ] && echo "%define vcs_date ${VCS_DATE}")
+$([ "${VCS_BRANCH}" ] && echo "%define vcs_branch ${VCS_BRANCH}")
+$([ "${VCS_TAG}" ] && echo "%define vcs_tag ${VCS_TAG}")
+$([ "${VCS_TICK}" ] && echo "%define vcs_tick ${VCS_TICK}")
+$([ "${VCS_EXTRA}" ] && echo "%define ${EXTRA_NAME} ${VCS_EXTRA}")
+
+$([ "${VCS_ACTION_STAMP}" ] && echo "%define vcs_action_stamp ${VCS_ACTION_STAMP}")
+$([ "${VCS_FULL_HASH}" ] && echo "%define vcs_full_hash ${VCS_FULL_HASH}")
+$([ "${VCS_SHORT_HASH}" ] && echo "%define vcs_short_hash ${VCS_SHORT_HASH}")
+
+$([ "${VCS_WC_MODIFIED}" ] && echo "%define vcs_wc_modified ${VCS_WC_MODIFIED}")
+# end
+EOF
+}
+
+# For C++ Header output
+# shellcheck disable=SC2155,SC2039
+hppOutput() {
+ ${LOCAL} NAMESPACE="$(echo "${VCS_BASENAME}" | sed -e 's:_::g' | tr '[:lower:]' '[:upper:]')"
+ tee << EOF
+/* ${GENERATED_HEADER} */
+
+#ifndef ${NAMESPACE}_AUTOREVISION_H
+#define ${NAMESPACE}_AUTOREVISION_H
+
+#include <string>
+
+namespace $(echo "${NAMESPACE}" | tr '[:upper:]' '[:lower:]')
+{
+ const std::string VCS_TYPE = "${VCS_TYPE}";
+ const std::string VCS_BASENAME = "${VCS_BASENAME}";
+ const std::string VCS_UUID = "${VCS_UUID}";
+ const int VCS_NUM = ${VCS_NUM};
+ const std::string VCS_DATE = "${VCS_DATE}";
+ const std::string VCS_BRANCH = "${VCS_BRANCH}";
+ const std::string VCS_TAG = "${VCS_TAG}";
+ const int VCS_TICK = ${VCS_TICK};
+ const std::string ${EXTRA_NAME} = "${VCS_EXTRA}";
+
+ const std::string VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}";
+ const std::string VCS_FULL_HASH = "${VCS_FULL_HASH}";
+ const std::string VCS_SHORT_HASH = "${VCS_SHORT_HASH}";
+
+ const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+}
+
+#endif
+
+/* end */
+EOF
+}
+
+# For Matlab output
+matlabOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="FALSE" ;;
+ 1) VCS_WC_MODIFIED="TRUE" ;;
+ esac
+ tee << EOF
+% ${GENERATED_HEADER}
+
+VCS_TYPE = '${VCS_TYPE}';
+VCS_BASENAME = '${VCS_BASENAME}';
+VCS_UUID = '${VCS_UUID}';
+VCS_NUM = ${VCS_NUM};
+VCS_DATE = '${VCS_DATE}';
+VCS_BRANCH = '${VCS_BRANCH}';
+VCS_TAG = '${VCS_TAG}';
+VCS_TICK = ${VCS_TICK};
+${EXTRA_NAME} = '${VCS_EXTRA}';
+
+VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}';
+VCS_FULL_HASH = '${VCS_FULL_HASH}';
+VCS_SHORT_HASH = '${VCS_SHORT_HASH}';
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+% end
+EOF
+}
+
+# For Octave output
+octaveOutput() {
+ tee << EOF
+% ${GENERATED_HEADER}
+
+VCS_TYPE = '${VCS_TYPE}';
+VCS_BASENAME = '${VCS_BASENAME}';
+VCS_UUID = '${VCS_UUID}';
+VCS_NUM = ${VCS_NUM};
+VCS_DATE = '${VCS_DATE}';
+VCS_BRANCH = '${VCS_BRANCH}';
+VCS_TAG = '${VCS_TAG}';
+VCS_TICK = ${VCS_TICK};
+${EXTRA_NAME} = '${VCS_EXTRA}';
+
+VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}';
+VCS_FULL_HASH = '${VCS_FULL_HASH}';
+VCS_SHORT_HASH = '${VCS_SHORT_HASH}';
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+% end
+EOF
+}
+
+
+# Helper functions
+# Count path segments
+# shellcheck disable=SC2039
+pathSegment() {
+ ${LOCAL} pathz="${1}"
+ ${LOCAL} depth="0"
+
+ if [ ! -z "${pathz}" ]; then
+ # Continue until we are at / or there are no path separators left.
+ while [ ! "${pathz}" = "/" ] && [ ! "${pathz}" = "$(echo "${pathz}" | sed -e 's:/::')" ]; do
+ pathz="$(dirname "${pathz}")"
+ depth="$((depth+1))"
+ done
+ fi
+ echo "${depth}"
+}
+
+# Largest of four numbers
+# shellcheck disable=SC2039
+multiCompare() {
+ ${LOCAL} larger="${1}"
+ ${LOCAL} numA="${2}"
+ ${LOCAL} numB="${3}"
+ ${LOCAL} numC="${4}"
+
+ [ "${numA}" -gt "${larger}" ] && larger="${numA}"
+ [ "${numB}" -gt "${larger}" ] && larger="${numB}"
+ [ "${numC}" -gt "${larger}" ] && larger="${numC}"
+ echo "${larger}"
+}
+
+# Test for repositories
+# shellcheck disable=SC2155,SC2039
+repoTest() {
+ REPONUM="0"
+ if command -v git > /dev/null 2>&1; then
+ ${LOCAL} gitPath="$(git rev-parse --show-toplevel 2>/dev/null)"
+ ${LOCAL} gitDepth="$(pathSegment "${gitPath}")"
+ if [ ! -z "${gitPath}" ]; then
+ REPONUM="$((REPONUM+1))"
+ fi
+ else
+ ${LOCAL} gitDepth="0"
+ fi
+ if command -v hg > /dev/null 2>&1; then
+ ${LOCAL} hgPath="$(hg root 2>/dev/null)"
+ ${LOCAL} hgDepth="$(pathSegment "${hgPath}")"
+ if [ ! -z "${hgPath}" ]; then
+ REPONUM="$((REPONUM+1))"
+ fi
+ else
+ ${LOCAL} hgDepth="0"
+ fi
+ if command -v bzr > /dev/null 2>&1; then
+ ${LOCAL} bzrPath="$(bzr root 2>/dev/null)"
+ ${LOCAL} bzrDepth="$(pathSegment "${bzrPath}")"
+ if [ ! -z "${bzrPath}" ]; then
+ REPONUM="$((REPONUM+1))"
+ fi
+ else
+ ${LOCAL} bzrDepth="0"
+ fi
+ if command -v svn > /dev/null 2>&1; then
+ ${LOCAL} stringz="<wcroot-abspath>"
+ ${LOCAL} stringx="</wcroot-abspath>"
+ ${LOCAL} svnPath="$(svn info --xml 2>/dev/null | sed -n -e "s:${stringz}::" -e "s:${stringx}::p")"
+ # An old enough svn will not be able give us a path; default
+ # to 1 for that case.
+ if [ ! -z "${svnPath}" ]; then
+ ${LOCAL} svnDepth="$(pathSegment "${svnPath}")"
+ REPONUM="$((REPONUM+1))"
+ elif [ -z "${svnPath}" ] && [ -d ".svn" ]; then
+ ${LOCAL} svnDepth="1"
+ REPONUM="$((REPONUM+1))"
+ else
+ ${LOCAL} svnDepth="0"
+ fi
+ else
+ ${LOCAL} svnDepth="0"
+ fi
+
+ # Do not do more work then we have to.
+ if [ "${REPONUM}" = "0" ]; then
+ return 0
+ fi
+
+ # Figure out which repo is the deepest and use it.
+ ${LOCAL} wonRepo="$(multiCompare "${gitDepth}" "${hgDepth}" "${bzrDepth}" "${svnDepth}")"
+ if [ "${wonRepo}" = "${gitDepth}" ]; then
+ gitRepo
+ elif [ "${wonRepo}" = "${hgDepth}" ]; then
+ hgRepo
+ elif [ "${wonRepo}" = "${bzrDepth}" ]; then
+ bzrRepo
+ elif [ "${wonRepo}" = "${svnDepth}" ]; then
+ svnRepo
+ fi
+}
+
+
+
+# Detect which repos we are in and gather data.
+# shellcheck source=/dev/null
+if [ -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
+ # When requested only read from the cache to populate our symbols.
+ . "${CACHEFILE}"
+else
+ # If a value is not set through the environment set VCS_EXTRA to nothing.
+ : "${VCS_EXTRA:=""}"
+ repoTest
+
+ if [ -f "${CACHEFILE}" ] && [ "${REPONUM}" = "0" ]; then
+ # We are not in a repo; try to use a previously generated cache to populate our symbols.
+ . "${CACHEFILE}"
+ # Do not overwrite the cache if we know we are not going to write anything new.
+ CACHEFORCE="1"
+ elif [ "${REPONUM}" = "0" ]; then
+ echo "error: No repo or cache detected." 1>&2
+ exit 1
+ fi
+fi
+
+
+# -s output is handled here.
+if [ ! -z "${VAROUT}" ]; then
+ if [ "${VAROUT}" = "VCS_TYPE" ]; then
+ echo "${VCS_TYPE}"
+ elif [ "${VAROUT}" = "VCS_BASENAME" ]; then
+ echo "${VCS_BASENAME}"
+ elif [ "${VAROUT}" = "VCS_NUM" ]; then
+ echo "${VCS_NUM}"
+ elif [ "${VAROUT}" = "VCS_DATE" ]; then
+ echo "${VCS_DATE}"
+ elif [ "${VAROUT}" = "VCS_BRANCH" ]; then
+ echo "${VCS_BRANCH}"
+ elif [ "${VAROUT}" = "VCS_TAG" ]; then
+ echo "${VCS_TAG}"
+ elif [ "${VAROUT}" = "VCS_TICK" ]; then
+ echo "${VCS_TICK}"
+ elif [ "${VAROUT}" = "VCS_FULL_HASH" ]; then
+ echo "${VCS_FULL_HASH}"
+ elif [ "${VAROUT}" = "VCS_SHORT_HASH" ]; then
+ echo "${VCS_SHORT_HASH}"
+ elif [ "${VAROUT}" = "VCS_WC_MODIFIED" ]; then
+ echo "${VCS_WC_MODIFIED}"
+ elif [ "${VAROUT}" = "VCS_ACTION_STAMP" ]; then
+ echo "${VCS_ACTION_STAMP}"
+ else
+ echo "error: Not a valid output symbol." 1>&2
+ exit 1
+ fi
+fi
+
+
+# Detect requested output type and use it.
+if [ ! -z "${AFILETYPE}" ]; then
+ if [ "${AFILETYPE}" = "c" ]; then
+ cOutput
+ elif [ "${AFILETYPE}" = "h" ]; then
+ hOutput
+ elif [ "${AFILETYPE}" = "xcode" ]; then
+ xcodeOutput
+ elif [ "${AFILETYPE}" = "swift" ]; then
+ swiftOutput
+ elif [ "${AFILETYPE}" = "sh" ]; then
+ shOutput
+ elif [ "${AFILETYPE}" = "py" ] || [ "${AFILETYPE}" = "python" ]; then
+ pyOutput
+ elif [ "${AFILETYPE}" = "pl" ] || [ "${AFILETYPE}" = "perl" ]; then
+ plOutput
+ elif [ "${AFILETYPE}" = "lua" ]; then
+ luaOutput
+ elif [ "${AFILETYPE}" = "php" ]; then
+ phpOutput
+ elif [ "${AFILETYPE}" = "ini" ]; then
+ iniOutput
+ elif [ "${AFILETYPE}" = "js" ]; then
+ jsOutput
+ elif [ "${AFILETYPE}" = "json" ]; then
+ jsonOutput
+ elif [ "${AFILETYPE}" = "java" ]; then
+ javaOutput
+ elif [ "${AFILETYPE}" = "javaprop" ]; then
+ javapropOutput
+ elif [ "${AFILETYPE}" = "csharp" ]; then
+ csharpOutput
+ elif [ "${AFILETYPE}" = "tex" ]; then
+ texOutput
+ elif [ "${AFILETYPE}" = "m4" ]; then
+ m4Output
+ elif [ "${AFILETYPE}" = "scheme" ]; then
+ schemeOutput
+ elif [ "${AFILETYPE}" = "clojure" ]; then
+ clojureOutput
+ elif [ "${AFILETYPE}" = "rpm" ]; then
+ rpmOutput
+ elif [ "${AFILETYPE}" = "hpp" ]; then
+ hppOutput
+ elif [ "${AFILETYPE}" = "matlab" ]; then
+ matlabOutput
+ elif [ "${AFILETYPE}" = "octave" ]; then
+ octaveOutput
+ elif [ "${AFILETYPE}" = "cmake" ]; then
+ cmakeOutput
+ else
+ echo "error: Not a valid output type." 1>&2
+ exit 1
+ fi
+fi
+
+
+# If requested, make a cache file.
+if [ ! -z "${CACHEFILE}" ] && [ ! "${CACHEFORCE}" = "1" ]; then
+ EXTRA_NAME="VCS_EXTRA"
+ shOutput > "${CACHEFILE}.tmp"
+
+ # Check to see if there have been any actual changes.
+ if [ ! -f "${CACHEFILE}" ]; then
+ mv -f "${CACHEFILE}.tmp" "${CACHEFILE}"
+ elif cmp -s "${CACHEFILE}.tmp" "${CACHEFILE}"; then
+ rm -f "${CACHEFILE}.tmp"
+ else
+ mv -f "${CACHEFILE}.tmp" "${CACHEFILE}"
+ fi
+fi
=====================================
wafhelpers/options.py
=====================================
--- a/wafhelpers/options.py
+++ b/wafhelpers/options.py
@@ -61,7 +61,7 @@ def options_cmd(ctx, config):
grp = ctx.add_option_group("NTP developer configure options")
grp.add_option('--build-version-tag', type='string',
- help="Append a tag to the version string (unused)")
+ help="Append a tag to the version string.")
grp.add_option('--cflags', type='string', action="callback",
callback=callback_flags,
help="Users should use CFLAGS in their environment.")
=====================================
wscript
=====================================
--- a/wscript
+++ b/wscript
@@ -1,9 +1,7 @@
from __future__ import print_function
-from datetime import datetime
import itertools
import os
-import re
import shlex
import sys
import time
@@ -201,29 +199,24 @@ def configure(ctx):
if ctx.options.disable_manpage:
ctx.env.DISABLE_MANPAGE = True
- source_date_epoch = os.getenv('SOURCE_DATE_EPOCH', None)
- if ctx.options.build_epoch is not None:
- build_epoch = ctx.options.build_epoch
- ctx.define("BUILD_EPOCH", build_epoch, comment="Using --build-epoch")
- elif source_date_epoch:
- if not source_date_epoch.isdigit():
- ctx.fatal("ERROR: malformed SOURCE_DATE_EPOCH")
- build_epoch = int(source_date_epoch)
- ctx.define("BUILD_EPOCH", build_epoch, comment="Using SOURCE_DATE_EPOCH")
- else:
- build_epoch = int(time.time())
- ctx.define("BUILD_EPOCH", build_epoch, comment="Using default")
-
- build_epoch_formatted = datetime.utcfromtimestamp(build_epoch).strftime("%Y-%m-%dT%H:%M:%SZ")
if ((os.path.exists(".git") and
ctx.find_program("git", var="BIN_GIT", mandatory=False))):
- cmd = ctx.env.BIN_GIT + shlex.split("log -1 --format=%h")
- git_short_hash = ctx.cmd_and_log(cmd).strip()
+ ctx.start_msg("DEVEL: Getting revision")
+ cmd = shlex.split("git log -1 --format=%H")
+ ctx.env.NTPSEC_REVISION = ctx.cmd_and_log(cmd).strip()
+ ctx.end_msg(ctx.env.NTPSEC_REVISION)
- ctx.env.NTPSEC_VERSION += "+ %s (git rev %s)" % (build_epoch_formatted, git_short_hash)
- else:
- ctx.env.NTPSEC_VERSION += " %s" % build_epoch_formatted
- ctx.define("NTPSEC_VERSION", ctx.env.NTPSEC_VERSION)
+ ctx.start_msg("Building version")
+ ctx.env.NTPSEC_VERSION_STRING = ctx.env.NTPSEC_VERSION
+
+ if ctx.env.NTPSEC_REVISION:
+ ctx.env.NTPSEC_VERSION_STRING += "-%s" % ctx.env.NTPSEC_REVISION[:7]
+
+ if ctx.options.build_version_tag:
+ ctx.env.NTPSEC_VERSION_STRING += "-%s" % ctx.options.build_version_tag
+
+ ctx.define("NTPSEC_VERSION_STRING", ctx.env.NTPSEC_VERSION_STRING)
+ ctx.end_msg(ctx.env.NTPSEC_VERSION_STRING)
# We require some things that C99 doesn't enable, like pthreads.
# These flags get propagated to both the host and main parts of the build.
@@ -858,6 +851,19 @@ int main(int argc, char **argv) {
msg("WARNING: This system has a 32-bit time_t.")
msg("WARNING: Your ntpd will fail on 2038-01-19T03:14:07Z.")
+ source_date_epoch = os.getenv('SOURCE_DATE_EPOCH', None)
+ if ctx.options.build_epoch is not None:
+ ctx.define("BUILD_EPOCH", ctx.options.build_epoch,
+ comment="Using --build-epoch")
+ elif source_date_epoch:
+ if not source_date_epoch.isdigit():
+ msg("ERROR: malformed SOURCE_DATE_EPOCH")
+ sys.exit(1)
+ ctx.define("BUILD_EPOCH", int(source_date_epoch),
+ comment="Using SOURCE_DATE_EPOCH")
+ else:
+ ctx.define("BUILD_EPOCH", int(time.time()), comment="Using default")
+
# before write_config()
droproot_type = ""
if ctx.is_defined("HAVE_LINUX_CAPABILITY"):
@@ -981,6 +987,8 @@ def afterparty(ctx):
# Note that this setup is applied to the build tree, not the
# source tree. Only the build-tree copies of the programs are
# expected to work.
+ if ctx.cmd == 'clean':
+ ctx.exec_command("rm -f ntpd/version.h ")
for x in ("ntpclients", "tests/pylib",):
# List used to be longer...
path_build = ctx.bldnode.make_node("pylib")
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/7f83b7f0cdd9480160a9655c3319b8272fda024d...3aae297ad674a904529f32a619476f935d3813e6
---
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/7f83b7f0cdd9480160a9655c3319b8272fda024d...3aae297ad674a904529f32a619476f935d3813e6
You're receiving this email because of your account on gitlab.com.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20180313/611f7148/attachment.html>
More information about the vc
mailing list