Skip to content

Commit f0222f4

Browse files
authored
docs: Fix 'mkdocs serve' endless build loop (#3662)
* docs: Fix 'mkdocs serve' endless build loop When developing and previewing docs using `mkdocs serve`, the site gets stuck into an endless build loop when any content is changed. This is caused by two hooks that copy content into the site-src/ directory on site build, mkdocs-copy-geps.py and mkdocs-generate-conformance.py, triggering another build due to the changed content. The solution is to use MkDocs Files API to programmatically add the files into the site instead of copying them into the site directory. See mkdocs/mkdocs#2544 for more information. * Fix failing test by upgrading to Python 3.12 The Path.walk() function was added in Python 3.12. * Ensure that reportedImplementationsPath is sorted
1 parent 8e8115c commit f0222f4

File tree

4 files changed

+96
-23
lines changed

4 files changed

+96
-23
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,3 @@ go.work.sum
5656
/cache
5757
.venv/
5858
release/
59-
site-src/geps

hack/mkdocs-copy-geps.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,50 @@
1515
import shutil
1616
import logging
1717
from mkdocs import plugins
18+
from mkdocs.structure.files import File
19+
from pathlib import Path
1820

19-
log = logging.getLogger('mkdocs')
21+
log = logging.getLogger(f'mkdocs.plugins.{__name__}')
2022

2123

2224
@plugins.event_priority(100)
23-
def on_pre_build(config, **kwargs):
24-
log.info("copying geps")
25-
shutil.copytree("geps", "site-src/geps", dirs_exist_ok=True)
25+
def on_files(files, config, **kwargs):
26+
log.info("adding geps")
27+
28+
# Check if site-src/geps exists (files copied out-of-band from MkDocs)
29+
site_src_geps = Path('site-src/geps')
30+
if site_src_geps.exists() and site_src_geps.is_dir():
31+
log.info("Found site-src/gep/ directory. Deleting...")
32+
33+
# Iterate over the list of files in this directory and remove them from
34+
# MkDocs
35+
for root_dir, _, gep_files in Path(site_src_geps).walk():
36+
for filename in gep_files:
37+
# Exclude the leading 'site-src/' to get the relative path as it
38+
# exists on the site. (i.e., geps/overview.md)
39+
path = '/'.join(root_dir.parts[1:])
40+
41+
existing_file = files.get_file_from_path(f'{path}/{filename}')
42+
if existing_file:
43+
files.remove(existing_file)
44+
45+
# Delete the 'site-src/geps' directory
46+
shutil.rmtree(site_src_geps)
47+
48+
for root_dir, _, gep_files in Path('geps').walk():
49+
50+
# Iterate over the all the files in the GEP folder and add them to the site
51+
for filename in gep_files:
52+
file_path = str(root_dir / filename)
53+
54+
if files.get_file_from_path(file_path) is None:
55+
new_file = File(
56+
path=file_path,
57+
src_dir='./',
58+
dest_dir=config['site_dir'],
59+
use_directory_urls=config['use_directory_urls']
60+
)
61+
62+
files.append(new_file)
63+
64+
return files

hack/mkdocs-generate-conformance.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,41 @@
1313
# limitations under the License.
1414

1515
import logging
16+
from io import StringIO
1617
from mkdocs import plugins
18+
from mkdocs.structure.files import File
1719
import yaml
1820
import pandas
1921
from fnmatch import fnmatch
2022
import glob
2123
import os
2224

23-
log = logging.getLogger('mkdocs')
25+
log = logging.getLogger(f'mkdocs.plugins.{__name__}')
2426

2527

2628
@plugins.event_priority(100)
27-
def on_pre_build(config, **kwargs):
29+
def on_files(files, config, **kwargs):
2830
log.info("generating conformance")
2931

3032
vers = getConformancePaths()
33+
# Iterate over the list of versions. Exclude the pre 1.0 versions.
3134
for v in vers[3:]:
3235

3336
confYamls = getYaml(v)
3437
releaseVersion = v.split(os.sep)[-2]
35-
generate_conformance_tables(confYamls, releaseVersion)
38+
file = generate_conformance_tables(confYamls, releaseVersion, config)
39+
40+
if file:
41+
existing_file = files.get_file_from_path(file.src_uri)
42+
if existing_file:
43+
# Remove the existing file that is likely present in the
44+
# repository
45+
files.remove(existing_file)
46+
47+
# Add the generated file to the site
48+
files.append(file)
49+
50+
return files
3651

3752

3853
desc = """
@@ -51,7 +66,10 @@ def on_pre_build(config, **kwargs):
5166

5267

5368

54-
def generate_conformance_tables(reports, currVersion):
69+
def generate_conformance_tables(reports, currVersion, mkdocsConfig):
70+
71+
# Enable Pandas copy-on-write
72+
pandas.options.mode.copy_on_write = True
5573

5674
gateway_tls_table = pandas.DataFrame()
5775
gateway_grpc_table = pandas.DataFrame()
@@ -79,7 +97,8 @@ def generate_conformance_tables(reports, currVersion):
7997
if entries.Project < 3:
8098
return
8199

82-
with open('site-src/implementations/'+versionFile+'.md', 'w') as f:
100+
try:
101+
f = StringIO()
83102

84103
f.write(desc)
85104
f.write("\n\n")
@@ -90,7 +109,7 @@ def generate_conformance_tables(reports, currVersion):
90109
f.write("## Gateway Profile\n\n")
91110
f.write("### HTTPRoute\n\n")
92111
f.write(gateway_http_table.to_markdown()+'\n\n')
93-
if currVersion != 'v1.0.0':
112+
if currVersion != 'v1.0.0':
94113
f.write('### GRPCRoute\n\n')
95114
f.write(gateway_grpc_table.to_markdown()+'\n\n')
96115
f.write('### TLSRoute\n\n')
@@ -100,6 +119,20 @@ def generate_conformance_tables(reports, currVersion):
100119
f.write("### HTTPRoute\n\n")
101120
f.write(mesh_http_table.to_markdown())
102121

122+
file_contents = f.getvalue()
123+
finally:
124+
f.close()
125+
126+
new_file = File(
127+
src_dir=None,
128+
dest_dir=mkdocsConfig['site_dir'],
129+
path=f'implementations/{versionFile}.md',
130+
use_directory_urls=mkdocsConfig['use_directory_urls'],
131+
)
132+
new_file.content_string = file_contents
133+
new_file.generated_by = f'{__name__}'
134+
135+
return new_file
103136

104137
def generate_profiles_report(reports, route,version):
105138

@@ -114,7 +147,7 @@ def generate_profiles_report(reports, route,version):
114147
'version','mode', 'extended.supportedFeatures']].T
115148
http_table.columns = http_table.iloc[0]
116149
http_table = http_table[1:].T
117-
150+
118151
for row in http_table.itertuples():
119152
if type(row._4) is list:
120153
for feat in row._4:
@@ -130,8 +163,8 @@ def generate_profiles_report(reports, route,version):
130163

131164

132165
pathTemp = "conformance/reports/*/"
133-
allVersions = []
134-
reportedImplementationsPath = []
166+
allVersions = set()
167+
reportedImplementationsPath = set()
135168

136169
# returns v1.0.0 and greater, since that's when reports started being generated in the comparison table
137170

@@ -141,9 +174,10 @@ def getConformancePaths():
141174
report_path = versions[-1]+"**"
142175
for v in versions:
143176
vers = v.split(os.sep)[-2]
144-
allVersions.append(vers)
145-
reportedImplementationsPath.append(v+"**")
146-
return reportedImplementationsPath
177+
allVersions.add(vers)
178+
reportedImplementationsPath.add(v+"**")
179+
180+
return sorted(list(reportedImplementationsPath))
147181

148182

149183
def getYaml(conf_path):
@@ -154,11 +188,12 @@ def getYaml(conf_path):
154188
if fnmatch(p, "*.yaml"):
155189

156190
x = load_yaml(p)
157-
profiles = pandas.json_normalize(
158-
x, record_path=['profiles'], meta=["mode","implementation"], errors='ignore')
191+
if 'profiles' in x:
192+
profiles = pandas.json_normalize(
193+
x, record_path=['profiles'], meta=["mode","implementation"], errors='ignore')
159194

160-
implementation = pandas.json_normalize(profiles.implementation)
161-
yamls.append(pandas.concat([implementation, profiles], axis=1))
195+
implementation = pandas.json_normalize(profiles.implementation)
196+
yamls.append(pandas.concat([implementation, profiles], axis=1))
162197

163198
yamls = pandas.concat(yamls)
164199
return yamls

netlify.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
command = "make docs"
44
publish = "site"
55
[build.environment]
6-
PYTHON_VERSION = "3.9"
6+
PYTHON_VERSION = "3.12"
77
GO_VERSION = "1.22.8"
88

99
# Standard Netlify redirects
@@ -30,4 +30,4 @@
3030
from = "/geps/gep-1426"
3131
to = "/geps/gep-1294"
3232
status = 301
33-
force = true
33+
force = true

0 commit comments

Comments
 (0)