@@ -32,6 +32,86 @@ def working_dir(path):
3232 os .chdir (prev_cwd )
3333
3434
35+ def extract_custom_config_vars (confpath , var_names ):
36+ """
37+ Extract custom config variables from conf.py for each branch/tag.
38+
39+ Needed because some variables (like myst_substitutions) aren't registered
40+ with Sphinx's config system and won't be available via Config.read().
41+
42+ This ensures each version gets its own values by executing that version's conf.py.
43+
44+ Args:
45+ confpath: Path to directory containing conf.py
46+ var_names: List of variable names to extract (e.g., ["myst_substitutions"])
47+
48+ Returns:
49+ dict: {var_name: value} for requested variables (None if not found)
50+ """
51+ conf_file = os .path .join (confpath , "conf.py" )
52+ if not os .path .exists (conf_file ):
53+ return {var_name : None for var_name in var_names }
54+
55+ logger = logging .getLogger (__name__ )
56+
57+ try :
58+ with open (conf_file , "r" , encoding = "utf-8" ) as f :
59+ content = f .read ()
60+
61+ # Execute conf.py in a minimal namespace
62+ namespace = {
63+ "__file__" : conf_file ,
64+ "__name__" : "__main__" ,
65+ "__builtins__" : __builtins__ ,
66+ }
67+
68+ # Change to confpath directory so relative imports work
69+ old_cwd = os .getcwd ()
70+ old_path = sys .path .copy ()
71+ try :
72+ os .chdir (confpath )
73+ sys .path .insert (0 , confpath )
74+
75+ # Try to execute the whole file
76+ try :
77+ exec (content , namespace )
78+ except (ImportError , ModuleNotFoundError ) as import_err :
79+ # If imports fail, try partial execution by skipping import lines
80+ logger .debug (
81+ "Import failed in %s: %s, trying partial execution" ,
82+ conf_file ,
83+ import_err ,
84+ )
85+
86+ lines = content .split ("\n " )
87+ partial_namespace = namespace .copy ()
88+ for i , line in enumerate (lines ):
89+ try :
90+ # Skip import lines that might fail
91+ if line .strip ().startswith (("import " , "from " )):
92+ continue
93+ exec ("\n " .join (lines [: i + 1 ]), partial_namespace )
94+ # Stop early if we've found all requested variables
95+ if all (var in partial_namespace for var in var_names ):
96+ namespace = partial_namespace
97+ break
98+ except Exception as _e :
99+ continue
100+
101+ # Extract requested variables
102+ return {var_name : namespace .get (var_name ) for var_name in var_names }
103+
104+ finally :
105+ os .chdir (old_cwd )
106+ sys .path = old_path
107+
108+ except Exception as err :
109+ logger .warning (
110+ "Failed to extract custom config variables from %s: %s" , conf_file , err
111+ )
112+ return {var_name : None for var_name in var_names }
113+
114+
35115def load_sphinx_config_worker (q , confpath , confoverrides , add_defaults ):
36116 try :
37117 with working_dir (confpath ):
@@ -309,7 +389,8 @@ def main(argv=None):
309389 current_sourcedir = os .path .join (repopath , sourcedir )
310390 project = sphinx_project .Project (current_sourcedir , source_suffixes )
311391
312- myst_substitutions = getattr (current_config , "myst_substitutions" , {})
392+ custom_vars = extract_custom_config_vars (confpath , ["myst_substitutions" ])
393+ myst_substitutions = custom_vars .get ("myst_substitutions" ) or {}
313394
314395 metadata [gitref .name ] = {
315396 "name" : gitref .name ,
0 commit comments