Repository: sphinx-doc/sphinx

Issue:
Documentation for <packages> field in latex.tex_t
**Problem description leading to this feature request**
When using `make latexpdf` (or any latex-related), it may be interesting to add extra packages to the preamble.

However, when adding packages via the `preamble` field of the `latex_elements`, they are added after the `hyperref` package.

This may lead to unwanted results with the 
```
\usepackage[makeindex]{imakeidx}
\usepackage[columns=1]{idxlayout}
```
for instance. Indeed, these commands will be added after the `hyperref` package, resulting in an incomplete `.ind` file (instead of `\hyperpage{9}` one would only get `9`).

**Is their a workaround? Yes.**
The workaround I use for the moment is to add
```
\usepackage[makeindex]{imakeidx}
\usepackage[columns=1]{idxlayout}
```
in the `passoptionstopackages` field of `latex_elements`.

**Proposed solution**
I looked at [https://github.com/sphinx-doc/sphinx/blob/master/sphinx/templates/latex/latex.tex_t](url), which is the template file I supposed is used by Sphinx. There is (lines 39-45)
```
<%- for name, option in packages %>
<%- if option %>
\usepackage[<%= option %>]{<%= name %>}
<%- else %>
\usepackage{<%= name %>}
<%- endif %>
<%- endfor %>
```
so I tried the key `packages` in the `latex_elements`, resulting in:
> WARNING: Unknown configure key: latex_elements['packages']. ignored.
Solution:
1) Maybe I miss something and it is possible to add packages before `hyperref` with others means (but after reading [https://www.sphinx-doc.org/en/master/latex.html#latex-macros-and-environments](url) I could not find any)
2) Provide a `packages` field that inserts packages where `<%- for name, option in packages %>` is located in the template. That would be great !

**The last word**
Anyway, Sphinx is an amazing tool !


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=h1) Report
> Merging [#6775](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=desc) into [2.0](https://codecov.io/gh/sphinx-doc/sphinx/commit/ec8d41b23780f4cb7302ab5f012a607d238db3d5?src=pr&el=desc) will **decrease** coverage by `<.01%`.
> The diff coverage is `37.5%`.

[![Impacted file tree graph](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775/graphs/tree.svg?width=650&token=VFlFSELbkw&height=150&src=pr)](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              2.0    #6775      +/-   ##
==========================================
- Coverage   83.75%   83.74%   -0.01%     
==========================================
  Files         269      269              
  Lines       41116    41123       +7     
  Branches     6007     6009       +2     
==========================================
+ Hits        34436    34438       +2     
- Misses       5352     5356       +4     
- Partials     1328     1329       +1
```


| [Impacted Files](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sphinx/writers/latex.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775/diff?src=pr&el=tree#diff-c3BoaW54L3dyaXRlcnMvbGF0ZXgucHk=) | `81.93% <ø> (ø)` | :arrow_up: |
| [tests/test\_build\_latex.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9idWlsZF9sYXRleC5weQ==) | `95.39% <100%> (ø)` | :arrow_up: |
| [sphinx/builders/latex/\_\_init\_\_.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775/diff?src=pr&el=tree#diff-c3BoaW54L2J1aWxkZXJzL2xhdGV4L19faW5pdF9fLnB5) | `79.92% <28.57%> (-1.45%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=footer). Last update [ec8d41b...52d9111](https://codecov.io/gh/sphinx-doc/sphinx/pull/6775?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jfbu: > I wonder if it will not be simpler rather than overloading the current `'packages'` which gathers its contents from extensions to add a `extrapackages` key for the latex template ...

An advantage a new `latex_elements` key is that in the rare case its insertion location needs to be changed this can be achieved via user modifying latex template. This would cater for fringe case where for some reason the user extra packages must be loaded prior to hyperref (hence `'preamble'` does not fit) but after some package loaded by a Sphinx extension.

(the user could write an own extension simply to insert packages via API, but problem is that loading order relative to packages from other extensions is perhaps not easy to control robustly?)

This PR means `'packages'` key (not meant originally for user) functionality is extended but it does not make easy to specify relative location to LaTeX packages loaded via Sphinx extensions. The approach via a `extrapackages` new key would at least make this possible for user, simply by overriding LaTeX templates. (not to mention adding even one more key `'latepackages'`... which also has the problem that "late" is confusingly similar to "latex": `'lateextrapackages'`)

tk0miya: I think adding new template variable is not good way. As you said, additional variable like `late-packages` (or `extrapackage-after-extensions'` will be requested in future.

To control the order of LaTeX packages completely, it would be better to add priority to each package. And then, users can insert packages to arbitrary position::
```
latex_elements = {
    'extrapackages': [
        ('some_package', 'option', 10),   # high priority, loaded before hyperref
        ('another_pacakge', None, 200),    # lower priority, loaded after hyperref 
    ]
}
```

On the other hand, we don't get a report around the LaTeX packages added by extension. So this idea might be too much.
jfbu: > On the other hand, we don't get a report around the LaTeX packages added by extension. So this idea might be too much.

I believe so, too. And it is a bit unfamiliar to LaTeX world to use such "priorities". I think since it is possible for user to use own latex template, question of control of where to load a package is not an urgent one. Also  is is not easy to fix satisfactorily because perhaps complete solution means externalizing out of `sphinx.sty` all package loading to a `sphinxpackages.sty` to allow user to insert a needed own package at a precise location in a custom `sphinxpackages.sty`. (I don't say this is probable, nobody asks something like this so far...)

I feel it is simpler to have an `'extrapackages'` key for `latex_elements` with a string value directly containing various user configured `\usepackage[options]{package}`. This accommodates that for some packages usage of some `\packagenamesetup{...}`  might be better for option passing than the optional argument to `\usepackage` : for some packages usage of a "setup" macro is mandatory for some options.

Allowing such syntax would not be compatible with common treatment of ``'packages'`` key and thus reinforces legitimacy of new separate key. I think ``'packages'`` key with its Python list syntax for options has the limitation mentioned in previous paragraph and this limitation is propagated in this PR...

No strong opinion, though!


tk0miya: Thank you for comment. I agree your idea; to take mere string value through `extrapackages` key. I'll update this soon.

Patch:
diff --git a/CHANGES b/CHANGES
index 5497d94a2..6473ed44d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,8 @@ Features added
 * #267: html: Eliminate prompt characters of doctest block from copyable text
 * #6729: html theme: agogo theme now supports ``rightsidebar`` option
 * #6780: Add PEP-561 Support
+* #6762: latex: Allow to load additonal LaTeX packages via ``extrapackages`` key
+  of :confval:`latex_elements`
 
 Bugs fixed
 ----------
diff --git a/doc/latex.rst b/doc/latex.rst
index 2501d1594..7321f964b 100644
--- a/doc/latex.rst
+++ b/doc/latex.rst
@@ -226,6 +226,25 @@ into the generated ``.tex`` files.  Its ``'sphinxsetup'`` key is described
 
      .. versionadded:: 1.5
 
+  ``'extrapackages'``
+     Additional LaTeX packages.  For example:
+
+     .. code-block:: python
+
+         latex_elements = {
+             'packages': r'\usepackage{isodate}'
+         }
+
+     It defaults to empty.
+
+     The specified LaTeX packages will be loaded before
+     hyperref package and packages loaded from Sphinx extensions.
+
+     .. hint:: If you'd like to load additional LaTeX packages after hyperref, use
+               ``'preamble'`` key instead.
+
+     .. versionadded:: 2.3
+
   ``'footer'``
      Additional footer content (before the indices), default empty.
 
diff --git a/sphinx/templates/latex/latex.tex_t b/sphinx/templates/latex/latex.tex_t
index 2beac82c9..a0a5a26b1 100644
--- a/sphinx/templates/latex/latex.tex_t
+++ b/sphinx/templates/latex/latex.tex_t
@@ -35,6 +35,7 @@
 <%= sphinxsetup %>
 <%= fvset %>
 <%= geometry %>
+<%= extrapackages %>
 
 <%- for name, option in packages %>
 <%- if option %>
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 832d34dfa..364067497 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -155,6 +155,7 @@
                         '% Set up styles of URL: it should be placed after hyperref.\n'
                         '\\urlstyle{same}'),
     'contentsname':    '',
+    'extrapackages':   '',
     'preamble':        '',
     'title':           '',
     'release':         '',


Test patch:
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index 8410bbd03..c48399f1b 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -1437,3 +1437,11 @@ def test_index_on_title(app, status, warning):
             '\\label{\\detokenize{contents:test-for-index-in-top-level-title}}'
             '\\index{index@\\spxentry{index}}\n'
             in result)
+
+
+@pytest.mark.sphinx('latex', testroot='basic',
+                    confoverrides={'latex_elements': {'extrapackages': r'\usepackage{foo}'}})
+def test_latex_elements_extrapackages(app, status, warning):
+    app.builder.build_all()
+    result = (app.outdir / 'test.tex').text()
+    assert r'\usepackage{foo}' in result