build: add PCHs for ndnsec and unit tests, fine-tune the existing ones

A full debug+tests build now takes 10% less time with gcc-9 and 21% less
with clang-10. Release builds (without tests) are only marginally faster
with gcc and 7% faster with clang.

Change-Id: I494778fe44cecc6209819a49f48a03f4d16c36af
diff --git a/tools/ndnsec/accumulator.hpp b/tools/ndnsec/accumulator.hpp
new file mode 100644
index 0000000..7c08581
--- /dev/null
+++ b/tools/ndnsec/accumulator.hpp
@@ -0,0 +1,168 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TOOLS_NDNSEC_ACCUMULATOR_HPP
+#define NDN_TOOLS_NDNSEC_ACCUMULATOR_HPP
+
+#include <boost/program_options/value_semantic.hpp>
+
+namespace ndn {
+namespace ndnsec {
+
+/**
+ * @brief An accumulating option value to handle multiple incrementing options.
+ *
+ * Based on https://gitorious.org/bwy/bwy/source/8753148c324ddfacb1f3cdc315650586bd7b75a4:use/accumulator.hpp
+ * @sa http://benjaminwolsey.de/node/103
+ */
+template<typename T>
+class AccumulatorType : public boost::program_options::value_semantic
+{
+public:
+  explicit
+  AccumulatorType(T* store)
+    : m_store(store)
+    , m_interval(1)
+    , m_default(0)
+  {
+  }
+
+  /// @brief Set the default value for this option.
+  AccumulatorType*
+  setDefaultValue(const T& t)
+  {
+    m_default = t;
+    return this;
+  }
+
+  /**
+   * @brief Set the interval for this option.
+   *
+   * Unlike for program_options::value, this specifies a value
+   * to be applied on each occurrence of the option.
+   */
+  AccumulatorType*
+  setInterval(const T& t)
+  {
+    m_interval = t;
+    return this;
+  }
+
+  std::string
+  name() const final
+  {
+    return std::string();
+  }
+
+  // There are no tokens for an AccumulatorType
+  unsigned
+  min_tokens() const final
+  {
+    return 0;
+  }
+
+  unsigned
+  max_tokens() const final
+  {
+    return 0;
+  }
+
+  // Accumulating from different sources is silly.
+  bool
+  is_composing() const final
+  {
+    return false;
+  }
+
+  // Requiring one or more appearances is unlikely.
+  bool
+  is_required() const final
+  {
+    return false;
+  }
+
+  /**
+   * @brief Parse options
+   *
+   * Every appearance of the option simply increments the value
+   * There should never be any tokens.
+   */
+  void
+  parse(boost::any& value_store, const std::vector<std::string>& new_tokens, bool utf8) const final
+  {
+    if (value_store.empty())
+      value_store = T();
+    boost::any_cast<T&>(value_store) += m_interval;
+  }
+
+  /**
+   * @brief If the option doesn't appear, this is the default value.
+   */
+  bool
+  apply_default(boost::any& value_store) const final
+  {
+    value_store = m_default;
+    return true;
+  }
+
+  /**
+   * @brief Notify the user function with the value of the value store.
+   */
+  void
+  notify(const boost::any& value_store) const final
+  {
+    const T* val = boost::any_cast<T>(&value_store);
+    if (m_store)
+      *m_store = *val;
+  }
+
+#if (BOOST_VERSION >= 105900) && (BOOST_VERSION < 106500)
+  bool
+  adjacent_tokens_only() const final
+  {
+    return false;
+  }
+#endif // (BOOST_VERSION >= 105900) && (BOOST_VERSION < 106500)
+
+private:
+  T* m_store;
+  T m_interval;
+  T m_default;
+};
+
+template <typename T>
+AccumulatorType<T>*
+accumulator()
+{
+  return new AccumulatorType<T>(nullptr);
+}
+
+template <typename T>
+AccumulatorType<T>*
+accumulator(T* store)
+{
+  return new AccumulatorType<T>(store);
+}
+
+} // namespace ndnsec
+} // namespace ndn
+
+#endif // NDN_TOOLS_NDNSEC_ACCUMULATOR_HPP
diff --git a/tools/ndnsec/cert-dump.cpp b/tools/ndnsec/cert-dump.cpp
index 8e74948..38d8ed5 100644
--- a/tools/ndnsec/cert-dump.cpp
+++ b/tools/ndnsec/cert-dump.cpp
@@ -22,6 +22,8 @@
 #include "ndnsec.hpp"
 #include "util.hpp"
 
