Repository: sphinx-doc/sphinx

Issue:
Warning about duplicate definitions in intersphinx mapping
### Describe the bug

We recently started getting warnings about duplicate definition in intersphinx, from two upstream projects:

- sklearn, see https://github.com/scikit-learn/scikit-learn/issues/29337
- ipywidgets, see https://github.com/jupyter-widgets/ipywidgets/issues/3930

Sklearn maintainers suspect this to be a sphinx bug, so I am opening this issue here.

### How to Reproduce
```
$ pip list | grep  Sphinx
Sphinx                        7.4.2
$ python -m sphinx.ext.intersphinx https://scikit-learn.org/stable/objects.inv | grep "    y "
WARNING:sphinx.sphinx.util.inventory:inventory <> contains multiple definitions for std:term:y
    y        
```

and
```
$ python -m sphinx.ext.intersphinx https://ipywidgets.readthedocs.io/en/stable/objects.inv | grep 'Widget Layout.ipynb#display'
WARNING:sphinx.sphinx.util.inventory:inventory <> contains multiple definitions for std:label:examples/Widget Layout.ipynb#display
    examples/Widget Layout.ipynb#display     display                                 : examples/Widget%20Layout.html#id1
```

### Environment Information

```text
Sphinx 7.4.2
```


### Sphinx extensions

```python
intersphinx
```


### Additional context

_No response_

Discussion:
jayaddison: @AA-Turner a note to let you know that I'm holding this change in reserve - it is about Intersphinx _resolution_ -- e.g. occurs when people attempt to include links to potential ambiguity in external projects from their own.

