docs: def make_app(**kwargs):
      """
      Create a dummy Sphinx app, filling in various hardcoded assumptions.
  
      For example, Sphinx assumes the existence of various source/dest
      directories, even if you're only calling internals that never generate (or
      sometimes, even read!) on-disk files. This function creates safe temp
      directories for these instances.
  
      It also neuters Sphinx's internal logging, which otherwise causes verbosity
      in one's own test output and/or debug logs.
  
      Finally, it does load the given srcdir's ``conf.py``, but only to read
      specific bits like ``extensions`` (if requested); most of it is ignored.
  
      All args are stored in a single ``**kwargs``. Aside from the params listed
      below (all of which are optional), all kwargs given are turned into
      'releases_xxx' config settings; e.g. ``make_app(foo='bar')`` is like
      setting ``releases_foo = 'bar'`` in ``conf.py``.
  
      :param str docname:
          Override the document name used (mostly for internal testing).
  
      :param str srcdir:
          Sphinx source directory path.
  
      :param str dstdir:
          Sphinx dest directory path.
  
      :param str doctreedir:
          Sphinx doctree directory path.
  
      :param bool load_extensions:
          Whether to load the real ``conf.py`` and setup any extensions it
          configures. Default: ``False``.
  
      :returns: A Sphinx ``Application`` instance.
  
      .. versionchanged:: 1.6
          Added the ``load_extensions`` kwarg.
      """
      srcdir = kwargs.pop('srcdir', mkdtemp())
      dstdir = kwargs.pop('dstdir', mkdtemp())
      doctreedir = kwargs.pop('doctreedir', mkdtemp())
      load_extensions = kwargs.pop('load_extensions', False)
      real_conf = None
      try:
          # Sphinx <1.6ish
          Sphinx._log = lambda self, message, wfile, nonl=False: None
          # Sphinx >=1.6ish. Technically still lets Very Bad Things through,
          # unlike the total muting above, but probably OK.
          # NOTE: used to just do 'sphinx' but that stopped working, even on
          # sphinx 1.6.x. Weird. Unsure why hierarchy not functioning.
          for name in ('sphinx', 'sphinx.sphinx.application'):
              logging.getLogger(name).setLevel(logging.ERROR)
          # App API seems to work on all versions so far.
          app = Sphinx(
              srcdir=srcdir,
              confdir=None,
              outdir=dstdir,
              doctreedir=doctreedir,
              buildername='html',
          )
          # Might as well load the conf file here too.
          if load_extensions:
              real_conf = load_conf(srcdir)
      finally:
          for d in (srcdir, dstdir, doctreedir):
              # Only remove empty dirs; non-empty dirs are implicitly something
              # that existed before we ran, and should not be touched.
              try:
                  os.rmdir(d)
              except OSError:
                  pass
      setup(app)
      # Mock out the config within. More assumptions by Sphinx :(
      # TODO: just use real config and overlay what truly needs changing? is that
      # feasible given the rest of the weird ordering we have to do? If it is,
      # maybe just literally slap this over the return value of load_conf()...
      config = {
          'releases_release_uri': 'foo_%s',
          'releases_issue_uri': 'bar_%s',
          'releases_debug': False,
          'master_doc': 'index',
      }
      # Allow tinkering with document filename
      if 'docname' in kwargs:
          app.env.temp_data['docname'] = kwargs.pop('docname')
      # Allow config overrides via kwargs
      for name in kwargs:
          config['releases_{}'.format(name)] = kwargs[name]
      # Stitch together as the sphinx app init() usually does w/ real conf files
      app.config._raw_config = config
      # init_values() requires a 'warn' runner on Sphinx 1.3-1.6, so if we seem
      # to be hitting arity errors, give it a dummy such callable. Hopefully
      # calling twice doesn't introduce any wacko state issues :(
      try:
          app.config.init_values()
      except TypeError: # boy I wish Python had an ArityError or w/e
          app.config.init_values(lambda x: x)
      # Initialize extensions (the internal call to this happens at init time,
      # which of course had no valid config yet here...)
      if load_extensions:
          for extension in real_conf.get('extensions', []):
              # But don't set up ourselves again, that causes errors
              if extension == 'releases':
                  continue
              app.setup_extension(extension)
      return app