build: add waf-tool to simplify building with AddressSanitizer & friends

Change-Id: I368fe5bbbe3a92a7e82c22f56f6784761d94118d
Refs: #2589
diff --git a/.waf-tools/default-compiler-flags.py b/.waf-tools/default-compiler-flags.py
index abe0a25..fd15dbb 100644
--- a/.waf-tools/default-compiler-flags.py
+++ b/.waf-tools/default-compiler-flags.py
@@ -60,7 +60,7 @@
             supportedFlags += [flag]
 
     self.end_msg(' '.join(supportedFlags))
-    self.env.CXXFLAGS = supportedFlags + self.env.CXXFLAGS
+    self.env.prepend_value('CXXFLAGS', supportedFlags)
 
 @Configure.conf
 def add_supported_linkflags(self, linkflags):
@@ -78,7 +78,7 @@
             supportedFlags += [flag]
 
     self.end_msg(' '.join(supportedFlags))
-    self.env.LINKFLAGS = supportedFlags + self.env.LINKFLAGS
+    self.env.prepend_value('LINKFLAGS', supportedFlags)
 
 
 class CompilerFlags(object):
diff --git a/.waf-tools/sanitizers.py b/.waf-tools/sanitizers.py
new file mode 100644
index 0000000..a8fe55d
--- /dev/null
+++ b/.waf-tools/sanitizers.py
@@ -0,0 +1,22 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+
+def options(opt):
+    opt.add_option('--with-sanitizer', action='store', default='', dest='sanitizers',
+                   help='Comma-separated list of compiler sanitizers to enable [default=none]')
+
+def configure(conf):
+    for san in conf.options.sanitizers.split(','):
+        if not san:
+            continue
+
+        sanflag = '-fsanitize=%s' % san
+        conf.start_msg('Checking if compiler supports %s' % sanflag)
+
+        if conf.check_cxx(cxxflags=['-Werror', sanflag, '-fno-omit-frame-pointer'],
+                          linkflags=[sanflag], mandatory=False):
+            conf.end_msg('yes')
+            conf.env.append_unique('CXXFLAGS', [sanflag, '-fno-omit-frame-pointer'])
+            conf.env.append_unique('LINKFLAGS', [sanflag])
+        else:
+            conf.end_msg('no', color='RED')
+            conf.fatal('%s sanitizer is not supported by the current compiler' % san)
diff --git a/wscript b/wscript
index 76c39a8..af7e4e9 100644
--- a/wscript
+++ b/wscript
@@ -11,9 +11,10 @@
 
 def options(opt):
     opt.load(['compiler_cxx', 'gnu_dirs', 'c_osx'])
-    opt.load(['default-compiler-flags', 'coverage', 'osx-security', 'pch',
-              'boost', 'cryptopp', 'sqlite3', 'openssl',
-              'doxygen', 'sphinx_build', 'type_traits', 'compiler-features'],
+    opt.load(['default-compiler-flags', 'compiler-features', 'type_traits',
+              'coverage', 'pch', 'sanitizers', 'osx-security',
+              'boost', 'cryptopp', 'openssl', 'sqlite3',
+              'doxygen', 'sphinx_build'],
              tooldir=['.waf-tools'])
 
     opt = opt.add_option_group('Library Options')
@@ -65,9 +66,11 @@
     if not conf.options.enable_shared and not conf.options.enable_static:
         conf.fatal("Either static library or shared library must be enabled")
 
-    conf.load(['compiler_cxx', 'gnu_dirs', 'c_osx', 'default-compiler-flags',
-               'osx-security', 'pch', 'boost', 'cryptopp', 'sqlite3', 'openssl',
-               'type_traits', 'compiler-features', 'doxygen', 'sphinx_build'])
+    conf.load(['compiler_cxx', 'gnu_dirs', 'c_osx',
+               'default-compiler-flags', 'compiler-features', 'type_traits',
+               'pch', 'sanitizers', 'osx-security',
+               'boost', 'cryptopp', 'openssl', 'sqlite3',
+               'doxygen', 'sphinx_build'])
 
     conf.env['WITH_TESTS'] = conf.options.with_tests
     conf.env['WITH_TOOLS'] = conf.options.with_tools
@@ -156,8 +159,7 @@
                 int(VERSION_SPLIT[2]),
         VERSION_MAJOR=VERSION_SPLIT[0],
         VERSION_MINOR=VERSION_SPLIT[1],
-        VERSION_PATCH=VERSION_SPLIT[2],
-        )
+        VERSION_PATCH=VERSION_SPLIT[2])
 
     libndn_cxx = dict(
         target="ndn-cxx",
@@ -169,8 +171,7 @@
         use='version BOOST CRYPTOPP OPENSSL SQLITE3 RT PTHREAD',
         includes=". src",
         export_includes="src",
-        install_path='${LIBDIR}',
-        )
+        install_path='${LIBDIR}')
 
     if bld.env['HAVE_OSX_SECURITY']:
         libndn_cxx['source'] += bld.path.ant_glob('src/security/**/*-osx.cpp')
