Repository: readthedocs/readthedocs.org

Issue:
Inject canonical tag on mkdocs as we do with Sphinx
In Sphinx if a user has a custom domain we setup the canonical tag for the user (we also have some docs for that feature https://docs.readthedocs.io/en/latest/canonical.html)

In Sphinx we do that here https://github.com/rtfd/readthedocs.org/blob/9b2b17c0fc603267b4b20f5923862c2db82602da/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl#L95-L95

On mkdocs we don't do that, but it's possible using the `site_url` setting https://www.mkdocs.org/user-guide/configuration/#site_url

We can do that here

https://github.com/rtfd/readthedocs.org/blob/9b2b17c0fc603267b4b20f5923862c2db82602da/readthedocs/doc_builder/backends/mkdocs.py#L107-L107

Ref https://github.com/rtfd/readthedocs.org/issues/3137


Discussion:
@stsewd I would like to take this issue up.
I didn't find the way to get the canonical url of the project, is it present in the keyword arguments that `append_conf` receive, i.e. `__`?
That comes from the project model, but this issue has the `Needed: design decision` label, we need to wait to be approved before writing some code
Just adding that looks like the `site_url` option have been there for a long time, nothing relevant on the changelog https://www.mkdocs.org/about/release-notes
> Just btw MKdocs just yesterday added support for Environment variables in version 1.2.0, all you have to do is make a variable with the final URL available for the build :)

https://www.mkdocs.org/about/release-notes/#support-added-for-environment-variables-in-the-configuration-file-1954
As mentioned by @astrojuanlu, we can use environment variables in `.mkdocs.yml` and that would be valuable in general to users.

However, then this needs to be fixed: https://github.com/readthedocs/readthedocs.org/issues/8529

Resolving patch:
diff --git a/readthedocs/api/v2/serializers.py b/readthedocs/api/v2/serializers.py
--- a/readthedocs/api/v2/serializers.py
+++ b/readthedocs/api/v2/serializers.py
@@ -5,6 +5,7 @@
 from rest_framework import serializers
 
 from readthedocs.api.v2.utils import normalize_build_command
+from readthedocs.builds.constants import EXTERNAL
 from readthedocs.builds.models import Build, BuildCommandResult, Version
 from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
 from readthedocs.projects.models import Domain, Project
@@ -124,11 +125,20 @@ class VersionAdminSerializer(VersionSerializer):
     """Version serializer that returns admin project data."""
 
     project = ProjectAdminSerializer()
+    canonical_url = serializers.SerializerMethodField()
     build_data = serializers.JSONField(required=False, write_only=True)
 
+    def get_canonical_url(self, obj):
+        return obj.project.get_docs_url(
+            lang_slug=obj.project.language,
+            version_slug=obj.slug,
+            external=obj.type == EXTERNAL,
+        )
+
     class Meta(VersionSerializer.Meta):
         fields = VersionSerializer.Meta.fields + [
             "build_data",
+            "canonical_url",
         ]
 
 
diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py
--- a/readthedocs/builds/models.py
+++ b/readthedocs/builds/models.py
@@ -601,7 +601,8 @@ class Meta:
         proxy = True
 
     def __init__(self, *args, **kwargs):
-        self.project = APIProject(**kwargs.pop('project', {}))
+        self.project = APIProject(**kwargs.pop("project", {}))
+        self.canonical_url = kwargs.pop("canonical_url", None)
         # These fields only exist on the API return, not on the model, so we'll
         # remove them to avoid throwing exceptions due to unexpected fields
         for key in ['resource_uri', 'absolute_url', 'downloads']:
diff --git a/readthedocs/doc_builder/director.py b/readthedocs/doc_builder/director.py
--- a/readthedocs/doc_builder/director.py
+++ b/readthedocs/doc_builder/director.py
@@ -615,6 +615,12 @@ def get_build_env_vars(self):
                 }
             )
 
+        env.update(
+            {
+                "READTHEDOCS_CANONICAL_URL": self.data.version.canonical_url,
+            }
+        )
+
         # Update environment from Project's specific environment variables,
         # avoiding to expose private environment variables
         # if the version is external (i.e. a PR build).


Test patch:
diff --git a/readthedocs/projects/tests/test_build_tasks.py b/readthedocs/projects/tests/test_build_tasks.py
--- a/readthedocs/projects/tests/test_build_tasks.py
+++ b/readthedocs/projects/tests/test_build_tasks.py
@@ -301,6 +301,11 @@ def test_get_env_vars(self, load_yaml_config, build_environment, config, externa
             # Local and Circle are different values.
             # We only check it's present, but not its value.
             READTHEDOCS_VIRTUALENV_PATH=mock.ANY,
+            READTHEDOCS_CANONICAL_URL=self.project.get_docs_url(
+                lang_slug=self.project.language,
+                version_slug=self.version.slug,
+                external=external,
+            ),
         )
         if not external:
             expected_build_env_vars["PRIVATE_TOKEN"] = "a1b2c3"
diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py
--- a/readthedocs/rtd_tests/tests/test_api.py
+++ b/readthedocs/rtd_tests/tests/test_api.py
@@ -2442,6 +2442,7 @@ def test_get_version_by_id(self):
             "built": False,
             "id": 18,
             "active": True,
+            "canonical_url": "http://readthedocs.org/docs/pip/en/0.8/",
             "project": {
                 "analytics_code": None,
                 "analytics_disabled": False,