This should happen far less often than ambiguity-detection at inventory loading-time, and I think is a more serious scenario (because it could result in incorrect hyperlinks).  Even so, the pure-duplicate case might make sense to reduce log-severity for here too.
jayaddison: (I'm not sure that I explained that particularly clearly, so let me know if I can clarify.  there are a few similar-seeming changes in-flight here, and I regret that because I could have planned this rollout better to reduce the chance of confusion)
jayaddison: Following the principle of "who is this warning actionable for" (ref [here](https://github.com/sphinx-doc/sphinx/issues/12613#issuecomment-2236846759)), I don't think that either the referrer or the referee are likely to want or need to do anything about ambiguities that are effectively duplicate entries.  It's potentially useful to be aware of these cases, but not something that should block strict-mode builds.
jayaddison: NB: This has no merge conflicts, but v7.4.6 has already been released, so the `CHANGES.rst` entry's placement is incorrect.
jayaddison: > NB: This has no merge conflicts, but v7.4.6 has already been released, so the `CHANGES.rst` entry's placement is incorrect.

(this is now resolved, so I've removed the do-not-merge label from this pull request)
AA-Turner: Does this address a regression in 7.4? Otherwise I will hold off until 7.5 // 8.0.

A
jayaddison: > Does this address a regression in 7.4? Otherwise I will hold off until 7.5 // 8.0.

It does not, no - and that sounds fine to me, thank you :+1: 

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 99fc230cd..b3fb5042b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,9 @@ Deprecated
 Features added
 --------------
 
+* #11328: Mention evaluation of templated content during production of static
+  output files.
+
 Bugs fixed
 ----------
 
@@ -29,6 +32,10 @@ Bugs fixed
   so that it now runs after the docutils ``Footnotes`` resolution transform.
   Patch by Chris Sewell.
 
+* #12587: Do not warn when potential ambiguity detected during Intersphinx
+  resolution occurs due to duplicate targets that differ case-insensitively.
+  Patch by James Addison.
+
 Testing
 -------
 
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 19079d2cf..c32d7a1c4 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -96,138 +96,156 @@ The ``toctree`` directive is the central element.
    You can also add external links, by giving an HTTP URL instead of a document
    name.
 
-   **Section numbering**
+   The special entry name ``self`` stands for the document containing the
+   toctree directive.  This is useful if you want to generate a "sitemap" from
+   the toctree.
 
-   If you want to have section numbers even in HTML output, give the
-   **toplevel** toctree a ``numbered`` option.  For example::
+   In the end, all documents in the :term:`source directory` (or subdirectories)
+   must occur in some ``toctree`` directive; Sphinx will emit a warning if it
+   finds a file that is not included, because that means that this file will not
+   be reachable through standard navigation.
 
-      .. toctree::
-         :numbered:
+   Use :confval:`exclude_patterns` to explicitly exclude documents or
+   directories from building completely.  Use :ref:`the "orphan" metadata
+   <metadata>` to let a document be built, but notify Sphinx that it is not
+   reachable via a toctree.
 
-         foo
-         bar
+   The "root document" (selected by :confval:`root_doc`) is the "root" of the TOC
+   tree hierarchy.  It can be used as the documentation's main page, or as a
+   "full table of contents" if you don't give a ``:maxdepth:`` option.
 
-   Numbering then starts at the heading of ``foo``.  Sub-toctrees are
-   automatically numbered (don't give the ``numbered`` flag to those).
+   .. versionchanged:: 0.6
+      Added support for external links and "self" references.
 
-   Numbering up to a specific depth is also possible, by giving the depth as a
-   numeric argument to ``numbered``.
+   .. rubric:: options
 
-   **Additional options**
+   .. rst:directive:option:: class: class names
+      :type: a list of class names, separated by spaces
 
-   You can use the ``caption`` option to provide a toctree caption and you can
-   use the ``name`` option to provide an implicit target name that can be
-   referenced by using :rst:role:`ref`::
+      Assign `class attributes`_.
+      This is a :dudir:`common option <common-options>`.
+      For example::
 
-      .. toctree::
-         :caption: Table of Contents
-         :name: mastertoc
+          .. toctree::
+             :class: custom-toc
 
-         foo
+      .. _class attributes: https://docutils.sourceforge.io/docs/ref/doctree.html#classes
 
-   As with :dudir:`most directives <common-options>`,
-   you can use the ``class`` option to assign `class attributes`_::
+      .. versionadded:: 7.4
 
-      .. toctree::
-         :class: custom-toc
+   .. rst:directive:option:: name: label
+      :type: text
 
-   .. _class attributes: https://docutils.sourceforge.io/docs/ref/doctree.html#classes
+      An implicit target name that can be referenced using :rst:role:`ref`.
+      This is a :dudir:`common option <common-options>`.
+      For example::
 
-   If you want only the titles of documents in the tree to show up, not other
-   headings of the same level, you can use the ``titlesonly`` option::
+          .. toctree::
+             :name: mastertoc
 
-      .. toctree::
-         :titlesonly:
+             foo
 
-         foo
-         bar
+      .. versionadded:: 1.3
 
-   You can use "globbing" in toctree directives, by giving the ``glob`` flag
-   option.  All entries are then matched against the list of available
-   documents, and matches are inserted into the list alphabetically.  Example::
+   .. rst:directive:option:: caption
+      :type: text
 
-      .. toctree::
-         :glob:
+      Add a caption to the toctree.
+      For example::
 
-         intro*
-         recipe/*
-         *
+          .. toctree::
+             :caption: Table of Contents
 
-   This includes first all documents whose names start with ``intro``, then all
-   documents in the ``recipe`` folder, then all remaining documents (except the
-   one containing the directive, of course.) [#]_
+              foo
 
-   The special entry name ``self`` stands for the document containing the
-   toctree directive.  This is useful if you want to generate a "sitemap" from
-   the toctree.
+      .. versionadded:: 1.3
 
-   You can use the ``reversed`` flag option to reverse the order of the entries
-   in the list. This can be useful when using the ``glob`` flag option to
-   reverse the ordering of the files.  Example::
+   .. rst:directive:option:: numbered
+                             numbered: depth
 
-      .. toctree::
-         :glob:
-         :reversed:
+      If you want to have section numbers even in HTML output,
+      add the ``:numbered:`` option to the *top-level* toctree.
+      For example::
 
-         recipe/*
+         .. toctree::
+            :numbered:
 
-   You can also give a "hidden" option to the directive, like this::
+            foo
+            bar
 
-      .. toctree::
-         :hidden:
+      Section numbering then starts at the heading of ``foo``.
+      Sub-toctrees are automatically numbered
+      (don't give the ``numbered`` flag to those).
 
-         doc_1
-         doc_2
+      Numbering up to a specific depth is also possible,
+      by giving the depth as a numeric argument to ``numbered``.
 
-   This will still notify Sphinx of the document hierarchy, but not insert links
-   into the document at the location of the directive -- this makes sense if you
-   intend to insert these links yourself, in a different style, or in the HTML
-   sidebar.
+      .. versionadded:: 0.6
 
-   In cases where you want to have only one top-level toctree and hide all other
-   lower level toctrees you can add the "includehidden" option to the top-level
-   toctree entry::
+      .. versionchanged:: 1.1
+         Added the numeric *depth* argument.
 
-      .. toctree::
-         :includehidden:
+   .. rst:directive:option:: titlesonly
 
-         doc_1
-         doc_2
+      Only list document titles, not other headings of the same level.
+      For example::
 
-   All other toctree entries can then be eliminated by the "hidden" option.
+          .. toctree::
+             :titlesonly:
 
-   In the end, all documents in the :term:`source directory` (or subdirectories)
-   must occur in some ``toctree`` directive; Sphinx will emit a warning if it
-   finds a file that is not included, because that means that this file will not
-   be reachable through standard navigation.
+             foo
+             bar
 
-   Use :confval:`exclude_patterns` to explicitly exclude documents or
-   directories from building completely.  Use :ref:`the "orphan" metadata
-   <metadata>` to let a document be built, but notify Sphinx that it is not
-   reachable via a toctree.
+      .. versionadded:: 1.0
 
-   The "root document" (selected by :confval:`root_doc`) is the "root" of the TOC
-   tree hierarchy.  It can be used as the documentation's main page, or as a
-   "full table of contents" if you don't give a ``maxdepth`` option.
+   .. rst:directive:option:: glob
 
-   .. versionchanged:: 0.3
-      Added "globbing" option.
+      Parse glob wildcards in toctree entries.
+      All entries are matched against the list of available documents,
+      and matches are inserted into the list alphabetically.
+      For example::
 
-   .. versionchanged:: 0.6
-      Added "numbered" and "hidden" options as well as external links and
-      support for "self" references.
+          .. toctree::
+             :glob:
 
-   .. versionchanged:: 1.0
-      Added "titlesonly" option.
+             intro*
+             recipe/*
+             *
 
-   .. versionchanged:: 1.1
-      Added numeric argument to "numbered".
+      This includes first all documents whose names start with ``intro``,
+      then all documents in the ``recipe`` folder, then all remaining documents
+      (except the one containing the directive, of course.) [#]_
 
-   .. versionchanged:: 1.2
-      Added "includehidden" option.
+      .. versionadded:: 0.3
+
+   .. rst:directive:option:: reversed
+
+      Reverse the order of the entries in the list.
+      This is particularly useful when using the ``:glob:`` option.
+
+      .. versionadded:: 1.5
+
+   .. rst:directive:option:: hidden
+
+      A hidden toctree only defines the document hierarchy.
+      It will not insert links into the document at the location of the directive.
+
+      This makes sense if you have other means of navigation,
+      e.g. through manual links, HTML sidebar navigation,
+      or if you use the ``:includehidden:`` option on the top-level toctree.
+
+      .. versionadded:: 0.6
+
+   .. rst:directive:option:: includehidden
+
+      If you want one global table of contents showing the complete document structure,
+      you can add the ``:includehidden:`` option to the *top-level* toctree directive.
+      All other toctrees on child pages can then be made invisible
+      with the ``:hidden:`` option.
+      The top-level toctree with ``:includehidden:`` will then include their entries.
+
+      .. versionadded:: 1.2
 
-   .. versionchanged:: 1.3
-      Added "caption" and "name" option.
 
 Special names
 ^^^^^^^^^^^^^
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 78b9acccc..2fa5f360a 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -908,7 +908,7 @@ def copy_html_favicon(self) -> None:
 
     def copy_static_files(self) -> None:
         try:
-            with progress_message(__('copying static files')):
+            with progress_message(__('copying static files'), nonl=False):
                 ensuredir(self.outdir / '_static')
 
                 # prepare context for templates
@@ -929,7 +929,7 @@ def copy_static_files(self) -> None:
     def copy_extra_files(self) -> None:
         """Copy html_extra_path files."""
         try:
-            with progress_message(__('copying extra files')):
+            with progress_message(__('copying extra files'), nonl=False):
                 excluded = Matcher(self.config.exclude_patterns)
                 for extra_path in self.config.html_extra_path:
                     copy_asset(
diff --git a/sphinx/ext/intersphinx/_resolve.py b/sphinx/ext/intersphinx/_resolve.py
index 730385a73..35a8c12bc 100644
--- a/sphinx/ext/intersphinx/_resolve.py
+++ b/sphinx/ext/intersphinx/_resolve.py
@@ -82,10 +82,16 @@ def _resolve_reference_in_domain_by_target(
             insensitive_matches = list(filter(lambda k: k.lower() == target_lower,
                                               inventory[objtype].keys()))
             if len(insensitive_matches) > 1:
+                data_items = {inventory[objtype][match] for match in insensitive_matches}
                 inv_descriptor = inv_name or 'main_inventory'
-                LOGGER.warning(__("inventory '%s': multiple matches found for %s:%s"),
-                               inv_descriptor, objtype, target,
-                               type='intersphinx',  subtype='external', location=node)
+                if len(data_items) == 1:  # these are duplicates; relatively innocuous
+                    LOGGER.debug(__("inventory '%s': duplicate matches found for %s:%s"),
+                                 inv_descriptor, objtype, target,
+                                 type='intersphinx',  subtype='external', location=node)
+                else:
+                    LOGGER.warning(__("inventory '%s': multiple matches found for %s:%s"),
+                                   inv_descriptor, objtype, target,
+                                   type='intersphinx',  subtype='external', location=node)
             if insensitive_matches:
                 data = inventory[objtype][insensitive_matches[0]]
             else:
diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py
index 1996a7af8..259a2af19 100644
--- a/sphinx/util/fileutil.py
+++ b/sphinx/util/fileutil.py
@@ -81,6 +81,9 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLi
 
         destination = _template_basename(destination) or destination
         with open(destination, 'w', encoding='utf-8') as fdst:
+            msg = __('Writing evaluated template result to %s')
+            logger.info(msg, os.fsdecode(destination), type='misc',
+                        subtype='template_evaluation')
             fdst.write(rendered_template)
     else:
         copyfile(source, destination, force=force)


Test patch:
diff --git a/tests/test_extensions/test_ext_intersphinx.py b/tests/test_extensions/test_ext_intersphinx.py
index 4620964d4..e4b2c0d2f 100644
--- a/tests/test_extensions/test_ext_intersphinx.py
+++ b/tests/test_extensions/test_ext_intersphinx.py
@@ -313,7 +313,14 @@ def test_missing_reference_stddomain(tmp_path, app):
     assert rn.astext() == 'The Julia Domain'
 
 
-def test_ambiguous_reference_warning(tmp_path, app):
+@pytest.mark.parametrize(
+    ('term', 'expected_ambiguity'),
+    [
+        ('A TERM', False),
+        ('B TERM', True),
+    ],
+)
+def test_ambiguous_reference_handling(term, expected_ambiguity, tmp_path, app, warning):
     inv_file = tmp_path / 'inventory'
     inv_file.write_bytes(INVENTORY_V2_AMBIGUOUS_TERMS)
     set_config(
@@ -328,10 +335,11 @@ def test_ambiguous_reference_warning(tmp_path, app):
     load_mappings(app)
 
     # term reference (case insensitive)
-    node, contnode = fake_node('std', 'term', 'A TERM', 'A TERM')
+    node, contnode = fake_node('std', 'term', term, term)
     missing_reference(app, app.env, node, contnode)
 
-    assert 'multiple matches found for std:term:A TERM' in app.warning.getvalue()
+    ambiguity = f'multiple matches found for std:term:{term}' in warning.getvalue()
+    assert ambiguity is expected_ambiguity
 
 
 @pytest.mark.sphinx('html', testroot='ext-intersphinx-cppdomain')
diff --git a/tests/test_util/test_util_fileutil.py b/tests/test_util/test_util_fileutil.py
index 76c62b0cf..33633a8b0 100644
--- a/tests/test_util/test_util_fileutil.py
+++ b/tests/test_util/test_util_fileutil.py
@@ -1,5 +1,6 @@
 """Tests sphinx.util.fileutil functions."""
 
+import re
 from unittest import mock
 
 import pytest
@@ -119,6 +120,15 @@ def excluded(path):
     assert not (destdir / '_templates' / 'sidebar.html').exists()
 
 
+@pytest.mark.sphinx('html', testroot='html_assets')
+def test_copy_asset_template(app):
+    app.build(force_all=True)
+
+    expected_msg = r'^Writing evaluated template result to [^\n]*\bAPI.html$'
+    output = strip_colors(app.status.getvalue())
+    assert re.findall(expected_msg, output, flags=re.MULTILINE)
+
+
 @pytest.mark.sphinx('html', testroot='util-copyasset_overwrite')
 def test_copy_asset_overwrite(app):
     app.build()