We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
404 page: navigation links are broken
# Issue

When hitting a missing page (404), the navigation links are not working anymore. 
The page link contains the base URL.

# Steps to reproduce

1. Visit https://arnaudbreton.github.io/mkdocs-404-nav-issue/hello => works. HW nav link is well formed.
2. Go to https://arnaudbreton.github.io/mkdocs-404-nav-issue/hello/missing.
3. Hover the "HW" nav link. It's "/hello/hello/" when it should "/hello".
4. Clicking on it keep appending the previous path.

Source repository is here: https://github.com/arnaudbreton/mkdocs-404-nav-issue
It's as minimal as possible to nail down the issue.

I hope I don't do things the right way.
Thanks for your help!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkdocs/commands/build.py`
Content:
```
1 # coding: utf-8
2 
3 from __future__ import unicode_literals
4 from datetime import datetime
5 from calendar import timegm
6 import logging
7 import os
8 import gzip
9 import io
10 
11 from jinja2.exceptions import TemplateNotFound
12 import jinja2
13 
14 from mkdocs import utils
15 from mkdocs.structure.files import get_files
16 from mkdocs.structure.nav import get_navigation
17 import mkdocs
18 
19 
20 class DuplicateFilter(object):
21     ''' Avoid logging duplicate messages. '''
22     def __init__(self):
23         self.msgs = set()
24 
25     def filter(self, record):
26         rv = record.msg not in self.msgs
27         self.msgs.add(record.msg)
28         return rv
29 
30 
31 log = logging.getLogger(__name__)
32 log.addFilter(DuplicateFilter())
33 
34 
35 def get_context(nav, files, config, page=None, base_url=''):
36     """
37     Return the template context for a given page or template.
38     """
39 
40     if page is not None:
41         base_url = utils.get_relative_url('.', page.url)
42 
43     extra_javascript = utils.create_media_urls(config['extra_javascript'], page, base_url)
44 
45     extra_css = utils.create_media_urls(config['extra_css'], page, base_url)
46 
47     # Support SOURCE_DATE_EPOCH environment variable for "reproducible" builds.
48     # See https://reproducible-builds.org/specs/source-date-epoch/
49     timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))
50 
51     return {
52         'nav': nav,
53         'pages': files.documentation_pages(),
54 
55         'base_url': base_url.rstrip('/'),
56 
57         'extra_css': extra_css,
58         'extra_javascript': extra_javascript,
59 
60         'mkdocs_version': mkdocs.__version__,
61         'build_date_utc': datetime.utcfromtimestamp(timestamp),
62 
63         'config': config,
64         'page': page,
65     }
66 
67 
68 def _build_template(name, template, files, config, nav):
69     """
70     Return rendered output for given template as a string.
71     """
72 
73     # Run `pre_template` plugin events.
74     template = config['plugins'].run_event(
75         'pre_template', template, template_name=name, config=config
76     )
77 
78     if utils.is_error_template(name):
79         # Force absolute URLs in the nav of error pages and account for the
80         # possability that the docs root might be different than the server root.
81         # See https://github.com/mkdocs/mkdocs/issues/77
82         base_url = utils.urlparse(config['site_url']).path
83     else:
84         base_url = utils.get_relative_url('.', name)
85 
86     context = get_context(nav, files, config, base_url=base_url)
87 
88     # Run `template_context` plugin events.
89     context = config['plugins'].run_event(
90         'template_context', context, template_name=name, config=config
91     )
92 
93     output = template.render(context)
94 
95     # Run `post_template` plugin events.
96     output = config['plugins'].run_event(
97         'post_template', output, template_name=name, config=config
98     )
99 
100     return output
101 
102 
103 def _build_theme_template(template_name, env, files, config, nav):
104     """ Build a template using the theme environment. """
105 
106     log.debug("Building theme template: {}".format(template_name))
107 
108     try:
109         template = env.get_template(template_name)
110     except TemplateNotFound:
111         log.warn("Template skipped: '{}' not found in theme directories.".format(template_name))
112         return
113 
114     output = _build_template(template_name, template, files, config, nav)
115 
116     if output.strip():
117         output_path = os.path.join(config['site_dir'], template_name)
118         utils.write_file(output.encode('utf-8'), output_path)
119 
120         if template_name == 'sitemap.xml':
121             log.debug("Gzipping template: %s", template_name)
122             with gzip.open('{}.gz'.format(output_path), 'wb') as f:
123                 f.write(output.encode('utf-8'))
124     else:
125         log.info("Template skipped: '{}' generated empty output.".format(template_name))
126 
127 
128 def _build_extra_template(template_name, files, config, nav):
129     """ Build user templates which are not part of the theme. """
130 
131     log.debug("Building extra template: {}".format(template_name))
132 
133     file = files.get_file_from_path(template_name)
134     if file is None:
135         log.warn("Template skipped: '{}' not found in docs_dir.".format(template_name))
136         return
137 
138     try:
139         with io.open(file.abs_src_path, 'r', encoding='utf-8', errors='strict') as f:
140             template = jinja2.Template(f.read())
141     except Exception as e:
142         log.warn("Error reading template '{}': {}".format(template_name, e))
143         return
144 
145     output = _build_template(template_name, template, files, config, nav)
146 
147     if output.strip():
148         utils.write_file(output.encode('utf-8'), file.abs_dest_path)
149     else:
150         log.info("Template skipped: '{}' generated empty output.".format(template_name))
151 
152 
153 def _populate_page(page, config, files, dirty=False):
154     """ Read page content from docs_dir and render Markdown. """
155 
156     try:
157         # When --dirty is used, only read the page if the file has been modified since the
158         # previous build of the output.
159         if dirty and not page.file.is_modified():
160             return
161 
162         # Run the `pre_page` plugin event
163         page = config['plugins'].run_event(
164             'pre_page', page, config=config, files=files
165         )
166 
167         page.read_source(config)
168 
169         # Run `page_markdown` plugin events.
170         page.markdown = config['plugins'].run_event(
171             'page_markdown', page.markdown, page=page, config=config, files=files
172         )
173 
174         page.render(config, files)
175 
176         # Run `page_content` plugin events.
177         page.content = config['plugins'].run_event(
178             'page_content', page.content, page=page, config=config, files=files
179         )
180     except Exception as e:
181         log.error("Error reading page '{}': {}".format(page.file.src_path, e))
182         raise
183 
184 
185 def _build_page(page, config, files, nav, env, dirty=False):
186     """ Pass a Page to theme template and write output to site_dir. """
187 
188     try:
189         # When --dirty is used, only build the page if the file has been modified since the
190         # previous build of the output.
191         if dirty and not page.file.is_modified():
192             return
193 
194         log.debug("Building page {}".format(page.file.src_path))
195 
196         # Activate page. Signals to theme that this is the current page.
197         page.active = True
198 
199         context = get_context(nav, files, config, page)
200 
201         # Allow 'template:' override in md source files.
202         if 'template' in page.meta:
203             template = env.get_template(page.meta['template'])
204         else:
205             template = env.get_template('main.html')
206 
207         # Run `page_context` plugin events.
208         context = config['plugins'].run_event(
209             'page_context', context, page=page, config=config, nav=nav
210         )
211 
212         # Render the template.
213         output = template.render(context)
214 
215         # Run `post_page` plugin events.
216         output = config['plugins'].run_event(
217             'post_page', output, page=page, config=config
218         )
219 
220         # Write the output file.
221         if output.strip():
222             utils.write_file(output.encode('utf-8', errors='xmlcharrefreplace'), page.file.abs_dest_path)
223         else:
224             log.info("Page skipped: '{}'. Generated empty output.".format(page.file.src_path))
225 
226         # Deactivate page
227         page.active = False
228     except Exception as e:
229         log.error("Error building page '{}': {}".format(page.file.src_path, e))
230         raise
231 
232 
233 def build(config, live_server=False, dirty=False):
234     """ Perform a full site build. """
235 
236     # Run `config` plugin events.
237     config = config['plugins'].run_event('config', config)
238 
239     # Run `pre_build` plugin events.
240     config['plugins'].run_event('pre_build', config)
241 
242     if not dirty:
243         log.info("Cleaning site directory")
244         utils.clean_directory(config['site_dir'])
245     else:  # pragma: no cover
246         # Warn user about problems that may occur with --dirty option
247         log.warning("A 'dirty' build is being performed, this will likely lead to inaccurate navigation and other"
248                     " links within your site. This option is designed for site development purposes only.")
249 
250     if not live_server:  # pragma: no cover
251         log.info("Building documentation to directory: %s", config['site_dir'])
252         if dirty and site_directory_contains_stale_files(config['site_dir']):
253             log.info("The directory contains stale files. Use --clean to remove them.")
254 
255     # First gather all data from all files/pages to ensure all data is consistent across all pages.
256 
257     files = get_files(config)
258     env = config['theme'].get_env()
259     files.add_files_from_theme(env, config)
260 
261     # Run `files` plugin events.
262     files = config['plugins'].run_event('files', files, config=config)
263 
264     nav = get_navigation(files, config)
265 
266     # Run `nav` plugin events.
267     nav = config['plugins'].run_event('nav', nav, config=config, files=files)
268 
269     log.debug("Reading markdown pages.")
270     for file in files.documentation_pages():
271         _populate_page(file.page, config, files, dirty)
272 
273     # Run `env` plugin events.
274     env = config['plugins'].run_event(
275         'env', env, config=config, files=files
276     )
277 
278     # Start writing files to site_dir now that all data is gathered. Note that order matters. Files
279     # with lower precedence get written first so that files with higher precedence can overwrite them.
280 
281     log.debug("Copying static assets.")
282     files.copy_static_files(dirty=dirty)
283 
284     for template in config['theme'].static_templates:
285         _build_theme_template(template, env, files, config, nav)
286 
287     for template in config['extra_templates']:
288         _build_extra_template(template, files, config, nav)
289 
290     log.debug("Building markdown pages.")
291     for file in files.documentation_pages():
292         _build_page(file.page, config, files, nav, env, dirty)
293 
294     # Run `post_build` plugin events.
295     config['plugins'].run_event('post_build', config)
296 
297 
298 def site_directory_contains_stale_files(site_directory):
299     """ Check if the site directory contains stale files from a previous build. """
300 
301     return True if os.path.exists(site_directory) and os.listdir(site_directory) else False
302
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py
--- a/mkdocs/commands/build.py
+++ b/mkdocs/commands/build.py
@@ -52,7 +52,7 @@
         'nav': nav,
         'pages': files.documentation_pages(),
 
