From 1c5b771feb55ef2a5eb67690e7c40c7e83be6006 Mon Sep 17 00:00:00 2001 From: Abhijeet More Date: Mon, 4 Aug 2025 21:49:55 +0530 Subject: [PATCH 01/12] fix(build): raise clear error if mkdocs.yml config file is missing --- readthedocs/doc_builder/backends/mkdocs.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 36f2e1e5d6f..2a7212ca71c 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -14,6 +14,8 @@ from readthedocs.doc_builder.base import BaseBuilder from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML +from readthedocs.doc_builder.exceptions import BuildUserError + log = structlog.get_logger(__name__) @@ -99,6 +101,13 @@ def build(self): "--config-file", os.path.relpath(self.yaml_file, self.project_path), ] + + if not os.path.exists(self.yaml_file): + raise BuildUserError( + f"MkDocs configuration file is missing — we could not find a 'mkdocs.yml' file at {self.yaml_file}. " + "Please make sure your repository includes one and try again." + ) + if self.config.mkdocs.fail_on_warning: build_command.append("--strict") cmd_ret = self.run( From 364220e18877fd21b0a5555d759d3ffdea9026eb Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:19:29 +0530 Subject: [PATCH 02/12] fix import order to satisfy Ruff --- readthedocs/doc_builder/backends/mkdocs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 2a7212ca71c..52183e40c63 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -5,16 +5,15 @@ """ import os - import structlog -import yaml from django.conf import settings from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder +from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML -from readthedocs.doc_builder.exceptions import BuildUserError + From 16fea11f18193d9a61099c58bae00978d16ae077 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:47:30 +0530 Subject: [PATCH 03/12] Update mkdocs.py Sorry for being so late. --- readthedocs/doc_builder/backends/mkdocs.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 52183e40c63..d90e1441163 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -13,7 +13,7 @@ from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML - +from readthedocs.doc_builder.exceptions import ProjectConfigurationError @@ -102,10 +102,7 @@ def build(self): ] if not os.path.exists(self.yaml_file): - raise BuildUserError( - f"MkDocs configuration file is missing — we could not find a 'mkdocs.yml' file at {self.yaml_file}. " - "Please make sure your repository includes one and try again." - ) + raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) if self.config.mkdocs.fail_on_warning: build_command.append("--strict") From 354cff285222d3905b5421e0bf8ce10b724ae855 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:04:25 +0530 Subject: [PATCH 04/12] Update exceptions.py added MKDOCS_NOT_FOUND constant to ProjectConfigurationError --- readthedocs/projects/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 466d09a174d..93c8b89e697 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -9,6 +9,8 @@ class ProjectConfigurationError(BuildUserError): NOT_FOUND = "project:sphinx:conf-py-not-found" MULTIPLE_CONF_FILES = "project:sphinx:multiple-conf-py-files-found" + MKDOCS_NOT_FOUND = "project:configuration:mkdocs-not-found" + class UserFileNotFound(BuildUserError): From 032a0bb1db0cc94a27fc64773e577dc82fb9ae83 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:06:29 +0530 Subject: [PATCH 05/12] Update notifications.py added MkDocs notification for missing mkdocs.yml --- readthedocs/projects/notifications.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 9b9e5d43e7a..03aced968a6 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -125,6 +125,20 @@ ), type=ERROR, ), + Message( + id=ProjectConfigurationError.MKDOCS_NOT_FOUND, + header=_("MkDocs configuration file is missing"), + body=_( + textwrap.dedent( + """ + A configuration file was not found. + Make sure you have a mkdocs.yml file in your repository. + """ + ).strip(), + ), + type=ERROR, + ), + Message( id=ProjectConfigurationError.MULTIPLE_CONF_FILES, header=_("Multiple Sphinx configuration files found"), From b0837a93f39ebcf82f56ef515d100a14f236dd7e Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:08:14 +0530 Subject: [PATCH 06/12] Update mkdocs.py raise MKDOCS_NOT_FOUND when mkdocs.yml is missing --- readthedocs/doc_builder/backends/mkdocs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index d90e1441163..ef7969f305f 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -46,6 +46,8 @@ def __init__(self, *args, **kwargs): # This is the *MkDocs* yaml file self.yaml_file = self.get_yaml_config() + if not os.path.exists(self.yaml_file): + raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_NOT_FOUND) def get_final_doctype(self): """ From ded0110dab1edf8d8782eba3e3814dd3bff34d1b Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:14:06 +0530 Subject: [PATCH 07/12] Fixing minor mistake in import of mkdocs.py --- readthedocs/doc_builder/backends/mkdocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index ef7969f305f..f68efecb223 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -13,7 +13,7 @@ from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML -from readthedocs.doc_builder.exceptions import ProjectConfigurationError +from readthedocs.projects.exceptions import ProjectConfigurationError From f5900ce5f9925776a8b9266f6e866450e43905af Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:19:02 +0530 Subject: [PATCH 08/12] yaml import problem fixed --- readthedocs/doc_builder/backends/mkdocs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index f68efecb223..e9236c3d9ed 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -6,6 +6,7 @@ import os import structlog +import yaml from django.conf import settings from readthedocs.core.utils.filesystem import safe_open From fbe174540e3739de49795d498a04fa809283bd02 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 13 Oct 2025 12:20:12 +0200 Subject: [PATCH 09/12] Updates to match Sphinx builder --- readthedocs/doc_builder/backends/mkdocs.py | 43 +++++++++++----------- readthedocs/projects/exceptions.py | 3 +- readthedocs/projects/notifications.py | 5 +-- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index e9236c3d9ed..0a750705ace 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -5,17 +5,17 @@ """ import os + import structlog import yaml from django.conf import settings from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML from readthedocs.projects.exceptions import ProjectConfigurationError - +from readthedocs.projects.exceptions import UserFileNotFound log = structlog.get_logger(__name__) @@ -46,9 +46,12 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.yaml_file = self.get_yaml_config() - if not os.path.exists(self.yaml_file): - raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_NOT_FOUND) + self.config_file = None + if self.config.mkdocs.configuration: + self.config_file = os.path.join( + self.project_path, + self.config.mkdocs.configuration, + ) def get_final_doctype(self): """ @@ -63,7 +66,7 @@ def get_final_doctype(self): # Allow symlinks, but only the ones that resolve inside the base directory. with safe_open( - self.yaml_file, + self.config_file, "r", allow_symlinks=True, base_path=self.project_path, @@ -72,22 +75,23 @@ def get_final_doctype(self): use_directory_urls = config.get("use_directory_urls", True) return MKDOCS if use_directory_urls else MKDOCS_HTML - def get_yaml_config(self): - """Find the ``mkdocs.yml`` file in the project root.""" - mkdocs_path = self.config.mkdocs.configuration - if not mkdocs_path: - mkdocs_path = "mkdocs.yml" - return os.path.join( - self.project_path, - mkdocs_path, - ) - def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" + if self.config_file is None: + raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND) + + if not os.path.exists(self.config_file): + raise UserFileNotFound( + message_id=UserFileNotFound.FILE_NOT_FOUND, + format_values={ + "filename": os.path.relpath(self.config_file, self.project_path), + }, + ) + # Write the mkdocs.yml to the build logs self.run( "cat", - os.path.relpath(self.yaml_file, self.project_path), + os.path.relpath(self.config_file, self.project_path), cwd=self.project_path, ) @@ -101,12 +105,9 @@ def build(self): "--site-dir", os.path.join("$READTHEDOCS_OUTPUT", "html"), "--config-file", - os.path.relpath(self.yaml_file, self.project_path), + os.path.relpath(self.config_file, self.project_path), ] - if not os.path.exists(self.yaml_file): - raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) - if self.config.mkdocs.fail_on_warning: build_command.append("--strict") cmd_ret = self.run( diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 93c8b89e697..5b9d9ce7e52 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -9,8 +9,7 @@ class ProjectConfigurationError(BuildUserError): NOT_FOUND = "project:sphinx:conf-py-not-found" MULTIPLE_CONF_FILES = "project:sphinx:multiple-conf-py-files-found" - MKDOCS_NOT_FOUND = "project:configuration:mkdocs-not-found" - + MKDOCS_YAML_NOT_FOUND = "project:mkdocs:yaml-not-found" class UserFileNotFound(BuildUserError): diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 03aced968a6..53648da7171 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -101,7 +101,7 @@ ), Message( id=RepositoryError.UNSUPPORTED_VCS, - header=_("Repository type not suported"), + header=_("Repository type not supported"), body=_( textwrap.dedent( """ @@ -126,7 +126,7 @@ type=ERROR, ), Message( - id=ProjectConfigurationError.MKDOCS_NOT_FOUND, + id=ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND, header=_("MkDocs configuration file is missing"), body=_( textwrap.dedent( @@ -138,7 +138,6 @@ ), type=ERROR, ), - Message( id=ProjectConfigurationError.MULTIPLE_CONF_FILES, header=_("Multiple Sphinx configuration files found"), From 92288157a2349026325f86d8ec8439181a92aaa6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 28 Oct 2025 12:18:56 +0100 Subject: [PATCH 10/12] Update tests and code to make them pass --- readthedocs/doc_builder/backends/mkdocs.py | 25 ------------------- .../projects/tests/test_build_tasks.py | 20 ++++++++++++++- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 0a750705ace..19f71a199d4 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -10,10 +10,7 @@ import yaml from django.conf import settings -from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.projects.constants import MKDOCS -from readthedocs.projects.constants import MKDOCS_HTML from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.exceptions import UserFileNotFound @@ -53,28 +50,6 @@ def __init__(self, *args, **kwargs): self.config.mkdocs.configuration, ) - def get_final_doctype(self): - """ - Select a doctype based on the ``use_directory_urls`` setting. - - https://www.mkdocs.org/user-guide/configuration/#use_directory_urls - """ - - # TODO: we should eventually remove this method completely and stop - # relying on "loading the `mkdocs.yml` file in a safe way just to know - # if it's a MKDOCS or MKDOCS_HTML documentation type". - - # Allow symlinks, but only the ones that resolve inside the base directory. - with safe_open( - self.config_file, - "r", - allow_symlinks=True, - base_path=self.project_path, - ) as fh: - config = yaml_load_safely(fh) - use_directory_urls = config.get("use_directory_urls", True) - return MKDOCS if use_directory_urls else MKDOCS_HTML - def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" if self.config_file is None: diff --git a/readthedocs/projects/tests/test_build_tasks.py b/readthedocs/projects/tests/test_build_tasks.py index 51177f88868..89bab7cf4df 100644 --- a/readthedocs/projects/tests/test_build_tasks.py +++ b/readthedocs/projects/tests/test_build_tasks.py @@ -302,6 +302,14 @@ def test_build_updates_documentation_type(self, load_yaml_config): ) ).touch() + # Create "mkdocs.yml" for the "cat" command to find it + pathlib.Path( + os.path.join( + self.project.checkout_path(self.version.slug), + "mkdocs.yml", + ) + ).touch() + self._trigger_update_docs_task() # Update version state @@ -1413,7 +1421,7 @@ def test_build_commands_executed_with_clone_token( os.makedirs(self.project.artifact_path(version=self.version.slug, type_="epub")) os.makedirs(self.project.artifact_path(version=self.version.slug, type_="pdf")) - get_clone_token.return_value = "toke:1234" + get_clone_token.return_value = "token:1234" github_app_installation = get( GitHubAppInstallation, installation_id=1234, @@ -2626,6 +2634,16 @@ def test_mkdocs_fail_on_warning(self, load_yaml_config): validate=True, ) + # Create "mkdocs.yaml" for the "cat" command to find it + os.makedirs(os.path.join(self.project.checkout_path(version=self.version.slug), "docs")) + pathlib.Path( + os.path.join( + self.project.checkout_path(self.version.slug), + "docs", + "mkdocs.yaml", + ) + ).touch() + self._trigger_update_docs_task() self.mocker.mocks["environment.run"].assert_has_calls( From 34ee160d399eb1717700d36784ac49133108e982 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 5 Nov 2025 16:38:33 +0100 Subject: [PATCH 11/12] Feedback from review --- readthedocs/doc_builder/backends/mkdocs.py | 14 ++++---------- readthedocs/projects/exceptions.py | 1 - readthedocs/projects/notifications.py | 13 ------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 19f71a199d4..04cdc6a8d71 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -11,7 +11,6 @@ from django.conf import settings from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.exceptions import UserFileNotFound @@ -43,18 +42,13 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.config_file = None - if self.config.mkdocs.configuration: - self.config_file = os.path.join( - self.project_path, - self.config.mkdocs.configuration, - ) + self.config_file = self.config_file = os.path.join( + self.project_path, + self.config.mkdocs.configuration, + ) def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" - if self.config_file is None: - raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND) - if not os.path.exists(self.config_file): raise UserFileNotFound( message_id=UserFileNotFound.FILE_NOT_FOUND, diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 5b9d9ce7e52..466d09a174d 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -9,7 +9,6 @@ class ProjectConfigurationError(BuildUserError): NOT_FOUND = "project:sphinx:conf-py-not-found" MULTIPLE_CONF_FILES = "project:sphinx:multiple-conf-py-files-found" - MKDOCS_YAML_NOT_FOUND = "project:mkdocs:yaml-not-found" class UserFileNotFound(BuildUserError): diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 53648da7171..2e4fc616ac8 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -125,19 +125,6 @@ ), type=ERROR, ), - Message( - id=ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND, - header=_("MkDocs configuration file is missing"), - body=_( - textwrap.dedent( - """ - A configuration file was not found. - Make sure you have a mkdocs.yml file in your repository. - """ - ).strip(), - ), - type=ERROR, - ), Message( id=ProjectConfigurationError.MULTIPLE_CONF_FILES, header=_("Multiple Sphinx configuration files found"), From 2f31863040f419128573c0d46976f1fe004234d6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 5 Nov 2025 17:20:54 +0100 Subject: [PATCH 12/12] Update readthedocs/doc_builder/backends/mkdocs.py Co-authored-by: Santos Gallegos --- readthedocs/doc_builder/backends/mkdocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 04cdc6a8d71..52bc208c2c5 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -42,7 +42,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.config_file = self.config_file = os.path.join( + self.config_file = os.path.join( self.project_path, self.config.mkdocs.configuration, )