Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Kitware
GitHub Repository: Kitware/CMake
Path: blob/master/Utilities/Sphinx/cmake.py
3150 views
1
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2
# file LICENSE.rst or https://cmake.org/licensing for details.
3
4
# BEGIN imports
5
6
import os
7
import re
8
from dataclasses import dataclass
9
from typing import Any, List, Tuple, Type, cast
10
11
import sphinx
12
13
# The following imports may fail if we don't have Sphinx 2.x or later.
14
if sphinx.version_info >= (2,):
15
from docutils import io, nodes
16
from docutils.nodes import Element, Node, TextElement, system_message
17
from docutils.parsers.rst import Directive, directives
18
from docutils.transforms import Transform
19
from docutils.utils.code_analyzer import Lexer, LexerError
20
21
from sphinx import addnodes
22
from sphinx.directives import ObjectDescription, nl_escape_re
23
from sphinx.domains import Domain, ObjType
24
from sphinx.roles import XRefRole
25
from sphinx.util import logging, ws_re
26
from sphinx.util.docutils import ReferenceRole
27
from sphinx.util.nodes import make_refnode
28
else:
29
# Sphinx 2.x is required.
30
assert sphinx.version_info >= (2,)
31
32
# END imports
33
34
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35
36
# BEGIN pygments tweaks
37
38
# Override much of pygments' CMakeLexer.
39
# We need to parse CMake syntax definitions, not CMake code.
40
41
# For hard test cases that use much of the syntax below, see
42
# - module/FindPkgConfig.html
43
# (with "glib-2.0>=2.10 gtk+-2.0" and similar)
44
# - module/ExternalProject.html
45
# (with http:// https:// git@; also has command options -E --build)
46
# - manual/cmake-buildsystem.7.html
47
# (with nested $<..>; relative and absolute paths, "::")
48
49
from pygments.lexer import bygroups # noqa I100
50
from pygments.lexers import CMakeLexer
51
from pygments.token import (Comment, Name, Number, Operator, Punctuation,
52
String, Text, Whitespace)
53
54
# Notes on regular expressions below:
55
# - [\.\+-] are needed for string constants like gtk+-2.0
56
# - Unix paths are recognized by '/'; support for Windows paths may be added
57
# if needed
58
# - (\\.) allows for \-escapes (used in manual/cmake-language.7)
59
# - $<..$<..$>..> nested occurrence in cmake-buildsystem
60
# - Nested variable evaluations are only supported in a limited capacity.
61
# Only one level of nesting is supported and at most one nested variable can
62
# be present.
63
64
CMakeLexer.tokens["root"] = [
65
# fctn(
66
(r'\b(\w+)([ \t]*)(\()',
67
bygroups(Name.Function, Text, Name.Function), '#push'),
68
(r'\(', Name.Function, '#push'),
69
(r'\)', Name.Function, '#pop'),
70
(r'\[', Punctuation, '#push'),
71
(r'\]', Punctuation, '#pop'),
72
(r'[|;,.=*\-]', Punctuation),
73
# used in commands/source_group
74
(r'\\\\', Punctuation),
75
(r'[:]', Operator),
76
# used in FindPkgConfig.cmake
77
(r'[<>]=', Punctuation),
78
# $<...>
79
(r'\$<', Operator, '#push'),
80
# <expr>
81
(r'<[^<|]+?>(\w*\.\.\.)?', Name.Variable),
82
# ${..} $ENV{..}, possibly nested
83
(r'(\$\w*\{)([^\}\$]*)?(?:(\$\w*\{)([^\}]+?)(\}))?([^\}]*?)(\})',
84
bygroups(Operator, Name.Tag, Operator, Name.Tag, Operator, Name.Tag,
85
Operator)),
86
# DATA{ ...}
87
(r'([A-Z]+\{)(.+?)(\})', bygroups(Operator, Name.Tag, Operator)),
88
# URL, git@, ...
89
(r'[a-z]+(@|(://))((\\.)|[\w.+-:/\\])+', Name.Attribute),
90
# absolute path
91
(r'/\w[\w\.\+-/\\]*', Name.Attribute),
92
(r'/', Name.Attribute),
93
# relative path
94
(r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute),
95
# initial A-Z, contains a-z
96
(r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin),
97
(r'@?[A-Z][A-Z0-9_]*', Name.Constant),
98
(r'[a-z_]((\\;)|(\\ )|[\w.+-])*', Name.Builtin),
99
(r'[0-9][0-9\.]*', Number),
100
# "string"
101
(r'(?s)"(\\"|[^"])*"', String),
102
(r'\.\.\.', Name.Variable),
103
# <..|..> is different from <expr>
104
(r'<', Operator, '#push'),
105
(r'>', Operator, '#pop'),
106
(r'\n', Whitespace),
107
(r'[ \t]+', Whitespace),
108
(r'#.*\n', Comment),
109
# fallback, for debugging only
110
# (r'[^<>\])\}\|$"# \t\n]+', Name.Exception),
111
]
112
113
# END pygments tweaks
114
115
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
116
117
logger = logging.getLogger(__name__)
118
119
# RE to split multiple command signatures.
120
sig_end_re = re.compile(r'(?<=[)])\n')
121
122
123
@dataclass
124
class ObjectEntry:
125
docname: str
126
objtype: str
127
node_id: str
128
name: str
129
130
131
class CMakeModule(Directive):
132
required_arguments = 1
133
optional_arguments = 0
134
final_argument_whitespace = True
135
option_spec = {'encoding': directives.encoding}
136
137
def __init__(self, *args, **keys):
138
self.re_start = re.compile(r'^#\[(?P<eq>=*)\[\.rst:$')
139
Directive.__init__(self, *args, **keys)
140
141
def run(self):
142
settings = self.state.document.settings
143
if not settings.file_insertion_enabled:
144
raise self.warning(f'{self.name!r} directive disabled.')
145
146
env = self.state.document.settings.env
147
rel_path, path = env.relfn2path(self.arguments[0])
148
path = os.path.normpath(path)
149
encoding = self.options.get('encoding', settings.input_encoding)
150
e_handler = settings.input_encoding_error_handler
151
try:
152
settings.record_dependencies.add(path)
153
f = io.FileInput(source_path=path, encoding=encoding,
154
error_handler=e_handler)
155
except UnicodeEncodeError:
156
msg = (f'Problems with {self.name!r} directive path:\n'
157
f'Cannot encode input file path {path!r} (wrong locale?).')
158
raise self.severe(msg)
159
except IOError as error:
160
msg = f'Problems with {self.name!r} directive path:\n{error}.'
161
raise self.severe(msg)
162
raw_lines = f.read().splitlines()
163
f.close()
164
rst = None
165
lines = []
166
for line in raw_lines:
167
if rst is not None and rst != '#':
168
# Bracket mode: check for end bracket
169
pos = line.find(rst)
170
if pos >= 0:
171
if line[0] == '#':
172
line = ''
173
else:
174
line = line[0:pos]
175
rst = None
176
else:
177
# Line mode: check for .rst start (bracket or line)
178
m = self.re_start.match(line)
179
if m:
180
rst = f']{m.group("eq")}]'
181
line = ''
182
elif line == '#.rst:':
183
rst = '#'
184
line = ''
185
elif rst == '#':
186
if line == '#' or line[:2] == '# ':
187
line = line[2:]
188
else:
189
rst = None
190
line = ''
191
elif rst is None:
192
line = ''
193
lines.append(line)
194
if rst is not None and rst != '#':
195
raise self.warning(f'{self.name!r} found unclosed bracket '
196
f'"#[{rst[1:-1]}[.rst:" in {path!r}')
197
self.state_machine.insert_input(lines, path)
198
return []
199
200
201
class _cmake_index_entry:
202
def __init__(self, desc):
203
self.desc = desc
204
205
def __call__(self, title, targetid, main='main'):
206
return ('pair', f'{self.desc} ; {title}', targetid, main, None)
207
208
209
_cmake_index_objs = {
210
'command': _cmake_index_entry('command'),
211
'cpack_gen': _cmake_index_entry('cpack generator'),
212
'envvar': _cmake_index_entry('envvar'),
213
'generator': _cmake_index_entry('generator'),
214
'genex': _cmake_index_entry('genex'),
215
'guide': _cmake_index_entry('guide'),
216
'manual': _cmake_index_entry('manual'),
217
'module': _cmake_index_entry('module'),
218
'policy': _cmake_index_entry('policy'),
219
'prop_cache': _cmake_index_entry('cache property'),
220
'prop_dir': _cmake_index_entry('directory property'),
221
'prop_gbl': _cmake_index_entry('global property'),
222
'prop_inst': _cmake_index_entry('installed file property'),
223
'prop_sf': _cmake_index_entry('source file property'),
224
'prop_test': _cmake_index_entry('test property'),
225
'prop_tgt': _cmake_index_entry('target property'),
226
'variable': _cmake_index_entry('variable'),
227
}
228
229
230
class CMakeTransform(Transform):
231
232
# Run this transform early since we insert nodes we want
233
# treated as if they were written in the documents.
234
default_priority = 210
235
236
def __init__(self, document, startnode):
237
Transform.__init__(self, document, startnode)
238
self.titles = {}
239
240
def parse_title(self, docname):
241
"""Parse a document title as the first line starting in [A-Za-z0-9<$]
242
or fall back to the document basename if no such line exists.
243
The cmake --help-*-list commands also depend on this convention.
244
Return the title or False if the document file does not exist.
245
"""
246
settings = self.document.settings
247
env = settings.env
248
title = self.titles.get(docname)
249
if title is None:
250
fname = os.path.join(env.srcdir, docname+'.rst')
251
try:
252
f = open(fname, 'r', encoding=settings.input_encoding)
253
except IOError:
254
title = False
255
else:
256
for line in f:
257
if len(line) > 0 and (line[0].isalnum() or
258
line[0] == '<' or line[0] == '$'):
259
title = line.rstrip()
260
break
261
f.close()
262
if title is None:
263
title = os.path.basename(docname)
264
self.titles[docname] = title
265
return title
266
267
def apply(self):
268
env = self.document.settings.env
269
270
# Treat some documents as cmake domain objects.
271
objtype, sep, tail = env.docname.partition('/')
272
make_index_entry = _cmake_index_objs.get(objtype)
273
if make_index_entry:
274
title = self.parse_title(env.docname)
275
# Insert the object link target.
276
if objtype == 'command':
277
targetname = title.lower()
278
elif objtype == 'guide' and not tail.endswith('/index'):
279
targetname = tail
280
else:
281
if objtype == 'genex':
282
m = CMakeXRefRole._re_genex.match(title)
283
if m:
284
title = m.group(1)
285
targetname = title
286
targetid = f'{objtype}:{targetname}'
287
targetnode = nodes.target('', '', ids=[targetid])
288
self.document.note_explicit_target(targetnode)
289
self.document.insert(0, targetnode)
290
# Insert the object index entry.
291
indexnode = addnodes.index()
292
indexnode['entries'] = [make_index_entry(title, targetid)]
293
self.document.insert(0, indexnode)
294
295
# Add to cmake domain object inventory
296
domain = cast(CMakeDomain, env.get_domain('cmake'))
297
domain.note_object(objtype, targetname, targetid, targetid)
298
299
300
class CMakeObject(ObjectDescription):
301
def __init__(self, *args, **kwargs):
302
self.targetname = None
303
super().__init__(*args, **kwargs)
304
305
def handle_signature(self, sig, signode):
306
# called from sphinx.directives.ObjectDescription.run()
307
signode += addnodes.desc_name(sig, sig)
308
return sig
309
310
def add_target_and_index(self, name, sig, signode):
311
if self.objtype == 'command':
312
targetname = name.lower()
313
elif self.targetname:
314
targetname = self.targetname
315
else:
316
targetname = name
317
targetid = f'{self.objtype}:{targetname}'
318
if targetid not in self.state.document.ids:
319
signode['names'].append(targetid)
320
signode['ids'].append(targetid)
321
signode['first'] = (not self.names)
322
self.state.document.note_explicit_target(signode)
323
324
domain = cast(CMakeDomain, self.env.get_domain('cmake'))
325
domain.note_object(self.objtype, targetname, targetid, targetid,
326
location=signode)
327
328
make_index_entry = _cmake_index_objs.get(self.objtype)
329
if make_index_entry:
330
self.indexnode['entries'].append(make_index_entry(name, targetid))
331
332
333
class CMakeGenexObject(CMakeObject):
334
option_spec = {
335
'target': directives.unchanged,
336
}
337
338
def handle_signature(self, sig, signode):
339
name = super().handle_signature(sig, signode)
340
341
m = CMakeXRefRole._re_genex.match(sig)
342
if m:
343
name = m.group(1)
344
345
return name
346
347
def run(self):
348
target = self.options.get('target')
349
if target is not None:
350
self.targetname = target
351
352
return super().run()
353
354
355
class CMakeSignatureObject(CMakeObject):
356
object_type = 'signature'
357
358
BREAK_ALL = 'all'
359
BREAK_SMART = 'smart'
360
BREAK_VERBATIM = 'verbatim'
361
362
BREAK_CHOICES = {BREAK_ALL, BREAK_SMART, BREAK_VERBATIM}
363
364
def break_option(argument):
365
return directives.choice(argument, CMakeSignatureObject.BREAK_CHOICES)
366
367
option_spec = {
368
'target': directives.unchanged,
369
'break': break_option,
370
}
371
372
def _break_signature_all(sig: str) -> str:
373
return ws_re.sub(' ', sig)
374
375
def _break_signature_verbatim(sig: str) -> str:
376
lines = [ws_re.sub('\xa0', line.strip()) for line in sig.split('\n')]
377
return ' '.join(lines)
378
379
def _break_signature_smart(sig: str) -> str:
380
tokens = []
381
for line in sig.split('\n'):
382
token = ''
383
delim = ''
384
385
for c in line.strip():
386
if len(delim) == 0 and ws_re.match(c):
387
if len(token):
388
tokens.append(ws_re.sub('\xa0', token))
389
token = ''
390
else:
391
if c == '[':
392
delim += ']'
393
elif c == '<':
394
delim += '>'
395
elif len(delim) and c == delim[-1]:
396
delim = delim[:-1]
397
token += c
398
399
if len(token):
400
tokens.append(ws_re.sub('\xa0', token))
401
402
return ' '.join(tokens)
403
404
def __init__(self, *args, **kwargs):
405
self.targetnames = {}
406
self.break_style = CMakeSignatureObject.BREAK_SMART
407
super().__init__(*args, **kwargs)
408
409
def get_signatures(self) -> List[str]:
410
content = nl_escape_re.sub('', self.arguments[0])
411
lines = sig_end_re.split(content)
412
413
if self.break_style == CMakeSignatureObject.BREAK_VERBATIM:
414
fixup = CMakeSignatureObject._break_signature_verbatim
415
elif self.break_style == CMakeSignatureObject.BREAK_SMART:
416
fixup = CMakeSignatureObject._break_signature_smart
417
else:
418
fixup = CMakeSignatureObject._break_signature_all
419
420
return [fixup(line.strip()) for line in lines]
421
422
def handle_signature(self, sig, signode):
423
language = 'cmake'
424
classes = ['code', 'cmake', 'highlight']
425
426
node = addnodes.desc_name(sig, '', classes=classes)
427
428
try:
429
tokens = Lexer(sig, language, 'short')
430
except LexerError as error:
431
if self.state.document.settings.report_level > 2:
432
# Silently insert without syntax highlighting.
433
tokens = Lexer(sig, language, 'none')
434
else:
435
raise self.warning(error)
436
437
for classes, value in tokens:
438
if value == '\xa0':
439
node += nodes.inline(value, value, classes=['nbsp'])
440
elif classes:
441
node += nodes.inline(value, value, classes=classes)
442
else:
443
node += nodes.Text(value)
444
445
signode.clear()
446
signode += node
447
448
return sig
449
450
def add_target_and_index(self, name, sig, signode):
451
sig = sig.replace('\xa0', ' ')
452
if sig in self.targetnames:
453
sigargs = self.targetnames[sig]
454
else:
455
def extract_keywords(params):
456
for p in params:
457
if p[0].isalpha():
458
yield p
459
else:
460
return
461
462
keywords = extract_keywords(sig.split('(')[1].split())
463
sigargs = ' '.join(keywords)
464
targetname = sigargs.lower()
465
targetid = nodes.make_id(targetname)
466
467
if targetid not in self.state.document.ids:
468
signode['names'].append(targetname)
469
signode['ids'].append(targetid)
470
signode['first'] = (not self.names)
471
self.state.document.note_explicit_target(signode)
472
473
# Register the signature as a command object.
474
command = sig.split('(')[0].lower()
475
refname = f'{command}({sigargs})'
476
refid = f'command:{command}({targetname})'
477
478
domain = cast(CMakeDomain, self.env.get_domain('cmake'))
479
domain.note_object('command', name=refname, target_id=refid,
480
node_id=targetid, location=signode)
481
482
def run(self):
483
self.break_style = CMakeSignatureObject.BREAK_ALL
484
485
targets = self.options.get('target')
486
if targets is not None:
487
signatures = self.get_signatures()
488
targets = [t.strip() for t in targets.split('\n')]
489
for signature, target in zip(signatures, targets):
490
self.targetnames[signature] = target
491
492
self.break_style = (
493
self.options.get('break', CMakeSignatureObject.BREAK_SMART))
494
495
return super().run()
496
497
498
class CMakeReferenceRole:
499
# See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'.
500
_re = re.compile(r'^(.+?)(\s*)(?<!\x00)<(.*?)>$', re.DOTALL)
501
502
@staticmethod
503
def _escape_angle_brackets(text: str) -> str:
504
# CMake cross-reference targets frequently contain '<' so escape
505
# any explicit `<target>` with '<' not preceded by whitespace.
506
while True:
507
m = CMakeReferenceRole._re.match(text)
508
if m and len(m.group(2)) == 0:
509
text = f'{m.group(1)}\x00<{m.group(3)}>'
510
else:
511
break
512
return text
513
514
def __class_getitem__(cls, parent: Any):
515
class Class(parent):
516
def __call__(self, name: str, rawtext: str, text: str,
517
*args, **kwargs
518
) -> Tuple[List[Node], List[system_message]]:
519
text = CMakeReferenceRole._escape_angle_brackets(text)
520
return super().__call__(name, rawtext, text, *args, **kwargs)
521
return Class
522
523
524
class CMakeCRefRole(CMakeReferenceRole[ReferenceRole]):
525
nodeclass: Type[Element] = nodes.reference
526
innernodeclass: Type[TextElement] = nodes.literal
527
classes: List[str] = ['cmake', 'literal']
528
529
def run(self) -> Tuple[List[Node], List[system_message]]:
530
refnode = self.nodeclass(self.rawtext)
531
self.set_source_info(refnode)
532
533
refnode['refid'] = nodes.make_id(self.target)
534
refnode += self.innernodeclass(self.rawtext, self.title,
535
classes=self.classes)
536
537
return [refnode], []
538
539
540
class CMakeXRefRole(CMakeReferenceRole[XRefRole]):
541
542
_re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL)
543
_re_genex = re.compile(r'^\$<([^<>:]+)(:[^<>]+)?>$', re.DOTALL)
544
_re_guide = re.compile(r'^([^<>/]+)/([^<>]*)$', re.DOTALL)
545
546
def __call__(self, typ, rawtext, text, *args, **kwargs):
547
if typ == 'cmake:command':
548
# Translate a CMake command cross-reference of the form:
549
# `command_name(SUB_COMMAND)`
550
# to be its own explicit target:
551
# `command_name(SUB_COMMAND) <command_name(SUB_COMMAND)>`
552
# so the XRefRole `fix_parens` option does not add more `()`.
553
m = CMakeXRefRole._re_sub.match(text)
554
if m:
555
text = f'{text} <{text}>'
556
elif typ == 'cmake:genex':
557
m = CMakeXRefRole._re_genex.match(text)
558
if m:
559
text = f'{text} <{m.group(1)}>'
560
elif typ == 'cmake:guide':
561
m = CMakeXRefRole._re_guide.match(text)
562
if m:
563
text = f'{m.group(2)} <{text}>'
564
return super().__call__(typ, rawtext, text, *args, **kwargs)
565
566
# We cannot insert index nodes using the result_nodes method
567
# because CMakeXRefRole is processed before substitution_reference
568
# nodes are evaluated so target nodes (with 'ids' fields) would be
569
# duplicated in each evaluated substitution replacement. The
570
# docutils substitution transform does not allow this. Instead we
571
# use our own CMakeXRefTransform below to add index entries after
572
# substitutions are completed.
573
#
574
# def result_nodes(self, document, env, node, is_ref):
575
# pass
576
577
578
class CMakeXRefTransform(Transform):
579
580
# Run this transform early since we insert nodes we want
581
# treated as if they were written in the documents, but
582
# after the sphinx (210) and docutils (220) substitutions.
583
default_priority = 221
584
585
# This helper supports docutils < 0.18, which is missing 'findall',
586
# and docutils == 0.18.0, which is missing 'traverse'.
587
def _document_findall_as_list(self, condition):
588
if hasattr(self.document, 'findall'):
589
# Fully iterate into a list so the caller can grow 'self.document'
590
# while iterating.
591
return list(self.document.findall(condition))
592
593
# Fallback to 'traverse' on old docutils, which returns a list.
594
return self.document.traverse(condition)
595
596
def apply(self):
597
env = self.document.settings.env
598
599
# Find CMake cross-reference nodes and add index and target
600
# nodes for them.
601
for ref in self._document_findall_as_list(addnodes.pending_xref):
602
if not ref['refdomain'] == 'cmake':
603
continue
604
605
objtype = ref['reftype']
606
make_index_entry = _cmake_index_objs.get(objtype)
607
if not make_index_entry:
608
continue
609
610
objname = ref['reftarget']
611
if objtype == 'guide' and CMakeXRefRole._re_guide.match(objname):
612
# Do not index cross-references to guide sections.
613
continue
614
615
if objtype == 'command':
616
# Index signature references to their parent command.
617
objname = objname.split('(')[0].lower()
618
619
targetnum = env.new_serialno(f'index-{objtype}:{objname}')
620
621
targetid = f'index-{targetnum}-{objtype}:{objname}'
622
targetnode = nodes.target('', '', ids=[targetid])
623
self.document.note_explicit_target(targetnode)
624
625
indexnode = addnodes.index()
626
indexnode['entries'] = [make_index_entry(objname, targetid, '')]
627
ref.replace_self([indexnode, targetnode, ref])
628
629
630
class CMakeDomain(Domain):
631
"""CMake domain."""
632
name = 'cmake'
633
label = 'CMake'
634
object_types = {
635
'command': ObjType('command', 'command'),
636
'cpack_gen': ObjType('cpack_gen', 'cpack_gen'),
637
'envvar': ObjType('envvar', 'envvar'),
638
'generator': ObjType('generator', 'generator'),
639
'genex': ObjType('genex', 'genex'),
640
'guide': ObjType('guide', 'guide'),
641
'variable': ObjType('variable', 'variable'),
642
'module': ObjType('module', 'module'),
643
'policy': ObjType('policy', 'policy'),
644
'prop_cache': ObjType('prop_cache', 'prop_cache'),
645
'prop_dir': ObjType('prop_dir', 'prop_dir'),
646
'prop_gbl': ObjType('prop_gbl', 'prop_gbl'),
647
'prop_inst': ObjType('prop_inst', 'prop_inst'),
648
'prop_sf': ObjType('prop_sf', 'prop_sf'),
649
'prop_test': ObjType('prop_test', 'prop_test'),
650
'prop_tgt': ObjType('prop_tgt', 'prop_tgt'),
651
'manual': ObjType('manual', 'manual'),
652
}
653
directives = {
654
'command': CMakeObject,
655
'envvar': CMakeObject,
656
'genex': CMakeGenexObject,
657
'signature': CMakeSignatureObject,
658
'variable': CMakeObject,
659
# Other `object_types` cannot be created except by the `CMakeTransform`
660
}
661
roles = {
662
'cref': CMakeCRefRole(),
663
'command': CMakeXRefRole(fix_parens=True, lowercase=True),
664
'cpack_gen': CMakeXRefRole(),
665
'envvar': CMakeXRefRole(),
666
'generator': CMakeXRefRole(),
667
'genex': CMakeXRefRole(),
668
'guide': CMakeXRefRole(),
669
'variable': CMakeXRefRole(),
670
'module': CMakeXRefRole(),
671
'policy': CMakeXRefRole(),
672
'prop_cache': CMakeXRefRole(),
673
'prop_dir': CMakeXRefRole(),
674
'prop_gbl': CMakeXRefRole(),
675
'prop_inst': CMakeXRefRole(),
676
'prop_sf': CMakeXRefRole(),
677
'prop_test': CMakeXRefRole(),
678
'prop_tgt': CMakeXRefRole(),
679
'manual': CMakeXRefRole(),
680
}
681
initial_data = {
682
'objects': {}, # fullname -> ObjectEntry
683
}
684
685
def clear_doc(self, docname):
686
to_clear = set()
687
for fullname, obj in self.data['objects'].items():
688
if obj.docname == docname:
689
to_clear.add(fullname)
690
for fullname in to_clear:
691
del self.data['objects'][fullname]
692
693
def merge_domaindata(self, docnames, otherdata):
694
"""Merge domaindata from the workers/chunks when they return.
695
696
Called once per parallelization chunk.
697
Only used when sphinx is run in parallel mode.
698
699
:param docnames: a Set of the docnames that are part of the current
700
chunk to merge
701
:param otherdata: the partial data calculated by the current chunk
702
"""
703
for refname, obj in otherdata['objects'].items():
704
if obj.docname in docnames:
705
self.data['objects'][refname] = obj
706
707
def resolve_xref(self, env, fromdocname, builder,
708
typ, target, node, contnode):
709
targetid = f'{typ}:{target}'
710
obj = self.data['objects'].get(targetid)
711
712
if obj is None and typ == 'command':
713
# If 'command(args)' wasn't found, try just 'command'.
714
# TODO: remove this fallback? warn?
715
# logger.warning(f'no match for {targetid}')
716
command = target.split('(')[0]
717
targetid = f'{typ}:{command}'
718
obj = self.data['objects'].get(targetid)
719
720
if obj is None:
721
# TODO: warn somehow?
722
return None
723
724
return make_refnode(builder, fromdocname, obj.docname, obj.node_id,
725
contnode, target)
726
727
def note_object(self, objtype: str, name: str, target_id: str,
728
node_id: str, location: Any = None):
729
if target_id in self.data['objects']:
730
other = self.data['objects'][target_id].docname
731
logger.warning(
732
f'CMake object {target_id!r} also described in {other!r}',
733
location=location)
734
735
self.data['objects'][target_id] = ObjectEntry(
736
self.env.docname, objtype, node_id, name)
737
738
def get_objects(self):
739
for refname, obj in self.data['objects'].items():
740
yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
741
742
743
def setup(app):
744
app.add_directive('cmake-module', CMakeModule)
745
app.add_transform(CMakeTransform)
746
app.add_transform(CMakeXRefTransform)
747
app.add_domain(CMakeDomain)
748
return {"parallel_read_safe": True}
749
750