# Life of a Sphinx build {#life-of-a-sphinx-build}

## Methodology {#methodology}

```
git clone https://github.com/kaycebasques/experiments.git`
cd experiments/20260930
./bazelisk run :sphinx.run
```

should see:

```
Starting local Bazel server (9.2.0) and connecting to it...
INFO: Analyzed target //:sphinx.run (140 packages loaded, 5710 targets configured).
INFO: Found 1 target...
Target //:sphinx.run up-to-date:
  bazel-bin/sphinx.run
INFO: Elapsed time: 7.654s, Critical Path: 0.30s
INFO: 3 processes: 15 action cache hit, 3 internal.
INFO: Build completed successfully, 3 total actions
INFO: Running command line: bazel-bin/sphinx.run
+ exec env -- bin/build --show-traceback --builder=html --fail-on-warning _sphinx/_sources /tmp/sphinx-out
> /home/kayce/.cache/bazel/_bazel_kayce/a88c0074b8ac7102a27d3a3445d1ab50/execroot/_main/bazel-out/k8-fastbuild/bin/sphinx.run.runfiles/_main/build.py(70)<module>()
-> main()
(Pdb)
```

press `s` then `Enter`

```
--Call--
> /home/kayce/.cache/bazel/_bazel_kayce/a88c0074b8ac7102a27d3a3445d1ab50/execroot/_main/bazel-out/k8-fastbuild/bin/sphinx.run.runfiles/rules_python++pip+pypi_312_sphinx/site-packages/sphinx/cmd/build.py(475)main()
> https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475
-> def main(argv: Sequence[str] = (), /) -> int:
(Pdb)
```

note the url to the source code
(https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475)

## Walkthrough {#walkthrough}

as expected, we start in the `main` function of [sphinx-build](https://www.sphinx-doc.org/en/master/man/sphinx-build.html)

the very first thing that happens is setting a locale

https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475```python
def main(argv: Sequence[str] = (), /) -> int:
    locale.setlocale(locale.LC_ALL, '')
    sphinx.locale.init_console()

    if not argv:
        argv = sys.argv[1:]

    # Allow calling as 'python -m sphinx build …'
    if argv[:1] == ['build']:
        argv = argv[1:]

    if argv[:1] == ['--bug-report']:
        return _bug_report_info()
    if argv[:1] == ['-M']:
        from sphinx.cmd import make_mode

        return make_mode.run_make_mode(argv[1:])
    else:
        return build_main(argv)
```

this `setlocale` function comes from cpython. the `if` block gets skipped,
so we go straight to the `return`. they do something weird with
`_setlocale`. it seems like it’s defined as `setlocale` which seems like an
infinite loop

https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475```python
def setlocale(category, locale=None):

    """ Set the locale for the given category.  The locale can be
        a string, an iterable of two strings (language code and encoding),
        or None.

        Iterables are converted to strings using the locale aliasing
        engine.  Locale strings are passed directly to the C lib.

        category may be given as one of the LC_* values.

    """
    if locale and not isinstance(locale, _builtin_str):
        # convert to string
        locale = normalize(_build_localename(locale))
    return _setlocale(category, locale)
```

we return to the `main` function of `sphinx-build`. now it’s time to enter
`init_console`

https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475```python
def main(argv: Sequence[str] = (), /) -> int:
    locale.setlocale(locale.LC_ALL, '')
    sphinx.locale.init_console()

    if not argv:
        argv = sys.argv[1:]

    # Allow calling as 'python -m sphinx build …'
    if argv[:1] == ['build']:
        argv = argv[1:]

    if argv[:1] == ['--bug-report']:
        return _bug_report_info()
    if argv[:1] == ['-M']:
        from sphinx.cmd import make_mode

        return make_mode.run_make_mode(argv[1:])
    else:
        return build_main(argv)
```

our minimal `conf.py` doesn’t define a locale dir so sphinx sets one for us:

https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475```python
def init_console(
    locale_dir: str | os.PathLike[str] | None = None,
    catalog: str = 'sphinx',
) -> tuple[NullTranslations, bool]:
    """Initialize locale for console.

    .. versionadded:: 1.8
    """
    if locale_dir is None:
        locale_dir = _LOCALE_DIR
    if sys.platform == 'win32':
        language = None
    else:
        try:
            # encoding is ignored
            language, _ = locale.getlocale(locale.LC_MESSAGES)
        except AttributeError:
            # Fallback to the default language in case LC_MESSAGES is not defined.
            language = None
    return init([locale_dir], language, catalog, 'console')
```

in my case `local_dir` gets set to:

```
PosixPath('/home/kayce/.cache/bazel/_bazel_kayce/a88c0074b8ac7102a27d3a3445d1ab50/external/rules_python++pip+pypi_312_sphinx/site-packages/sphinx/locale')
```

my platform is linux x86 (or amd64 or whatever we call it) so the next code
to execute is the `try` block:

https://github.com/sphinx-doc/sphinx/blob/v9.1.0/sphinx/cmd/build.py#L475```python
def init_console(
    locale_dir: str | os.PathLike[str] | None = None,
    catalog: str = 'sphinx',
) -> tuple[NullTranslations, bool]:
    """Initialize locale for console.

    .. versionadded:: 1.8
    """
    if locale_dir is None:
        locale_dir = _LOCALE_DIR
    if sys.platform == 'win32':
        language = None
    else:
        try:
            # encoding is ignored
            language, _ = locale.getlocale(locale.LC_MESSAGES)
        except AttributeError:
            # Fallback to the default language in case LC_MESSAGES is not defined.
            language = None
    return init([locale_dir], language, catalog, 'console')
```

so we enter `getlocale`

(I still don’t understand why we don’t step into `_setlocale`)

`category` is set to `5` in my case which seems to equal `LC_MESSAGES`
so the `if category == LC_ALL` check is `False` and we proceed to
`_parse_localname`

https://github.com/python/cpython/blob/v3.12.12/Lib/locale.py#L582```python
def getlocale(category=LC_CTYPE):

    """ Returns the current setting for the given locale category as
        tuple (language code, encoding).

        category may be one of the LC_* value except LC_ALL. It
        defaults to LC_CTYPE.

        Except for the code 'C', the language code corresponds to RFC
        1766.  code and encoding can be None in case the values cannot
        be determined.

    """
    localename = _setlocale(category)
    if category == LC_ALL and ';' in localename:
        raise TypeError('category LC_ALL is not supported')
    return _parse_localename(localename)
```
