diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 36f2e1e5d6f..52bc208c2c5 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -10,10 +10,8 @@ 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 UserFileNotFound log = structlog.get_logger(__name__) @@ -44,46 +42,25 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.yaml_file = self.get_yaml_config() - - 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.yaml_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 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.config_file = os.path.join( self.project_path, - mkdocs_path, + self.config.mkdocs.configuration, ) def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" + 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, ) @@ -97,8 +74,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 self.config.mkdocs.fail_on_warning: build_command.append("--strict") cmd_ret = self.run( diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 9e5ac539552..d4e8950bad9 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -103,7 +103,7 @@ ), Message( id=RepositoryError.UNSUPPORTED_VCS, - header=_("Repository type not suported"), + header=_("Repository type not supported"), body=_( textwrap.dedent( """ diff --git a/readthedocs/projects/tests/test_build_tasks.py b/readthedocs/projects/tests/test_build_tasks.py index 3a244d1e1b8..0f6d1320c24 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( diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index e510de3a45a..605d8c66d71 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -8,9 +8,11 @@ from django.test.utils import override_settings from readthedocs.config.tests.test_config import get_build_config +from readthedocs.doc_builder.backends.mkdocs import BaseMkdocs from readthedocs.doc_builder.backends.sphinx import BaseSphinx from readthedocs.doc_builder.python_environments import Virtualenv from readthedocs.projects.exceptions import ProjectConfigurationError +from readthedocs.projects.exceptions import UserFileNotFound from readthedocs.projects.models import Project @@ -112,3 +114,62 @@ def test_multiple_conf_py( with pytest.raises(ProjectConfigurationError): with override_settings(DOCROOT=tmp_docs_dir): base_sphinx.show_conf() + + +@override_settings(PRODUCTION_DOMAIN="readthedocs.org") +class MkDocsBuilderTest(TestCase): + fixtures = ["test_data", "eric"] + + def setUp(self): + self.project = Project.objects.get(slug="pip") + self.version = self.project.versions.first() + + self.build_env = mock.MagicMock() + self.build_env.project = self.project + self.build_env.version = self.version + self.build_env.build = { + "id": 123, + } + self.build_env.api_client = mock.MagicMock() + + @patch("readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.run") + @patch("readthedocs.projects.models.Project.checkout_path") + @patch("readthedocs.doc_builder.python_environments.load_yaml_config") + def test_project_without_mkdocs_yml( + self, + load_yaml_config, + checkout_path, + _, + ): + """ + Test for a project with a missing ``mkdocs.yml`` file. + + When ``mkdocs.configuration`` points to a file that doesn't exist, + a ``UserFileNotFound`` error should be raised. + """ + tmp_dir = tempfile.mkdtemp() + checkout_path.return_value = tmp_dir + python_env = Virtualenv( + version=self.version, + build_env=self.build_env, + config=get_build_config( + {"mkdocs": {"configuration": "mkdocs.yml"}}, + validate=True, + source_file=f"{tmp_dir}/readthedocs.yml", + ), + ) + base_mkdocs = BaseMkdocs( + build_env=self.build_env, + python_env=python_env, + ) + with self.assertRaises(UserFileNotFound) as e: + base_mkdocs.show_conf() + + self.assertEqual( + e.exception.message_id, + UserFileNotFound.FILE_NOT_FOUND, + ) + self.assertEqual( + e.exception.format_values.get("filename"), + "mkdocs.yml", + )