-        'base_url': base_url.rstrip('/'),
+        'base_url': base_url,
 
         'extra_css': extra_css,
         'extra_javascript': extra_javascript,
@@ -78,8 +78,10 @@
     if utils.is_error_template(name):
         # Force absolute URLs in the nav of error pages and account for the
         # possability that the docs root might be different than the server root.
-        # See https://github.com/mkdocs/mkdocs/issues/77
-        base_url = utils.urlparse(config['site_url']).path
+        # See https://github.com/mkdocs/mkdocs/issues/77.
+        # However, if site_url is not set, assume the docs root and server root
+        # are the same. See https://github.com/mkdocs/mkdocs/issues/1598.
+        base_url = utils.urlparse(config['site_url'] or '/').path
     else:
         base_url = utils.get_relative_url('.', name)

{"golden_diff": "diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py\n--- a/mkdocs/commands/build.py\n+++ b/mkdocs/commands/build.py\n@@ -52,7 +52,7 @@\n         'nav': nav,\n         'pages': files.documentation_pages(),\n \n-        'base_url': base_url.rstrip('/'),\n+        'base_url': base_url,\n \n         'extra_css': extra_css,\n         'extra_javascript': extra_javascript,\n@@ -78,8 +78,10 @@\n     if utils.is_error_template(name):\n         # Force absolute URLs in the nav of error pages and account for the\n         # possability that the docs root might be different than the server root.\n-        # See https://github.com/mkdocs/mkdocs/issues/77\n-        base_url = utils.urlparse(config['site_url']).path\n+        # See https://github.com/mkdocs/mkdocs/issues/77.\n+        # However, if site_url is not set, assume the docs root and server root\n+        # are the same. See https://github.com/mkdocs/mkdocs/issues/1598.\n+        base_url = utils.urlparse(config['site_url'] or '/').path\n     else:\n         base_url = utils.get_relative_url('.', name)\n", "issue": "404 page: navigation links are broken\n# Issue\r\n\r\nWhen hitting a missing page (404), the navigation links are not working anymore. \r\nThe page link contains the base URL.\r\n\r\n# Steps to reproduce\r\n\r\n1. Visit https://arnaudbreton.github.io/mkdocs-404-nav-issue/hello => works. HW nav link is well formed.\r\n2. Go to https://arnaudbreton.github.io/mkdocs-404-nav-issue/hello/missing.\r\n3. Hover the \"HW\" nav link. It's \"/hello/hello/\" when it should \"/hello\".\r\n4. Clicking on it keep appending the previous path.\r\n\r\nSource repository is here: https://github.com/arnaudbreton/mkdocs-404-nav-issue\r\nIt's as minimal as possible to nail down the issue.\r\n\r\nI hope I don't do things the right way.\r\nThanks for your help!\n", "before_files": [{"content": "# coding: utf-8\n\nfrom __future__ import unicode_literals\nfrom datetime import datetime\nfrom calendar import timegm\nimport logging\nimport os\nimport gzip\nimport io\n\nfrom jinja2.exceptions import TemplateNotFound\nimport jinja2\n\nfrom mkdocs import utils\nfrom mkdocs.structure.files import get_files\nfrom mkdocs.structure.nav import get_navigation\nimport mkdocs\n\n\nclass DuplicateFilter(object):\n    ''' Avoid logging duplicate messages. '''\n    def __init__(self):\n        self.msgs = set()\n\n    def filter(self, record):\n        rv = record.msg not in self.msgs\n        self.msgs.add(record.msg)\n        return rv\n\n\nlog = logging.getLogger(__name__)\nlog.addFilter(DuplicateFilter())\n\n\ndef get_context(nav, files, config, page=None, base_url=''):\n    \"\"\"\n    Return the template context for a given page or template.\n    \"\"\"\n\n    if page is not None:\n        base_url = utils.get_relative_url('.', page.url)\n\n    extra_javascript = utils.create_media_urls(config['extra_javascript'], page, base_url)\n\n    extra_css = utils.create_media_urls(config['extra_css'], page, base_url)\n\n    # Support SOURCE_DATE_EPOCH environment variable for \"reproducible\" builds.\n    # See https://reproducible-builds.org/specs/source-date-epoch/\n    timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))\n\n    return {\n        'nav': nav,\n        'pages': files.documentation_pages(),\n\n        'base_url': base_url.rstrip('/'),\n\n        'extra_css': extra_css,\n        'extra_javascript': extra_javascript,\n\n        'mkdocs_version': mkdocs.__version__,\n        'build_date_utc': datetime.utcfromtimestamp(timestamp),\n\n        'config': config,\n        'page': page,\n    }\n\n\ndef _build_template(name, template, files, config, nav):\n    \"\"\"\n    Return rendered output for given template as a string.\n    \"\"\"\n\n    # Run `pre_template` plugin events.\n    template = config['plugins'].run_event(\n        'pre_template', template, template_name=name, config=config\n    )\n\n    if utils.is_error_template(name):\n        # Force absolute URLs in the nav of error pages and account for the\n        # possability that the docs root might be different than the server root.\n        # See https://github.com/mkdocs/mkdocs/issues/77\n        base_url = utils.urlparse(config['site_url']).path\n    else:\n        base_url = utils.get_relative_url('.', name)\n\n    context = get_context(nav, files, config, base_url=base_url)\n\n    # Run `template_context` plugin events.\n    context = config['plugins'].run_event(\n        'template_context', context, template_name=name, config=config\n    )\n\n    output = template.render(context)\n\n    # Run `post_template` plugin events.\n    output = config['plugins'].run_event(\n        'post_template', output, template_name=name, config=config\n    )\n\n    return output\n\n\ndef _build_theme_template(template_name, env, files, config, nav):\n    \"\"\" Build a template using the theme environment. \"\"\"\n\n    log.debug(\"Building theme template: {}\".format(template_name))\n\n    try:\n        template = env.get_template(template_name)\n    except TemplateNotFound:\n        log.warn(\"Template skipped: '{}' not found in theme directories.\".format(template_name))\n        return\n\n    output = _build_template(template_name, template, files, config, nav)\n\n    if output.strip():\n        output_path = os.path.join(config['site_dir'], template_name)\n        utils.write_file(output.encode('utf-8'), output_path)\n\n        if template_name == 'sitemap.xml':\n            log.debug(\"Gzipping template: %s\", template_name)\n            with gzip.open('{}.gz'.format(output_path), 'wb') as f:\n                f.write(output.encode('utf-8'))\n    else:\n        log.info(\"Template skipped: '{}' generated empty output.\".format(template_name))\n\n\ndef _build_extra_template(template_name, files, config, nav):\n    \"\"\" Build user templates which are not part of the theme. \"\"\"\n\n    log.debug(\"Building extra template: {}\".format(template_name))\n\n    file = files.get_file_from_path(template_name)\n    if file is None:\n        log.warn(\"Template skipped: '{}' not found in docs_dir.\".format(template_name))\n        return\n\n    try:\n        with io.open(file.abs_src_path, 'r', encoding='utf-8', errors='strict') as f:\n            template = jinja2.Template(f.read())\n    except Exception as e:\n        log.warn(\"Error reading template '{}': {}\".format(template_name, e))\n        return\n\n    output = _build_template(template_name, template, files, config, nav)\n\n    if output.strip():\n        utils.write_file(output.encode('utf-8'), file.abs_dest_path)\n    else:\n        log.info(\"Template skipped: '{}' generated empty output.\".format(template_name))\n\n\ndef _populate_page(page, config, files, dirty=False):\n    \"\"\" Read page content from docs_dir and render Markdown. \"\"\"\n\n    try:\n        # When --dirty is used, only read the page if the file has been modified since the\n        # previous build of the output.\n        if dirty and not page.file.is_modified():\n            return\n\n        # Run the `pre_page` plugin event\n        page = config['plugins'].run_event(\n            'pre_page', page, config=config, files=files\n        )\n\n        page.read_source(config)\n\n        # Run `page_markdown` plugin events.\n        page.markdown = config['plugins'].run_event(\n            'page_markdown', page.markdown, page=page, config=config, files=files\n        )\n\n        page.render(config, files)\n\n        # Run `page_content` plugin events.\n        page.content = config['plugins'].run_event(\n            'page_content', page.content, page=page, config=config, files=files\n        )\n    except Exception as e:\n        log.error(\"Error reading page '{}': {}\".format(page.file.src_path, e))\n        raise\n\n\ndef _build_page(page, config, files, nav, env, dirty=False):\n    \"\"\" Pass a Page to theme template and write output to site_dir. \"\"\"\n\n    try:\n        # When --dirty is used, only build the page if the file has been modified since the\n        # previous build of the output.\n        if dirty and not page.file.is_modified():\n            return\n\n        log.debug(\"Building page {}\".format(page.file.src_path))\n\n        # Activate page. Signals to theme that this is the current page.\n        page.active = True\n\n        context = get_context(nav, files, config, page)\n\n        # Allow 'template:' override in md source files.\n        if 'template' in page.meta:\n            template = env.get_template(page.meta['template'])\n        else:\n            template = env.get_template('main.html')\n\n        # Run `page_context` plugin events.\n        context = config['plugins'].run_event(\n            'page_context', context, page=page, config=config, nav=nav\n        )\n\n        # Render the template.\n        output = template.render(context)\n\n        # Run `post_page` plugin events.\n        output = config['plugins'].run_event(\n            'post_page', output, page=page, config=config\n        )\n\n        # Write the output file.\n        if output.strip():\n            utils.write_file(output.encode('utf-8', errors='xmlcharrefreplace'), page.file.abs_dest_path)\n        else:\n            log.info(\"Page skipped: '{}'. Generated empty output.\".format(page.file.src_path))\n\n        # Deactivate page\n        page.active = False\n    except Exception as e:\n        log.error(\"Error building page '{}': {}\".format(page.file.src_path, e))\n        raise\n\n\ndef build(config, live_server=False, dirty=False):\n    \"\"\" Perform a full site build. \"\"\"\n\n    # Run `config` plugin events.\n    config = config['plugins'].run_event('config', config)\n\n    # Run `pre_build` plugin events.\n    config['plugins'].run_event('pre_build', config)\n\n    if not dirty:\n        log.info(\"Cleaning site directory\")\n        utils.clean_directory(config['site_dir'])\n    else:  # pragma: no cover\n        # Warn user about problems that may occur with --dirty option\n        log.warning(\"A 'dirty' build is being performed, this will likely lead to inaccurate navigation and other\"\n                    \" links within your site. This option is designed for site development purposes only.\")\n\n    if not live_server:  # pragma: no cover\n        log.info(\"Building documentation to directory: %s\", config['site_dir'])\n        if dirty and site_directory_contains_stale_files(config['site_dir']):\n            log.info(\"The directory contains stale files. Use --clean to remove them.\")\n\n    # First gather all data from all files/pages to ensure all data is consistent across all pages.\n\n    files = get_files(config)\n    env = config['theme'].get_env()\n    files.add_files_from_theme(env, config)\n\n    # Run `files` plugin events.\n    files = config['plugins'].run_event('files', files, config=config)\n\n    nav = get_navigation(files, config)\n\n    # Run `nav` plugin events.\n    nav = config['plugins'].run_event('nav', nav, config=config, files=files)\n\n    log.debug(\"Reading markdown pages.\")\n    for file in files.documentation_pages():\n        _populate_page(file.page, config, files, dirty)\n\n    # Run `env` plugin events.\n    env = config['plugins'].run_event(\n        'env', env, config=config, files=files\n    )\n\n    # Start writing files to site_dir now that all data is gathered. Note that order matters. Files\n    # with lower precedence get written first so that files with higher precedence can overwrite them.\n\n    log.debug(\"Copying static assets.\")\n    files.copy_static_files(dirty=dirty)\n\n    for template in config['theme'].static_templates:\n        _build_theme_template(template, env, files, config, nav)\n\n    for template in config['extra_templates']:\n        _build_extra_template(template, files, config, nav)\n\n    log.debug(\"Building markdown pages.\")\n    for file in files.documentation_pages():\n        _build_page(file.page, config, files, nav, env, dirty)\n\n    # Run `post_build` plugin events.\n    config['plugins'].run_event('post_build', config)\n\n\ndef site_directory_contains_stale_files(site_directory):\n    \"\"\" Check if the site directory contains stale files from a previous build. \"\"\"\n\n    return True if os.path.exists(site_directory) and os.listdir(site_directory) else False\n", "path": "mkdocs/commands/build.py"}], "after_files": [{"content": "# coding: utf-8\n\nfrom __future__ import unicode_literals\nfrom datetime import datetime\nfrom calendar import timegm\nimport logging\nimport os\nimport gzip\nimport io\n\nfrom jinja2.exceptions import TemplateNotFound\nimport jinja2\n\nfrom mkdocs import utils\nfrom mkdocs.structure.files import get_files\nfrom mkdocs.structure.nav import get_navigation\nimport mkdocs\n\n\nclass DuplicateFilter(object):\n    ''' Avoid logging duplicate messages. '''\n    def __init__(self):\n        self.msgs = set()\n\n    def filter(self, record):\n        rv = record.msg not in self.msgs\n        self.msgs.add(record.msg)\n        return rv\n\n\nlog = logging.getLogger(__name__)\nlog.addFilter(DuplicateFilter())\n\n\ndef get_context(nav, files, config, page=None, base_url=''):\n    \"\"\"\n    Return the template context for a given page or template.\n    \"\"\"\n\n    if page is not None:\n        base_url = utils.get_relative_url('.', page.url)\n\n    extra_javascript = utils.create_media_urls(config['extra_javascript'], page, base_url)\n\n    extra_css = utils.create_media_urls(config['extra_css'], page, base_url)\n\n    # Support SOURCE_DATE_EPOCH environment variable for \"reproducible\" builds.\n    # See https://reproducible-builds.org/specs/source-date-epoch/\n    timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))\n\n    return {\n        'nav': nav,\n        'pages': files.documentation_pages(),\n\n        'base_url': base_url,\n\n        'extra_css': extra_css,\n        'extra_javascript': extra_javascript,\n\n        'mkdocs_version': mkdocs.__version__,\n        'build_date_utc': datetime.utcfromtimestamp(timestamp),\n\n        'config': config,\n        'page': page,\n    }\n\n\ndef _build_template(name, template, files, config, nav):\n    \"\"\"\n    Return rendered output for given template as a string.\n    \"\"\"\n\n    # Run `pre_template` plugin events.\n    template = config['plugins'].run_event(\n        'pre_template', template, template_name=name, config=config\n    )\n\n    if utils.is_error_template(name):\n        # Force absolute URLs in the nav of error pages and account for the\n        # possability that the docs root might be different than the server root.\n        # See https://github.com/mkdocs/mkdocs/issues/77.\n        # However, if site_url is not set, assume the docs root and server root\n        # are the same. See https://github.com/mkdocs/mkdocs/issues/1598.\n        base_url = utils.urlparse(config['site_url'] or '/').path\n    else:\n        base_url = utils.get_relative_url('.', name)\n\n    context = get_context(nav, files, config, base_url=base_url)\n\n    # Run `template_context` plugin events.\n    context = config['plugins'].run_event(\n        'template_context', context, template_name=name, config=config\n    )\n\n    output = template.render(context)\n\n    # Run `post_template` plugin events.\n    output = config['plugins'].run_event(\n        'post_template', output, template_name=name, config=config\n    )\n\n    return output\n\n\ndef _build_theme_template(template_name, env, files, config, nav):\n    \"\"\" Build a template using the theme environment. \"\"\"\n\n    log.debug(\"Building theme template: {}\".format(template_name))\n\n    try:\n        template = env.get_template(template_name)\n    except TemplateNotFound:\n        log.warn(\"Template skipped: '{}' not found in theme directories.\".format(template_name))\n        return\n\n    output = _build_template(template_name, template, files, config, nav)\n\n    if output.strip():\n        output_path = os.path.join(config['site_dir'], template_name)\n        utils.write_file(output.encode('utf-8'), output_path)\n\n        if template_name == 'sitemap.xml':\n            log.debug(\"Gzipping template: %s\", template_name)\n            with gzip.open('{}.gz'.format(output_path), 'wb') as f:\n                f.write(output.encode('utf-8'))\n    else:\n        log.info(\"Template skipped: '{}' generated empty output.\".format(template_name))\n\n\ndef _build_extra_template(template_name, files, config, nav):\n    \"\"\" Build user templates which are not part of the theme. \"\"\"\n\n    log.debug(\"Building extra template: {}\".format(template_name))\n\n    file = files.get_file_from_path(template_name)\n    if file is None:\n        log.warn(\"Template skipped: '{}' not found in docs_dir.\".format(template_name))\n        return\n\n    try:\n        with io.open(file.abs_src_path, 'r', encoding='utf-8', errors='strict') as f:\n            template = jinja2.Template(f.read())\n    except Exception as e:\n        log.warn(\"Error reading template '{}': {}\".format(template_name, e))\n        return\n\n    output = _build_template(template_name, template, files, config, nav)\n\n    if output.strip():\n        utils.write_file(output.encode('utf-8'), file.abs_dest_path)\n    else:\n        log.info(\"Template skipped: '{}' generated empty output.\".format(template_name))\n\n\ndef _populate_page(page, config, files, dirty=False):\n    \"\"\" Read page content from docs_dir and render Markdown. \"\"\"\n\n    try:\n        # When --dirty is used, only read the page if the file has been modified since the\n        # previous build of the output.\n        if dirty and not page.file.is_modified():\n            return\n\n        # Run the `pre_page` plugin event\n        page = config['plugins'].run_event(\n            'pre_page', page, config=config, files=files\n        )\n\n        page.read_source(config)\n\n        # Run `page_markdown` plugin events.\n        page.markdown = config['plugins'].run_event(\n            'page_markdown', page.markdown, page=page, config=config, files=files\n        )\n\n        page.render(config, files)\n\n        # Run `page_content` plugin events.\n        page.content = config['plugins'].run_event(\n            'page_content', page.content, page=page, config=config, files=files\n        )\n    except Exception as e:\n        log.error(\"Error reading page '{}': {}\".format(page.file.src_path, e))\n        raise\n\n\ndef _build_page(page, config, files, nav, env, dirty=False):\n    \"\"\" Pass a Page to theme template and write output to site_dir. \"\"\"\n\n    try:\n        # When --dirty is used, only build the page if the file has been modified since the\n        # previous build of the output.\n        if dirty and not page.file.is_modified():\n            return\n\n        log.debug(\"Building page {}\".format(page.file.src_path))\n\n        # Activate page. Signals to theme that this is the current page.\n        page.active = True\n\n        context = get_context(nav, files, config, page)\n\n        # Allow 'template:' override in md source files.\n        if 'template' in page.meta:\n            template = env.get_template(page.meta['template'])\n        else:\n            template = env.get_template('main.html')\n\n        # Run `page_context` plugin events.\n        context = config['plugins'].run_event(\n            'page_context', context, page=page, config=config, nav=nav\n        )\n\n        # Render the template.\n        output = template.render(context)\n\n        # Run `post_page` plugin events.\n        output = config['plugins'].run_event(\n            'post_page', output, page=page, config=config\n        )\n\n        # Write the output file.\n        if output.strip():\n            utils.write_file(output.encode('utf-8', errors='xmlcharrefreplace'), page.file.abs_dest_path)\n        else:\n            log.info(\"Page skipped: '{}'. Generated empty output.\".format(page.file.src_path))\n\n        # Deactivate page\n        page.active = False\n    except Exception as e:\n        log.error(\"Error building page '{}': {}\".format(page.file.src_path, e))\n        raise\n\n\ndef build(config, live_server=False, dirty=False):\n    \"\"\" Perform a full site build. \"\"\"\n\n    # Run `config` plugin events.\n    config = config['plugins'].run_event('config', config)\n\n    # Run `pre_build` plugin events.\n    config['plugins'].run_event('pre_build', config)\n\n    if not dirty:\n        log.info(\"Cleaning site directory\")\n        utils.clean_directory(config['site_dir'])\n    else:  # pragma: no cover\n        # Warn user about problems that may occur with --dirty option\n        log.warning(\"A 'dirty' build is being performed, this will likely lead to inaccurate navigation and other\"\n                    \" links within your site. This option is designed for site development purposes only.\")\n\n    if not live_server:  # pragma: no cover\n        log.info(\"Building documentation to directory: %s\", config['site_dir'])\n        if dirty and site_directory_contains_stale_files(config['site_dir']):\n            log.info(\"The directory contains stale files. Use --clean to remove them.\")\n\n    # First gather all data from all files/pages to ensure all data is consistent across all pages.\n\n    files = get_files(config)\n    env = config['theme'].get_env()\n    files.add_files_from_theme(env, config)\n\n    # Run `files` plugin events.\n    files = config['plugins'].run_event('files', files, config=config)\n\n    nav = get_navigation(files, config)\n\n    # Run `nav` plugin events.\n    nav = config['plugins'].run_event('nav', nav, config=config, files=files)\n\n    log.debug(\"Reading markdown pages.\")\n    for file in files.documentation_pages():\n        _populate_page(file.page, config, files, dirty)\n\n    # Run `env` plugin events.\n    env = config['plugins'].run_event(\n        'env', env, config=config, files=files\n    )\n\n    # Start writing files to site_dir now that all data is gathered. Note that order matters. Files\n    # with lower precedence get written first so that files with higher precedence can overwrite them.\n\n    log.debug(\"Copying static assets.\")\n    files.copy_static_files(dirty=dirty)\n\n    for template in config['theme'].static_templates:\n        _build_theme_template(template, env, files, config, nav)\n\n    for template in config['extra_templates']:\n        _build_extra_template(template, files, config, nav)\n\n    log.debug(\"Building markdown pages.\")\n    for file in files.documentation_pages():\n        _build_page(file.page, config, files, nav, env, dirty)\n\n    # Run `post_build` plugin events.\n    config['plugins'].run_event('post_build', config)\n\n\ndef site_directory_contains_stale_files(site_directory):\n    \"\"\" Check if the site directory contains stale files from a previous build. \"\"\"\n\n    return True if os.path.exists(site_directory) and os.listdir(site_directory) else False\n", "path": "mkdocs/commands/build.py"}]}