| #!/usr/bin/env python3 |
| |
| # Copyright 2018 The Fuchsia Authors |
| # |
| # Use of this source code is governed by a MIT-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/MIT |
| """ |
| This tool uses the contents of the kazoo-generated syscalls/definitions.json |
| to update docs/syscalls/. |
| |
| It is not run automatically as part of the build for now (to allow confirmation |
| of what it does). So it should be run manually after updating //zircon/vdso |
| and building zircon, followed by uploading the changes to docs/ as a CL. (It |
| will attempt to do a build if it appears that definitions.json is out-of-date |
| with respect to the syscall fidl.) |
| |
| It updates the signature, synopsis, and rights annotations, and corrects some |
| formatting. |
| """ |
| |
| import argparse |
| import json |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) |
| |
| STANDARD_COMMENT = \ |
| '<!-- Contents of this heading updated by update-docs-from-fidl, do not edit. -->' |
| STANDARD_BLOCK_HEADER = ['', STANDARD_COMMENT, ''] |
| |
| REFERENCES_COMMENT = \ |
| '<!-- References updated by update-docs-from-fidl, do not edit. -->' |
| |
| SYSCALLS_FIDL_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, |
| 'vdso')) |
| |
| |
| def parse_args(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter) |
| parser.add_argument( |
| '--builddir', |
| default=os.path.join('out','default'), |
| help='overrides default build directory if set') |
| parser.add_argument('--json', |
| default=None, |
| help='path to kazoo .json output') |
| parser.add_argument('--docroot', |
| default=os.path.normpath( |
| os.path.join(SCRIPT_DIR, os.pardir, os.pardir, |
| 'docs', 'reference', 'syscalls')), |
| help='root of syscalls/ to be updated') |
| parser.add_argument( |
| '--generate-missing', |
| default=False, |
| action="store_true", |
| help='if set, generate stubs for any syscalls that are missing') |
| parser.add_argument('name', nargs='*', help='only generate these syscalls') |
| return parser.parse_args() |
| |
| |
| def break_into_sentences(stream): |
| """Partition on '.' to break into chunks. '.' can't appear elsewhere |
| in the input stream.""" |
| sentences = [] |
| cur = [] |
| for tok in stream: |
| cur.append(tok) |
| if tok == '.': |
| sentences.append(cur) |
| cur = [] |
| assert not cur, cur |
| return sentences |
| |
| |
| def match_sentence_form(sentence, arg_names): |
| """Matches a known sentence form, returning a format string and a dict for |
| substitution. The values in dict are converted to markdown format. |
| |
| Certain TERMINALS are special: |
| - ARG must appear in arg_names |
| - RIGHT must be a valid ZX_RIGHT_ |
| - TYPE must be a valid ZX_OBJ_TYPE_ |
| - RSRC must be a valid ZX_RSRC_KIND_ |
| - POLCOND must be a valid ZX_POL_ |
| |
| VALUE is a generic unchecked value type, used for masks, options, etc. |
| """ |
| sentence_forms = [ |
| ['None', '.'], |
| ['ARG', 'must', 'have', 'RIGHT', '.'], |
| ['ARG', 'must', 'have', 'RIGHT1', 'and', 'have', 'RIGHT2', '.'], |
| [ |
| 'ARG', 'must', 'have', 'RIGHT1', 'and', 'have', 'RIGHT2', 'and', |
| 'have', 'RIGHT3', '.' |
| ], |
| [ |
| 'ARG', 'must', 'have', 'RIGHT1', 'and', 'have', 'RIGHT2', 'and', |
| 'have', 'RIGHT3', 'and', 'have', 'RIGHT4', '.' |
| ], |
| ['ARG', 'must', 'have', 'resource', 'kind', 'RSRC', '.'], |
| ['ARG', 'must', 'be', 'of', 'type', 'TYPE', '.'], |
| [ |
| 'ARG', 'must', 'be', 'of', 'type', 'TYPE', 'and', 'have', 'RIGHT1', |
| 'and', 'have', 'RIGHT2', '.' |
| ], |
| [ |
| 'ARG', 'must', 'be', 'of', 'type', 'TYPE', 'and', 'have', 'RIGHT', |
| '.' |
| ], |
| [ |
| 'ARG', 'must', 'be', 'of', 'type', 'TYPE', 'and', 'have', 'RIGHT', |
| 'or', 'have', 'RIGHT', '.' |
| ], |
| [ |
| 'ARG', 'must', 'be', 'of', 'type', 'TYPE1', 'or', 'TYPE2', 'and', |
| 'have', 'RIGHT', '.' |
| ], |
| [ |
| 'If', 'ARG', 'is', 'of', 'type', 'TYPE1', 'or', 'TYPE2', ',', 'it', |
| 'must', 'have', 'RIGHT', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'have', 'RIGHT', |
| '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'have', |
| 'resource', 'kind', 'RSRC', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'be', 'of', |
| 'type', 'TYPE', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'be', 'of', |
| 'type', 'TYPE', 'and', 'have', 'RIGHT', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'be', 'of', |
| 'type', 'TYPE1', ',', 'TYPE2', ',', 'or', 'TYPE3', ',', 'and', 'have', 'RIGHT', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'be', 'of', |
| 'type', 'TYPE', 'and', 'have', 'RIGHT1', 'and', 'RIGHT2', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'ARG2', 'must', 'be', 'of', |
| 'type', 'TYPE', 'and', 'have', 'RIGHT1', 'and', 'RIGHT2', 'and', |
| 'RIGHT3', '.' |
| ], |
| [ |
| 'If', 'ARG1', '&', 'VALUE', ',', 'ARG2', 'must', 'be', 'of', 'type', |
| 'TYPE', 'and', 'have', 'RIGHT', '.' |
| ], |
| ['Every', 'entry', 'of', 'ARG', 'must', 'have', 'RIGHT', '.'], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE', ',', 'affected', 'mappings', 'must', |
| 'be', 'writable', '.' |
| ], |
| [ |
| 'Every', 'entry', 'of', 'ARG', 'must', 'have', 'a', |
| 'WAITITEMMEMBER', 'field', 'with', 'RIGHT', '.' |
| ], |
| [ |
| 'If', 'ARG1', 'is', 'VALUE1', ',', 'ARG2', 'must', 'have', |
| 'resource', 'kind', 'RSRC', 'with', 'base', 'VALUE2', '.' |
| ], |
| [ |
| 'ARG', 'must', 'have', 'resource', 'kind', 'RSRC', 'with', 'base', |
| 'VALUE', '.' |
| ], |
| [ 'Caller', 'job', 'policy', 'must', 'allow', 'POLCOND', '.'], |
| # TODO(fxbug.dev/32253) TODO(scottmg): This is a hack specifically for |
| # zx_channel_call_args_t. Trying to make a pseudo-generic case (that |
| # handles the length from wr_num_handles, etc.) for this doesn't seem |
| # worth the trouble at the moment, since it's only checking that the |
| # handles have TRANSFER anyway. Revisit if/when there's more instances |
| # like this. |
| ['All', 'wr_handles', 'of', 'ARG', 'must', 'have', 'RIGHT', '.'], |
| ] |
| |
| all_rights = set([ |
| 'ZX_RIGHT_NONE', |
| 'ZX_RIGHT_DUPLICATE', |
| 'ZX_RIGHT_TRANSFER', |
| 'ZX_RIGHT_READ', |
| 'ZX_RIGHT_WRITE', |
| 'ZX_RIGHT_EXECUTE', |
| 'ZX_RIGHT_MAP', |
| 'ZX_RIGHT_GET_PROPERTY', |
| 'ZX_RIGHT_SET_PROPERTY', |
| 'ZX_RIGHT_ENUMERATE', |
| 'ZX_RIGHT_DESTROY', |
| 'ZX_RIGHT_SET_POLICY', |
| 'ZX_RIGHT_GET_POLICY', |
| 'ZX_RIGHT_SIGNAL', |
| 'ZX_RIGHT_SIGNAL_PEER', |
| 'ZX_RIGHT_WAIT', |
| 'ZX_RIGHT_INSPECT', |
| 'ZX_RIGHT_MANAGE_JOB', |
| 'ZX_RIGHT_MANAGE_PROCESS', |
| 'ZX_RIGHT_MANAGE_THREAD', |
| 'ZX_RIGHT_APPLY_PROFILE', |
| 'ZX_RIGHT_MANAGE_SOCKET', |
| ]) |
| |
| all_types = set([ |
| 'ZX_OBJ_TYPE_BTI', |
| 'ZX_OBJ_TYPE_CHANNEL', |
| 'ZX_OBJ_TYPE_CLOCK', |
| 'ZX_OBJ_TYPE_EVENT', |
| 'ZX_OBJ_TYPE_EVENTPAIR', |
| 'ZX_OBJ_TYPE_EXCEPTION', |
| 'ZX_OBJ_TYPE_FIFO', |
| 'ZX_OBJ_TYPE_GUEST', |
| 'ZX_OBJ_TYPE_INTERRUPT', |
| 'ZX_OBJ_TYPE_IOMMU', |
| 'ZX_OBJ_TYPE_JOB', |
| 'ZX_OBJ_TYPE_LOG', |
| 'ZX_OBJ_TYPE_MSI', |
| 'ZX_OBJ_TYPE_PAGER', |
| 'ZX_OBJ_TYPE_PCI_DEVICE', |
| 'ZX_OBJ_TYPE_PMT', |
| 'ZX_OBJ_TYPE_PORT', |
| 'ZX_OBJ_TYPE_PROCESS', |
| 'ZX_OBJ_TYPE_PROFILE', |
| 'ZX_OBJ_TYPE_RESOURCE', |
| 'ZX_OBJ_TYPE_SOCKET', |
| 'ZX_OBJ_TYPE_STREAM', |
| 'ZX_OBJ_TYPE_SUSPEND_TOKEN', |
| 'ZX_OBJ_TYPE_THREAD', |
| 'ZX_OBJ_TYPE_TIMER', |
| 'ZX_OBJ_TYPE_VCPU', |
| 'ZX_OBJ_TYPE_VMAR', |
| 'ZX_OBJ_TYPE_VMO', |
| ]) |
| |
| all_rsrcs = set([ |
| 'ZX_RSRC_KIND_MMIO', |
| 'ZX_RSRC_KIND_IRQ', |
| 'ZX_RSRC_KIND_IOPORT', |
| 'ZX_RSRC_KIND_HYPERVISOR', |
| 'ZX_RSRC_KIND_ROOT', |
| 'ZX_RSRC_KIND_VMEX', |
| 'ZX_RSRC_KIND_SMC', |
| 'ZX_RSRC_KIND_SYSTEM', |
| ]) |
| |
| all_new_obj_polcond = set([ |
| 'ZX_POL_NEW_VMO', |
| 'ZX_POL_NEW_CHANNEL', |
| 'ZX_POL_NEW_EVENT', |
| 'ZX_POL_NEW_EVENTPAIR', |
| 'ZX_POL_NEW_PORT', |
| 'ZX_POL_NEW_SOCKET', |
| 'ZX_POL_NEW_FIFO', |
| 'ZX_POL_NEW_TIMER', |
| 'ZX_POL_NEW_PROCESS', |
| 'ZX_POL_NEW_PROFILE', |
| 'ZX_POL_NEW_PAGER', |
| ]) |
| |
| # There's only two structs in zircon/types.h, so hardcoding this here is |
| # a bit stinky, but probably OK. |
| members_of_zx_wait_item_t = set([ |
| 'handle', |
| 'waitfor', |
| 'pending', |
| ]) |
| |
| for form in sentence_forms: |
| result_fmt = '' |
| result_values = {} |
| for f, s in zip(form, sentence): |
| # Literal match. |
| if s == f: |
| if f == '.' or f == ',' or f == '->': |
| result_fmt += f |
| elif f == '[': |
| result_fmt += '\[' |
| else: |
| result_fmt += ' ' + f |
| elif f.startswith('ARG'): |
| if s not in arg_names: |
| break |
| else: |
| result_values[f] = '*' + s + '*' |
| result_fmt += ' %(' + f + ')s' |
| elif f.startswith('VALUE'): |
| # TODO(scottmg): Worth checking these in some way? |
| result_fmt += ' %(' + f + ')s' |
| result_values[f] = '**' + s + '**' |
| elif f.startswith('RIGHT'): |
| if s not in all_rights: |
| break |
| result_fmt += ' %(' + f + ')s' |
| result_values[f] = '**' + s + '**' |
| elif f.startswith('RSRC'): |
| if s not in all_rsrcs: |
| break |
| result_fmt += ' %(' + f + ')s' |
| result_values[f] = '**' + s + '**' |
| elif f.startswith('TYPE'): |
| if s not in all_types: |
| break |
| result_fmt += ' %(' + f + ')s' |
| result_values[f] = '**' + s + '**' |
| elif f.startswith('WAITITEMMEMBER'): |
| if s not in members_of_zx_wait_item_t: |
| break |
| result_fmt += ' %(' + f + ')s' |
| result_values[f] = '*' + s + '*' |
| elif f.startswith('POLCOND'): |
| if s not in all_new_obj_polcond: |
| break |
| result_fmt += ' %(' + f + ')s' |
| result_values[f] = '**' + s + '**' |
| else: |
| break |
| else: |
| if result_fmt[0] == ' ': |
| result_fmt = result_fmt[1:] |
| return result_fmt, result_values |
| else: |
| return None, None |
| |
| |
| def to_markdown(req, arguments, warn): |
| """Parses a few known forms of rules (see match_sentence_forms). |
| |
| Converts |req| to formatted markdown. |
| """ |
| sentences = break_into_sentences(req) |
| |
| if not sentences: |
| rights = ['TODO(fxbug.dev/32253)', ''] |
| else: |
| rights = [] |
| for sentence in sentences: |
| match_fmt, match_values = match_sentence_form( |
| sentence, [x['name'] for x in arguments]) |
| if not match_fmt: |
| warn('failed to parse: ' + repr(sentence)) |
| raise SystemExit(1) |
| else: |
| rights.append(match_fmt % match_values) |
| rights.append('') |
| |
| return STANDARD_BLOCK_HEADER + rights |
| |
| |
| def find_block(lines, name): |
| """Finds a .md block with the given name, and returns (start, end) line |
| indices. |
| """ |
| start_index = -1 |
| end_index = -1 |
| for i, line in enumerate(lines): |
| if line == '## ' + name: |
| start_index = i + 1 |
| elif ((start_index >= 0 and line.startswith('## ')) or |
| line == REFERENCES_COMMENT): |
| end_index = i |
| break |
| return start_index, end_index |
| |
| |
| def update_rights(lines, syscall_data, warn): |
| """Updates the RIGHTS block of the .md file in lines. |
| """ |
| rights_start_index, rights_end_index = find_block(lines, 'RIGHTS') |
| if rights_start_index == -1 or rights_end_index == -1: |
| warn('did not find RIGHTS section, skipping update') |
| return |
| |
| lines[rights_start_index:rights_end_index] = to_markdown( |
| syscall_data['requirements'], syscall_data['arguments'], warn) |
| |
| |
| def make_name_block(syscall_data, warn): |
| desc = '' |
| for x in syscall_data['top_description']: |
| # TODO(scottmg): This is gross, we should change the output to give us a |
| # string instead of tokens. |
| if x in (',', '.', '-', '/', '\'', ')'): |
| desc += x |
| else: |
| if desc and desc[-1] not in ('-', '/', '\'', '('): |
| desc += ' ' |
| desc += x |
| if not desc: |
| desc = 'TODO(fxbug.dev/32938)' |
| else: |
| if desc[0].upper() != desc[0]: |
| warn('short description (#^) should start with a capital') |
| if desc[-1] != '.': |
| warn('short description (#^) should end with a period') |
| return STANDARD_BLOCK_HEADER + [desc, ''] |
| |
| |
| def update_name(lines, syscall_data, warn): |
| """Updates the NAME block of the .md file in lines. |
| """ |
| name_start_index, name_end_index = find_block(lines, 'NAME') |
| if name_start_index == -1 or name_end_index == -1: |
| warn('did not find NAME section, skipping update') |
| return |
| |
| lines[name_start_index:name_end_index] = make_name_block(syscall_data, warn) |
| |
| |
| def make_synopsis_block(syscall_data, warn): |
| # Construct a synopsis block of the form: |
| # |
| # ```c |
| # #include <zircon/syscalls.h> |
| # |
| # zx_status_t zx_syscall_xxx(int32_t p1, int32_t p2); |
| # ``` |
| headers = set([ |
| '#include <zircon/syscalls.h>', |
| ]) |
| extra_headers = {} |
| for arg in syscall_data['arguments']: |
| if arg['type'] == 'zx_port_packet_t': |
| headers.add('#include <zircon/syscalls/port.h>') |
| elif (arg['type'] == 'zx_smc_parameters_t' or |
| arg['type'] == 'zx_smc_result_t'): |
| headers.add('#include <zircon/syscalls/smc.h>') |
| header = ['```c'] + sorted(list(headers)) + [''] |
| |
| def format_arg(x): |
| ret = '' |
| if 'IN' in x['attributes']: |
| ret += 'const ' |
| if x['type'] == 'any': |
| ret += 'void ' |
| else: |
| ret += x['type'] + ' ' |
| if x['is_array']: |
| ret += ' * ' |
| ret += ' ' + x['name'] |
| return ret |
| |
| no_return = '' |
| if 'noreturn' in syscall_data['attributes']: no_return = '[[noreturn]]' |
| |
| to_format = (no_return + syscall_data['return_type'] + ' zx_' + |
| syscall_data['name'] + '(') |
| args = ','.join(format_arg(x) for x in syscall_data['arguments']) |
| if not args: |
| args = 'void' |
| to_format += args + ');' |
| |
| CLANG_FORMAT_PATH = os.path.join(SCRIPT_DIR, os.pardir, os.pardir, |
| 'prebuilt', 'third_party', 'clang', |
| 'linux-x64', 'bin', 'clang-format') |
| clang_format = subprocess.Popen([ |
| CLANG_FORMAT_PATH, |
| '-style={BasedOnStyle: Google, BinPackParameters: false}' |
| ], |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE) |
| formatted, _ = clang_format.communicate(to_format.encode('utf-8')) |
| if clang_format.returncode != 0: |
| warn('formatting synopsis failed, skipping update') |
| return None |
| |
| footer = [ |
| '```', |
| '', |
| ] |
| return STANDARD_BLOCK_HEADER + header + [formatted.decode('utf-8')] + footer |
| |
| |
| def update_synopsis(lines, syscall_data, warn): |
| """Updates the SYNOPSIS block of the .md file in lines. |
| """ |
| start_index, end_index = find_block(lines, 'SYNOPSIS') |
| if start_index == -1 or end_index == -1: |
| warn('did not find SYNOPSIS section, skipping update') |
| return |
| |
| syn = make_synopsis_block(syscall_data, warn) |
| if not syn: |
| return |
| lines[start_index:end_index] = syn |
| |
| |
| def update_title(lines, syscall_data, _): |
| """Updates the main title of the .md file given by |filename|. |
| """ |
| correct_title = '# zx_' + syscall_data['name'] |
| if lines[0] != correct_title: |
| lines[0] = correct_title |
| |
| |
| def generate_stub(md): |
| """Makes a mostly-empty file that can then be filled out by later update |
| functions.""" |
| |
| stub = '''\ |
| # zx_xyz |
| |
| ## NAME |
| |
| ## SYNOPSIS |
| |
| ## DESCRIPTION |
| |
| TODO(fxbug.dev/32938) |
| |
| ## RIGHTS |
| |
| ## RETURN VALUE |
| |
| TODO(fxbug.dev/32938) |
| |
| ## ERRORS |
| |
| TODO(fxbug.dev/32938) |
| |
| ## SEE ALSO |
| |
| TODO(fxbug.dev/32938) |
| ''' |
| with open(md, 'w') as f: |
| f.write(stub) |
| |
| |
| def check_for_orphans(syscalls, root): |
| """Checks for any .md files that have been orphaned (no longer have an |
| associated syscalls file entry.) |
| """ |
| orphan_count = 0 |
| names = set([x['name'] for x in syscalls]) |
| for md in os.listdir(root): |
| if md == '_toc.yaml': |
| continue |
| if md == 'README.md': |
| continue |
| if not md.endswith('.md'): |
| print('warning: non .md file %s' % md, file=sys.stderr) |
| name = md[:-3] |
| if name not in names: |
| orphan_count += 1 |
| print('warning: %s has no entry in syscalls' % md, file=sys.stderr) |
| return orphan_count |
| |
| |
| # A few concept docs that are linked in SEE ALSO sections. |
| SEE_ALSO_CONCEPTS = { |
| 'clock transformations': '/docs/concepts/kernel/clock_transformations.md', |
| 'clocks': '/docs/reference/kernel_objects/clock.md', |
| 'exceptions': '/docs/concepts/kernel/exceptions.md', |
| 'futex objects': '/docs/reference/kernel_objects/futex.md', |
| 'kernel command line': '/docs/reference/kernel/kernel_cmdline.md', |
| 'rights': '/docs/concepts/kernel/rights.md', |
| 'signals': '/docs/concepts/kernel/signals.md', |
| 'timer slack': '/docs/concepts/kernel/timer_slack.md', |
| } |
| |
| |
| def make_see_also_block(referenced_syscalls, concepts, extra): |
| """Makes a formatted SEE ALSO block given a list of syscall names. |
| """ |
| result = [] |
| |
| for concept in sorted(concepts): |
| result.append(' - [' + concept + ']') |
| |
| for sc in sorted(referenced_syscalls): |
| # References to these will be done later by update_syscall_references(). |
| result.append(' - [`zx_' + sc + '()`]') |
| |
| if extra: |
| extra += [''] |
| |
| # No comment header here, because people are still editing this by hand, |
| # we're only canonicalizing it. |
| return [''] + extra + result + [''] |
| |
| |
| def update_seealso(lines, syscall, all_syscall_names, warn): |
| """Rewrites 'SEE ALSO' block to canonical format. |
| """ |
| concepts = set() |
| |
| start_index, end_index = find_block(lines, 'SEE ALSO') |
| if start_index == -1: |
| return concepts |
| |
| referenced = set() |
| extra = [] |
| for line in lines[start_index:end_index]: |
| if not line or line == STANDARD_COMMENT: |
| continue |
| |
| handled = False |
| for concept in SEE_ALSO_CONCEPTS: |
| if '[' + concept + ']' in line: |
| concepts.add(concept) |
| handled = True |
| if handled: |
| continue |
| |
| for sc in all_syscall_names: |
| old = '[' + sc + ']' |
| new = '[`zx_' + sc + '()`]' |
| if old in line or new in line: |
| referenced.add(sc) |
| break |
| else: |
| warn('unrecognized "see also", keeping before syscalls: ' + line) |
| extra.append(line) |
| |
| lines[start_index:end_index] = make_see_also_block(referenced, concepts, |
| extra) |
| return concepts |
| |
| |
| SYSCALL_RE = {} |
| |
| |
| def update_syscall_references(lines, syscall, all_syscall_names, concepts, warn): |
| """Attempts to update all syscall references to a canonical format, and |
| linkifies them to their corresponding syscall. |
| |
| TODO(fxbug.dev/32938): It'd be nice to do the references from outside of |
| docs/syscalls/ into syscalls too, in a similar style. |
| """ |
| |
| text = '\n'.join(lines) |
| |
| # Precompile these regexes as it takes measurable time. |
| if not SYSCALL_RE: |
| for sc in all_syscall_names: |
| # Look for **zx_stuff()** and [`zx_stuff()`], with both "zx_" and |
| # the () being optional. |
| SYSCALL_RE[sc] = re.compile(r'\*{2}(?:zx_)?' + sc + |
| r'(?:\(\))?\*{2}(?:\(\))?' |
| r'|' |
| r'(?:\[)`(?:zx_)?' + sc + |
| r'(?:\(\))?`(?:\])?(\(\))?') |
| |
| referred_to = set() |
| for sc in all_syscall_names: |
| scre = SYSCALL_RE[sc] |
| self = sc == syscall['name'] |
| repl = '`zx_' + sc + '()`' |
| # Don't link to ourselves. |
| if not self: |
| repl = '[' + repl + ']' |
| text, count = scre.subn(repl, text) |
| if count and not self: |
| referred_to.add(sc) |
| |
| lines[:] = text.splitlines() |
| |
| if REFERENCES_COMMENT not in lines: |
| lines.extend(['', REFERENCES_COMMENT]) |
| start_index = lines.index(REFERENCES_COMMENT) |
| |
| references = [] |
| for concept in sorted(concepts): |
| references.append('[' + concept + ']: ' + SEE_ALSO_CONCEPTS[concept]) |
| for ref in sorted(referred_to): |
| references.append('[`zx_' + ref + '()`]: ' + ref + '.md') |
| lines[start_index:] = [REFERENCES_COMMENT, ''] + references |
| |
| # Drop references section if it's empty to not be noisy. |
| if lines[-3:] == ['', REFERENCES_COMMENT, '']: |
| lines[:] = lines[:-3] |
| |
| |
| def build_if_out_of_date(syscalls_dir, build_dir, json): |
| """If the json file appears out-of-date relative to any files in the |
| syscalls dir, then run a zircon build to get the definitions updated. |
| """ |
| |
| def is_up_to_date(input, output): |
| """Checks if output exists and is newer-or-equal in mtime to input.""" |
| if not os.path.isfile(output): |
| return False |
| input_time = os.path.getmtime(input) |
| output_time = os.path.getmtime(output) |
| return output_time >= input_time |
| |
| for f in os.listdir(syscalls_dir): |
| if not f.endswith('.fidl'): |
| continue |
| syscall = os.path.join(syscalls_dir, f) |
| if not os.path.isfile(syscall): |
| continue |
| if not is_up_to_date(syscall, json): |
| print( |
| '%s out of date vs %s, running `fx build`\n' |
| 'This will only work if using out/default (or --builddir is set) ' |
| 'and configured for x64.' % (json, syscall)) |
| subprocess.check_call( |
| [ |
| 'fx', |
| '--dir=%s' % (build_dir), 'build', '-C', |
| build_dir, |
| os.path.relpath(json, build_dir), |
| ]) |
| if not is_up_to_date(syscall, json): |
| print('%s still out of date relative to %s!' % (json, syscall)) |
| raise SystemExit() |
| break |
| |
| |
| def main(): |
| args = parse_args() |
| # Need to construct the default json path here so that we can use any |
| # configured build directory. |
| if args.json: |
| json_path = args.json |
| else: |
| json_path = os.path.normpath( |
| os.path.join( |
| SCRIPT_DIR, os.pardir, os.pardir, args.builddir, 'gen', |
| 'include', 'zircon', 'syscalls', 'definitions.json')) |
| inf = os.path.relpath(json_path) |
| outf = os.path.relpath(args.docroot) |
| build_if_out_of_date( |
| os.path.relpath(SYSCALLS_FIDL_DIR), args.builddir, json_path) |
| print('using %s as input and updating %s...' % (inf, outf)) |
| data = json.load(open(inf, 'rb')) |
| missing_count = 0 |
| all_syscall_names = set(x['name'] for x in data['syscalls']) |
| for syscall in data['syscalls']: |
| if 'internal' in syscall['attributes']: |
| # Don't generate documentation for syscalls tagged as internal. |
| continue |
| name = syscall['name'] |
| if args.name and name not in args.name: |
| continue |
| md = os.path.join(outf, name + '.md') |
| |
| if not os.path.exists(md) and args.generate_missing: |
| generate_stub(md) |
| |
| if not os.path.exists(md): |
| print('warning: %s not found for updating, skipping update' % md, file=sys.stderr) |
| missing_count += 1 |
| else: |
| with open(md, 'r') as f: |
| lines = f.read().splitlines() |
| |
| assert (lines) |
| |
| def warn(msg): |
| print('warning: %s: %s' % (md, msg), file=sys.stderr) |
| |
| update_title(lines, syscall, warn) |
| update_name(lines, syscall, warn) |
| update_synopsis(lines, syscall, warn) |
| update_rights(lines, syscall, warn) |
| concepts = update_seealso(lines, syscall, all_syscall_names, warn) |
| update_syscall_references(lines, syscall, all_syscall_names, concepts, warn) |
| |
| with open(md, 'w') as f: |
| f.write('\n'.join(lines) + '\n') |
| |
| if missing_count > 0: |
| print('warning: %d missing .md files' % missing_count, file=sys.stderr) |
| missing_count += check_for_orphans(data['syscalls'], outf) |
| return missing_count |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |