build: update sphinx tool

Use the improved version from the ndn-cxx repository

Refs: #5298
Change-Id: I681f38faa0078a67089b6a9c22eb56572a1c2940
diff --git a/.waf-tools/boost.py b/.waf-tools/boost.py
index a6cdabe..dab6949 100644
--- a/.waf-tools/boost.py
+++ b/.waf-tools/boost.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python
-# encoding: utf-8
-#
 # partially based on boost.py written by Gernot Vormayr
 # written by Ruediger Sonderfeld <ruediger@c-plusplus.de>, 2008
 # modified by Bjoern Michaelsen, 2008
diff --git a/.waf-tools/coverage.py b/.waf-tools/coverage.py
index cc58165..4ca9740 100644
--- a/.waf-tools/coverage.py
+++ b/.waf-tools/coverage.py
@@ -1,5 +1,3 @@
-# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
-
 from waflib import TaskGen
 
 def options(opt):
diff --git a/.waf-tools/default-compiler-flags.py b/.waf-tools/default-compiler-flags.py
index ef97f25..98b3a7f 100644
--- a/.waf-tools/default-compiler-flags.py
+++ b/.waf-tools/default-compiler-flags.py
@@ -1,5 +1,3 @@
-# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
-
 import platform
 from waflib import Configure, Logs, Utils
 
@@ -128,16 +126,16 @@
 
     def getGeneralFlags(self, conf):
         """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are always needed"""
+        return {'CXXFLAGS': [], 'LINKFLAGS': [], 'DEFINES': []}
+
+    def getDebugFlags(self, conf):
+        """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are needed only in debug mode"""
         return {
             'CXXFLAGS': [],
             'LINKFLAGS': [],
             'DEFINES': ['BOOST_ASIO_NO_DEPRECATED', 'BOOST_FILESYSTEM_NO_DEPRECATED'],
         }
 
-    def getDebugFlags(self, conf):
-        """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are needed only in debug mode"""
-        return {'CXXFLAGS': [], 'LINKFLAGS': [], 'DEFINES': []}
-
     def getOptimizedFlags(self, conf):
         """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are needed only in optimized mode"""
         return {'CXXFLAGS': [], 'LINKFLAGS': [], 'DEFINES': ['NDEBUG']}
@@ -245,6 +243,9 @@
         elif self.getCompilerVersion(conf) >= (15, 0, 0):
             # https://releases.llvm.org/15.0.0/projects/libcxx/docs/UsingLibcxx.html#enabling-the-safe-libc-mode
             flags['DEFINES'] += ['_LIBCPP_ENABLE_ASSERTIONS=1']
+        # Tell libc++ to avoid including transitive headers
+        # https://libcxx.llvm.org/DesignDocs/HeaderRemovalPolicy.html
+        flags['DEFINES'] += ['_LIBCPP_REMOVE_TRANSITIVE_INCLUDES=1']
         return flags
 
     def getOptimizedFlags(self, conf):
diff --git a/.waf-tools/doxygen.py b/.waf-tools/doxygen.py
index 6d8066b..6948194 100644
--- a/.waf-tools/doxygen.py
+++ b/.waf-tools/doxygen.py
@@ -1,9 +1,6 @@
-#! /usr/bin/env python
-# encoding: UTF-8
 # Thomas Nagy 2008-2010 (ita)
 
 """
-
 Doxygen support
 
 Variables passed to bld():
@@ -27,9 +24,8 @@
 			bld(features="doxygen", doxyfile='Doxyfile', ...)
 """
 
-from fnmatch import fnmatchcase
-import os, os.path, re, stat
-from waflib import Task, Utils, Node, Logs, Errors, Build
+import os, os.path, re
+from waflib import Task, Utils, Node
 from waflib.TaskGen import feature
 
 DOXY_STR = '"${DOXYGEN}" - '
@@ -207,8 +203,8 @@
 	conf.find_program('doxygen', var='DOXYGEN', mandatory=False)
 	conf.find_program('tar', var='TAR', mandatory=False)
 
-# doxygen docs
+# doxygen command
 from waflib.Build import BuildContext
 class doxy(BuildContext):
-    cmd = "doxygen"
-    fun = "doxygen"
+    cmd = 'doxygen'
+    fun = 'doxygen'
diff --git a/.waf-tools/sanitizers.py b/.waf-tools/sanitizers.py
index a8fe55d..c65d009 100644
--- a/.waf-tools/sanitizers.py
+++ b/.waf-tools/sanitizers.py
@@ -1,4 +1,4 @@
-# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+# Davide Pesavento (LIP6), 2016
 
 def options(opt):
     opt.add_option('--with-sanitizer', action='store', default='', dest='sanitizers',
diff --git a/.waf-tools/sphinx.py b/.waf-tools/sphinx.py
new file mode 100644
index 0000000..7fccd64
--- /dev/null
+++ b/.waf-tools/sphinx.py
@@ -0,0 +1,77 @@
+# inspired by code by Hans-Martin von Gaudecker, 2012
+
+"""Support for Sphinx documentation"""
+
+import os
+from waflib import Task, TaskGen
+
+
+class sphinx_build(Task.Task):
+    color = 'BLUE'
+    run_str = '${SPHINX_BUILD} -q -b ${BUILDERNAME} -D ${VERSION} -D ${RELEASE} -d ${DOCTREEDIR} ${SRCDIR} ${OUTDIR}'
+
+    def keyword(self):
+        return f'Processing ({self.env.BUILDERNAME})'
+
+
+# from https://docs.python.org/3.12/whatsnew/3.12.html#imp
+def load_source(modname, filename):
+    import importlib.util
+    from importlib.machinery import SourceFileLoader
+    loader = SourceFileLoader(modname, filename)
+    spec = importlib.util.spec_from_file_location(modname, filename, loader=loader)
+    module = importlib.util.module_from_spec(spec)
+    loader.exec_module(module)
+    return module
+
+
+@TaskGen.feature('sphinx')
+@TaskGen.before_method('process_source')
+def process_sphinx(self):
+    """Set up the task generator with a Sphinx instance and create a task."""
+
+    conf = self.path.find_node(self.config)
+    if not conf:
+        self.bld.fatal(f'Sphinx configuration file {repr(self.config)} not found')
+
+    inputs = [conf] + self.to_nodes(self.source)
+    task = self.create_task('sphinx_build', inputs, always_run=getattr(self, 'always', False))
+
+    confdir = conf.parent.abspath()
+    buildername = getattr(self, 'builder', 'html')
+    srcdir = getattr(self, 'srcdir', confdir)
+    outdir = self.path.find_or_declare(getattr(self, 'outdir', buildername)).get_bld()
+    doctreedir = getattr(self, 'doctreedir', os.path.join(outdir.abspath(), '.doctrees'))
+    release = getattr(self, 'release', self.version)
+
+    task.env['BUILDERNAME'] = buildername
+    task.env['SRCDIR'] = srcdir
+    task.env['OUTDIR'] = outdir.abspath()
+    task.env['DOCTREEDIR'] = doctreedir
+    task.env['VERSION'] = f'version={self.version}'
+    task.env['RELEASE'] = f'release={release}'
+
+    if buildername == 'man':
+        confdata = load_source('sphinx_conf', conf.abspath())
+        for i in confdata.man_pages:
+            target = outdir.find_or_declare(f'{i[1]}.{i[4]}')
+            task.outputs.append(target)
+            if self.install_path:
+                self.bld.install_files(f'{self.install_path}/man{i[4]}/', target)
+    else:
+        task.outputs.append(outdir)
+
+    # prevent process_source from complaining that there is no extension mapping for .rst files
+    self.source = []
+
+
+def configure(conf):
+    """Check if sphinx-build program is available."""
+    conf.find_program('sphinx-build', var='SPHINX_BUILD', mandatory=False)
+
+
+# sphinx command
+from waflib.Build import BuildContext
+class sphinx(BuildContext):
+    cmd = 'sphinx'
+    fun = 'sphinx'
diff --git a/.waf-tools/sphinx_build.py b/.waf-tools/sphinx_build.py
deleted file mode 100644
index b44a54f..0000000
--- a/.waf-tools/sphinx_build.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf-8
-
-# inspired by code by Hans-Martin von Gaudecker, 2012
-
-import os
-from waflib import Node, Task, TaskGen, Errors, Logs, Build, Utils
-
-class sphinx_build(Task.Task):
-    color = 'BLUE'
-    run_str = '${SPHINX_BUILD} -D ${VERSION} -D ${RELEASE} -q -b ${BUILDERNAME} -d ${DOCTREEDIR} ${SRCDIR} ${OUTDIR}'
-
-    def __str__(self):
-        env = self.env
-        src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
-        tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
-        if self.outputs: sep = ' -> '
-        else: sep = ''
-        return'%s [%s]: %s%s%s\n'%(self.__class__.__name__.replace('_task',''),
-                                   self.env['BUILDERNAME'], src_str, sep, tgt_str)
-
-@TaskGen.extension('.py', '.rst')
-def sig_hook(self, node):
-    node.sig=Utils.h_file(node.abspath())
-
-@TaskGen.feature("sphinx")
-@TaskGen.before_method("process_source")
-def apply_sphinx(self):
-    """Set up the task generator with a Sphinx instance and create a task."""
-
-    inputs = []
-    for i in Utils.to_list(self.source):
-        if not isinstance(i, Node.Node):
-            node = self.path.find_node(node)
-        else:
-            node = i
-        if not node:
-            raise ValueError('[%s] file not found' % i)
-        inputs.append(node)
-
-    task = self.create_task('sphinx_build', inputs)
-
-    conf = self.path.find_node(self.config)
-    task.inputs.append(conf)
-
-    confdir = conf.parent.abspath()
-    buildername = getattr(self, 'builder', 'html')
-    srcdir = getattr(self, 'srcdir', confdir)
-    outdir = self.path.find_or_declare(getattr(self, 'outdir', buildername)).get_bld()
-    doctreedir = getattr(self, 'doctreedir', os.path.join(outdir.abspath(), '.doctrees'))
-
-    task.env['BUILDERNAME'] = buildername
-    task.env['SRCDIR'] = srcdir
-    task.env['DOCTREEDIR'] = doctreedir
-    task.env['OUTDIR'] = outdir.abspath()
-    task.env['VERSION'] = 'version=%s' % self.version
-    task.env['RELEASE'] = 'release=%s' % getattr(self, 'release', self.version)
-
-    import imp
-    confData = imp.load_source('sphinx_conf', conf.abspath())
-
-    if buildername == 'man':
-        for i in confData.man_pages:
-            target = outdir.find_or_declare('%s.%d' % (i[1], i[4]))
-            task.outputs.append(target)
-
-            if self.install_path:
-                self.bld.install_files('%s/man%d/' % (self.install_path, i[4]), target)
-    else:
-        task.outputs.append(outdir)
-
-def configure(conf):
-    conf.find_program('sphinx-build', var='SPHINX_BUILD', mandatory=False)
-
-# sphinx docs
-from waflib.Build import BuildContext
-class sphinx(BuildContext):
-    cmd = "sphinx"
-    fun = "sphinx"
diff --git a/wscript b/wscript
index c5cab7f..50c8cf8 100644
--- a/wscript
+++ b/wscript
@@ -1,6 +1,6 @@
 # -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
 """
-Copyright (c) 2014-2023,  The University of Memphis,
+Copyright (c) 2014-2024,  The University of Memphis,
                           Regents of the University of California,
                           Arizona Board of Regents.
 
@@ -31,7 +31,7 @@
     opt.load(['compiler_cxx', 'gnu_dirs'])
     opt.load(['default-compiler-flags',
               'coverage', 'sanitizers', 'boost',
-              'doxygen', 'sphinx_build'],
+              'doxygen', 'sphinx'],
              tooldir=['.waf-tools'])
 
     optgrp = opt.add_option_group('NLSR Options')
@@ -57,7 +57,7 @@
 def configure(conf):
     conf.load(['compiler_cxx', 'gnu_dirs',
                'default-compiler-flags', 'boost',
-               'doxygen', 'sphinx_build'])
+               'doxygen', 'sphinx'])
 
     conf.env.WITH_TESTS = conf.options.with_tests