+#include "ndn-cxx/util/io.hpp"
+
 #include <boost/asio/ip/tcp.hpp>
 #if BOOST_VERSION < 106700
 #include <boost/date_time/posix_time/posix_time_duration.hpp>
diff --git a/tools/ndnsec/cert-gen.cpp b/tools/ndnsec/cert-gen.cpp
index ec630c9..1749b2b 100644
--- a/tools/ndnsec/cert-gen.cpp
+++ b/tools/ndnsec/cert-gen.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -26,6 +26,7 @@
 #include "ndn-cxx/security/transform/buffer-source.hpp"
 #include "ndn-cxx/security/transform/public-key.hpp"
 #include "ndn-cxx/security/transform/stream-sink.hpp"
+#include "ndn-cxx/security/v2/additional-description.hpp"
 
 namespace ndn {
 namespace ndnsec {
diff --git a/tools/ndnsec/export.cpp b/tools/ndnsec/export.cpp
index 6d3c260..4429dd4 100644
--- a/tools/ndnsec/export.cpp
+++ b/tools/ndnsec/export.cpp
@@ -23,6 +23,7 @@
 #include "util.hpp"
 
 #include "ndn-cxx/security/impl/openssl.hpp"
+#include "ndn-cxx/util/io.hpp"
 
 #include <boost/scope_exit.hpp>
 
diff --git a/tools/ndnsec/import.cpp b/tools/ndnsec/import.cpp
index c58a6ed..fa67a44 100644
--- a/tools/ndnsec/import.cpp
+++ b/tools/ndnsec/import.cpp
@@ -23,6 +23,7 @@
 #include "util.hpp"
 
 #include "ndn-cxx/security/impl/openssl.hpp"
+#include "ndn-cxx/util/io.hpp"
 
 #include <boost/scope_exit.hpp>
 
diff --git a/tools/ndnsec/key-gen.cpp b/tools/ndnsec/key-gen.cpp
index 1d3d79a..553f06c 100644
--- a/tools/ndnsec/key-gen.cpp
+++ b/tools/ndnsec/key-gen.cpp
@@ -22,6 +22,8 @@
 #include "ndnsec.hpp"
 #include "util.hpp"
 
+#include "ndn-cxx/util/io.hpp"
+
 namespace ndn {
 namespace ndnsec {
 
diff --git a/tools/ndnsec/list.cpp b/tools/ndnsec/list.cpp
index 1aa2d4e..fbe1140 100644
--- a/tools/ndnsec/list.cpp
+++ b/tools/ndnsec/list.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -19,6 +19,7 @@
  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
  */
 
+#include "accumulator.hpp"
 #include "ndnsec.hpp"
 #include "util.hpp"
 
diff --git a/tools/ndnsec/main.cpp b/tools/ndnsec/main.cpp
index 8548748..c0be01b 100644
--- a/tools/ndnsec/main.cpp
+++ b/tools/ndnsec/main.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -20,12 +20,12 @@
  */
 
 #include "ndnsec.hpp"
-#include "util.hpp"
 
 #include "ndn-cxx/util/logger.hpp"
 #include "ndn-cxx/version.hpp"
 
 #include <boost/exception/diagnostic_information.hpp>
+#include <iostream>
 
 NDN_LOG_INIT(ndnsec);
 
diff --git a/tools/ndnsec/ndnsec-pch.hpp b/tools/ndnsec/ndnsec-pch.hpp
new file mode 100644
index 0000000..ea537fa
--- /dev/null
+++ b/tools/ndnsec/ndnsec-pch.hpp
@@ -0,0 +1,31 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TOOLS_NDNSEC_NDNSEC_PCH_HPP
+#define NDN_TOOLS_NDNSEC_NDNSEC_PCH_HPP
+
+#include "util.hpp"
+
+#include "ndn-cxx/util/io.hpp"
+
+#include <boost/asio/ip/tcp.hpp>
+
+#endif // NDN_TOOLS_NDNSEC_NDNSEC_PCH_HPP
diff --git a/tools/ndnsec/sign-req.cpp b/tools/ndnsec/sign-req.cpp
index d8d7486..f9f97e9 100644
--- a/tools/ndnsec/sign-req.cpp
+++ b/tools/ndnsec/sign-req.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,6 +22,8 @@
 #include "ndnsec.hpp"
 #include "util.hpp"
 
+#include "ndn-cxx/util/io.hpp"
+
 namespace ndn {
 namespace ndnsec {
 
diff --git a/tools/ndnsec/util.cpp b/tools/ndnsec/util.cpp
index 4895ed3..cb35b52 100644
--- a/tools/ndnsec/util.cpp
+++ b/tools/ndnsec/util.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,6 +22,7 @@
 #include "util.hpp"
 
 #include "ndn-cxx/security/impl/openssl.hpp"
+#include "ndn-cxx/util/io.hpp"
 
 #include <unistd.h>
 
diff --git a/tools/ndnsec/util.hpp b/tools/ndnsec/util.hpp
index 90c3284..873f6b0 100644
--- a/tools/ndnsec/util.hpp
+++ b/tools/ndnsec/util.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -23,12 +23,8 @@
 #define NDN_TOOLS_NDNSEC_UTIL_HPP
 
 #include "ndn-cxx/security/key-chain.hpp"
-#include "ndn-cxx/security/v2/additional-description.hpp"
-#include "ndn-cxx/util/io.hpp"
 
-#include <fstream>
 #include <iostream>
-#include <string>
 
 #include <boost/program_options/options_description.hpp>
 #include <boost/program_options/parsers.hpp>
@@ -46,146 +42,11 @@
   }
 };
 
-bool
-getPassword(std::string& password, const std::string& prompt, bool shouldConfirm = true);
-
 security::v2::Certificate
 loadCertificate(const std::string& fileName);
 
-/**
- * @brief An accumulating option value to handle multiple incrementing options.
- *
- * Based on https://gitorious.org/bwy/bwy/source/8753148c324ddfacb1f3cdc315650586bd7b75a4:use/accumulator.hpp
- * @sa http://benjaminwolsey.de/node/103
- */
-template<typename T>
-class AccumulatorType : public boost::program_options::value_semantic
-{
-public:
-  explicit
-  AccumulatorType(T* store)
-    : m_store(store)
-    , m_interval(1)
-    , m_default(0)
-  {
-  }
-
-  /// @brief Set the default value for this option.
-  AccumulatorType*
-  setDefaultValue(const T& t)
-  {
-    m_default = t;
-    return this;
-  }
-
-  /**
-   * @brief Set the interval for this option.
-   *
-   * Unlike for program_options::value, this specifies a value
-   * to be applied on each occurrence of the option.
-   */
-  AccumulatorType*
-  setInterval(const T& t)
-  {
-    m_interval = t;
-    return this;
-  }
-
-  std::string
-  name() const final
-  {
-    return std::string();
-  }
-
-  // There are no tokens for an AccumulatorType
-  unsigned
-  min_tokens() const final
-  {
-    return 0;
-  }
-
-  unsigned
-  max_tokens() const final
-  {
-    return 0;
-  }
-
-  // Accumulating from different sources is silly.
-  bool
-  is_composing() const final
-  {
-    return false;
-  }
-
-  // Requiring one or more appearances is unlikely.
-  bool
-  is_required() const final
-  {
-    return false;
-  }
-
-  /**
-   * @brief Parse options
-   *
-   * Every appearance of the option simply increments the value
-   * There should never be any tokens.
-   */
-  void
-  parse(boost::any& value_store, const std::vector<std::string>& new_tokens, bool utf8) const final
-  {
-    if (value_store.empty())
-      value_store = T();
-    boost::any_cast<T&>(value_store) += m_interval;
-  }
-
-  /**
-   * @brief If the option doesn't appear, this is the default value.
-   */
-  bool
-  apply_default(boost::any& value_store) const final
-  {
-    value_store = m_default;
-    return true;
-  }
-
-  /**
-   * @brief Notify the user function with the value of the value store.
-   */
-  void
-  notify(const boost::any& value_store) const final
-  {
-    const T* val = boost::any_cast<T>(&value_store);
-    if (m_store)
-      *m_store = *val;
-  }
-
-#if (BOOST_VERSION >= 105900) && (BOOST_VERSION < 106500)
-  bool
-  adjacent_tokens_only() const final
-  {
-    return false;
-  }
-#endif // (BOOST_VERSION >= 105900) && (BOOST_VERSION < 106500)
-
-private:
-  T* m_store;
-  T m_interval;
-  T m_default;
-};
-
-template <typename T>
-AccumulatorType<T>*
-accumulator()
-{
-  return new AccumulatorType<T>(0);
-}
-
-template <typename T>
-AccumulatorType<T>*
-accumulator(T* store)
-{
-  return new AccumulatorType<T>(store);
-}
+bool
+getPassword(std::string& password, const std::string& prompt, bool shouldConfirm = true);
 
 } // namespace ndnsec
 } // namespace ndn
diff --git a/tools/ndnsec/wscript b/tools/ndnsec/wscript
new file mode 100644
index 0000000..9f61ae4
--- /dev/null
+++ b/tools/ndnsec/wscript
@@ -0,0 +1,14 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+
+top = '../../'
+
+def build(bld):
+    bld.objects(target='tool-ndnsec-objects',
+                source=bld.path.ant_glob('*.cpp', excl=['main.cpp']),
+                features='pch',
+                headers='ndnsec-pch.hpp',
+                use='ndn-cxx')
+    bld.program(name='tool-ndnsec',
+                target=top + 'bin/ndnsec',
+                source=['main.cpp'],
+                use='tool-ndnsec-objects')
diff --git a/tools/wscript b/tools/wscript
index 29b8536..b8c2358 100644
--- a/tools/wscript
+++ b/tools/wscript
@@ -2,31 +2,36 @@
 
 from waflib import Utils
 
-top = '..'
+top = '../'
 
 def build(bld):
     # Single object tools:
-    # tools/example-tool.cpp is a self-contained tool with a main() function
-    # and is built as build/bin/example-tool.
-    # These tools cannot be unit-tested.
+    # tools/foo.cpp is a self-contained tool with a main() function
+    # and is built as build/bin/foo. These tools cannot be unit-tested.
     for tool in bld.path.ant_glob('*.cpp'):
         name = tool.change_ext('').path_from(bld.path.get_bld())
         bld.program(name='tool-%s' % name,
-                    target='../bin/%s' % name,
+                    target=top + 'bin/%s' % name,
                     source=[tool],
                     use='ndn-cxx')
 
     # Sub-directory tools:
-    # tools/example-tool/**/*.cpp is compiled and linked into build/bin/example-tool.
-    # tools/example-tool/main.cpp must exist and must contain the main() function.
-    # All other objects are collected into 'tools-objects' and can be unit-tested.
-    testableObjects = []
+    # tools/foo/**/*.cpp are compiled and linked into build/bin/foo.
+    # tools/foo/main.cpp must exist and must contain the main() function.
+    # All other objects are collected into 'tool-foo-objects' and can be unit-tested.
     for subdir in bld.path.ant_glob('*', dir=True, src=False):
+        name = subdir.path_from(bld.path)
+        subWscript = subdir.find_node('wscript')
+        if subWscript:
+            # if the subdir has a wscript, delegate to it
+            bld.recurse(name)
+            continue
+
         mainFile = subdir.find_node('main.cpp')
         if mainFile is None:
-            continue # not a C++ tool
+            # not a C++ tool, skip the subdir
+            continue
 
-        name = subdir.path_from(bld.path)
         srcFiles = subdir.ant_glob('**/*.cpp', excl=['main.cpp'])
         srcObjects = ''
         if srcFiles:
@@ -34,23 +39,17 @@
             bld.objects(target=srcObjects,
                         source=srcFiles,
                         use='ndn-cxx')
-            testableObjects.append(srcObjects)
-
         bld.program(name='tool-%s' % name,
-                    target='../bin/%s' % name,
+                    target=top + 'bin/%s' % name,
                     source=[mainFile],
                     use='ndn-cxx ' + srcObjects)
 
-    bld.objects(target='tools-objects',
-                source=[],
-                use=testableObjects)
-
     # Tool wrappers
     wrapperFiles = bld.path.ant_glob('wrapper/*.sh')
     bld(name='tool-wrappers',
         features='subst',
         source=wrapperFiles,
-        target=['../bin/%s' % node.change_ext('', '.sh').path_from(bld.path.find_dir('wrapper').get_bld())
+        target=[top + 'bin/%s' % node.change_ext('', '.sh').path_from(bld.path.find_dir('wrapper').get_bld())
                 for node in wrapperFiles],
         install_path='${BINDIR}',
         chmod=Utils.O755)