@@ -208,13 +209,13 @@
         if bld.env['CXXFLAGS_%s' % lib]:
             pkgconfig_cxxflags += Utils.to_list(bld.env['CXXFLAGS_%s' % lib])
 
-    EXTRA_FRAMEWORKS = "";
+    EXTRA_FRAMEWORKS = ""
     if bld.env['HAVE_OSX_SECURITY']:
         EXTRA_FRAMEWORKS = "-framework CoreFoundation -framework Security"
 
     def uniq(alist):
-        set = {}
-        return [set.setdefault(e,e) for e in alist if e not in set]
+        seen = set()
+        return [x for x in alist if x not in seen and not seen.add(x)]
 
     pkconfig = bld(features="subst",
          source="libndn-cxx.pc.in",
@@ -229,10 +230,8 @@
          EXTRA_LINKFLAGS=" ".join(uniq(pkgconfig_linkflags)),
          EXTRA_INCLUDES=" ".join([('-I%s' % i) for i in uniq(pkgconfig_includes)]),
          EXTRA_CXXFLAGS=" ".join(uniq(pkgconfig_cxxflags)),
-         EXTRA_FRAMEWORKS=EXTRA_FRAMEWORKS,
-        )
+         EXTRA_FRAMEWORKS=EXTRA_FRAMEWORKS)
 
-    # Unit tests
     if bld.env['WITH_TESTS']:
         bld.recurse('tests')
 
@@ -277,7 +276,7 @@
     version(bld)
 
     if not bld.env.DOXYGEN:
-        Logs.error("ERROR: cannot build documentation (`doxygen' is not found in $PATH)")
+        Logs.error("ERROR: cannot build documentation (`doxygen' not found in $PATH)")
     else:
         bld(features="subst",
             name="doxygen-conf",
@@ -289,8 +288,7 @@
             HTML_FOOTER="../build/docs/named_data_theme/named_data_footer-with-analytics.html" \
                           if os.getenv('GOOGLE_ANALYTICS', None) \
                           else "../docs/named_data_theme/named_data_footer.html",
-            GOOGLE_ANALYTICS=os.getenv('GOOGLE_ANALYTICS', ""),
-            )
+            GOOGLE_ANALYTICS=os.getenv('GOOGLE_ANALYTICS', ""))
 
         bld(features="doxygen",
             doxyfile='docs/doxygen.conf',
@@ -300,7 +298,7 @@
     version(bld)
 
     if not bld.env.SPHINX_BUILD:
-        bld.fatal("ERROR: cannot build documentation (`sphinx-build' is not found in $PATH)")
+        bld.fatal("ERROR: cannot build documentation (`sphinx-build' not found in $PATH)")
     else:
         bld(features="sphinx",
             outdir="docs",
@@ -308,7 +306,6 @@
             config="docs/conf.py",
             VERSION=VERSION)
 
-
 def version(ctx):
     if getattr(Context.g_module, 'VERSION_BASE', None):
         return