cmake.py 26 KB

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