Skip to content

Commit f495190

Browse files
humitosRabbitAlbatrossstsewdCopilot
authored
Raise clear error when mkdocs.yml config file is missing (#12521)
Follow up from user contributed PR at #12378 This implementation follows the same idea that we have implemented in Sphinx: - if `mkdocs.configuration` YAML key is missing, we show: `MkDocs configuration file is missing` - if the file declared in `mkdocs.configuration` doesn't exist, we show: `Expected file not found` (with the path) <img width="1188" height="166" alt="Screenshot_2025-10-28_11-57-09" src="https://github.com/user-attachments/assets/e6409f0f-d23a-413f-90ae-80e672994a98" /> <img width="1195" height="179" alt="Screenshot_2025-10-28_11-57-03" src="https://github.com/user-attachments/assets/41244494-e276-4a39-ad9c-7753207a7622" /> Closes #11937 Closes #12378 --------- Co-authored-by: Abhijeet More <moreabhijeet998@gmail.com> Co-authored-by: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Co-authored-by: Santos Gallegos <stsewd@proton.me> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: humitos <244656+humitos@users.noreply.github.com>
1 parent e61d7a6 commit f495190

File tree

4 files changed

+95
-38
lines changed

4 files changed

+95
-38
lines changed

readthedocs/doc_builder/backends/mkdocs.py

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
import yaml
1111
from django.conf import settings
1212

13-
from readthedocs.core.utils.filesystem import safe_open
1413
from readthedocs.doc_builder.base import BaseBuilder
15-
from readthedocs.projects.constants import MKDOCS
16-
from readthedocs.projects.constants import MKDOCS_HTML
14+
from readthedocs.projects.exceptions import UserFileNotFound
1715

1816

1917
log = structlog.get_logger(__name__)
@@ -44,46 +42,25 @@ def __init__(self, *args, **kwargs):
4442
super().__init__(*args, **kwargs)
4543

4644
# This is the *MkDocs* yaml file
47-
self.yaml_file = self.get_yaml_config()
48-
49-
def get_final_doctype(self):
50-
"""
51-
Select a doctype based on the ``use_directory_urls`` setting.
52-
53-
https://www.mkdocs.org/user-guide/configuration/#use_directory_urls
54-
"""
55-
56-
# TODO: we should eventually remove this method completely and stop
57-
# relying on "loading the `mkdocs.yml` file in a safe way just to know
58-
# if it's a MKDOCS or MKDOCS_HTML documentation type".
59-
60-
# Allow symlinks, but only the ones that resolve inside the base directory.
61-
with safe_open(
62-
self.yaml_file,
63-
"r",
64-
allow_symlinks=True,
65-
base_path=self.project_path,
66-
) as fh:
67-
config = yaml_load_safely(fh)
68-
use_directory_urls = config.get("use_directory_urls", True)
69-
return MKDOCS if use_directory_urls else MKDOCS_HTML
70-
71-
def get_yaml_config(self):
72-
"""Find the ``mkdocs.yml`` file in the project root."""
73-
mkdocs_path = self.config.mkdocs.configuration
74-
if not mkdocs_path:
75-
mkdocs_path = "mkdocs.yml"
76-
return os.path.join(
45+
self.config_file = os.path.join(
7746
self.project_path,
78-
mkdocs_path,
47+
self.config.mkdocs.configuration,
7948
)
8049

8150
def show_conf(self):
8251
"""Show the current ``mkdocs.yaml`` being used."""
52+
if not os.path.exists(self.config_file):
53+
raise UserFileNotFound(
54+
message_id=UserFileNotFound.FILE_NOT_FOUND,
55+
format_values={
56+
"filename": os.path.relpath(self.config_file, self.project_path),
57+
},
58+
)
59+
8360
# Write the mkdocs.yml to the build logs
8461
self.run(
8562
"cat",
86-
os.path.relpath(self.yaml_file, self.project_path),
63+
os.path.relpath(self.config_file, self.project_path),
8764
cwd=self.project_path,
8865
)
8966

@@ -97,8 +74,9 @@ def build(self):
9774
"--site-dir",
9875
os.path.join("$READTHEDOCS_OUTPUT", "html"),
9976
"--config-file",
100-
os.path.relpath(self.yaml_file, self.project_path),
77+
os.path.relpath(self.config_file, self.project_path),
10178
]
79+
10280
if self.config.mkdocs.fail_on_warning:
10381
build_command.append("--strict")
10482
cmd_ret = self.run(

readthedocs/projects/notifications.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
),
104104
Message(
105105
id=RepositoryError.UNSUPPORTED_VCS,
106-
header=_("Repository type not suported"),
106+
header=_("Repository type not supported"),
107107
body=_(
108108
textwrap.dedent(
109109
"""

readthedocs/projects/tests/test_build_tasks.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,14 @@ def test_build_updates_documentation_type(self, load_yaml_config):
302302
)
303303
).touch()
304304

305+
# Create "mkdocs.yml" for the "cat" command to find it
306+
pathlib.Path(
307+
os.path.join(
308+
self.project.checkout_path(self.version.slug),
309+
"mkdocs.yml",
310+
)
311+
).touch()
312+
305313
self._trigger_update_docs_task()
306314

307315
# Update version state
@@ -1413,7 +1421,7 @@ def test_build_commands_executed_with_clone_token(
14131421
os.makedirs(self.project.artifact_path(version=self.version.slug, type_="epub"))
14141422
os.makedirs(self.project.artifact_path(version=self.version.slug, type_="pdf"))
14151423

1416-
get_clone_token.return_value = "toke:1234"
1424+
get_clone_token.return_value = "token:1234"
14171425
github_app_installation = get(
14181426
GitHubAppInstallation,
14191427
installation_id=1234,
@@ -2626,6 +2634,16 @@ def test_mkdocs_fail_on_warning(self, load_yaml_config):
26262634
validate=True,
26272635
)
26282636

2637+
# Create "mkdocs.yaml" for the "cat" command to find it
2638+
os.makedirs(os.path.join(self.project.checkout_path(version=self.version.slug), "docs"))
2639+
pathlib.Path(
2640+
os.path.join(
2641+
self.project.checkout_path(self.version.slug),
2642+
"docs",
2643+
"mkdocs.yaml",
2644+
)
2645+
).touch()
2646+
26292647
self._trigger_update_docs_task()
26302648

26312649
self.mocker.mocks["environment.run"].assert_has_calls(

readthedocs/rtd_tests/tests/test_doc_builder.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
from django.test.utils import override_settings
99

1010
from readthedocs.config.tests.test_config import get_build_config
11+
from readthedocs.doc_builder.backends.mkdocs import BaseMkdocs
1112
from readthedocs.doc_builder.backends.sphinx import BaseSphinx
1213
from readthedocs.doc_builder.python_environments import Virtualenv
1314
from readthedocs.projects.exceptions import ProjectConfigurationError
15+
from readthedocs.projects.exceptions import UserFileNotFound
1416
from readthedocs.projects.models import Project
1517

1618

@@ -112,3 +114,62 @@ def test_multiple_conf_py(
112114
with pytest.raises(ProjectConfigurationError):
113115
with override_settings(DOCROOT=tmp_docs_dir):
114116
base_sphinx.show_conf()
117+
118+
119+
@override_settings(PRODUCTION_DOMAIN="readthedocs.org")
120+
class MkDocsBuilderTest(TestCase):
121+
fixtures = ["test_data", "eric"]
122+
123+
def setUp(self):
124+
self.project = Project.objects.get(slug="pip")
125+
self.version = self.project.versions.first()
126+
127+
self.build_env = mock.MagicMock()
128+
self.build_env.project = self.project
129+
self.build_env.version = self.version
130+
self.build_env.build = {
131+
"id": 123,
132+
}
133+
self.build_env.api_client = mock.MagicMock()
134+
135+
@patch("readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.run")
136+
@patch("readthedocs.projects.models.Project.checkout_path")
137+
@patch("readthedocs.doc_builder.python_environments.load_yaml_config")
138+
def test_project_without_mkdocs_yml(
139+
self,
140+
load_yaml_config,
141+
checkout_path,
142+
_,
143+
):
144+
"""
145+
Test for a project with a missing ``mkdocs.yml`` file.
146+
147+
When ``mkdocs.configuration`` points to a file that doesn't exist,
148+
a ``UserFileNotFound`` error should be raised.
149+
"""
150+
tmp_dir = tempfile.mkdtemp()
151+
checkout_path.return_value = tmp_dir
152+
python_env = Virtualenv(
153+
version=self.version,
154+
build_env=self.build_env,
155+
config=get_build_config(
156+
{"mkdocs": {"configuration": "mkdocs.yml"}},
157+
validate=True,
158+
source_file=f"{tmp_dir}/readthedocs.yml",
159+
),
160+
)
161+
base_mkdocs = BaseMkdocs(
162+
build_env=self.build_env,
163+
python_env=python_env,
164+
)
165+
with self.assertRaises(UserFileNotFound) as e:
166+
base_mkdocs.show_conf()
167+
168+
self.assertEqual(
169+
e.exception.message_id,
170+
UserFileNotFound.FILE_NOT_FOUND,
171+
)
172+
self.assertEqual(
173+
e.exception.format_values.get("filename"),
174+
"mkdocs.yml",
175+
)

0 commit comments

Comments
 (0)