From 6176c84768ad7ee0c9240c2df90eb2461bfa2a9f Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Sat, 2 Jul 2022 04:57:25 +1000 Subject: [PATCH] Added CI job to check for likely mame.lst errors. --- .github/workflows/ci-linux.yml | 2 + scripts/build/makedep.py | 141 +++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index abbbf10c130..9c52b688597 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -55,6 +55,8 @@ jobs: run: make -j2 - name: Validate run: ./mame -validate + - name: Reconcile driver list + run: ./mame -listxml | python scripts/build/makedep.py reconcilelist -l src/mame/mame.lst - - name: ORM check run: python scripts/minimaws/minimaws.py load --executable ./mame --softwarepath hash - uses: actions/upload-artifact@master diff --git a/scripts/build/makedep.py b/scripts/build/makedep.py index 07406423ff2..13d0691c3af 100755 --- a/scripts/build/makedep.py +++ b/scripts/build/makedep.py @@ -8,6 +8,7 @@ import glob import io import os.path import sys +import xml.sax class ParserBase: @@ -581,6 +582,128 @@ class DriverCollector(DriverFilter): self.sources = sorted(sources) +class DriverReconciler(DriverFilter): + class InfoHandler: + def __init__(self, drivers, **kwargs): + super().__init__(**kwargs) + self.drivers = drivers + self.bad = False + self.locator = None + self.ignored_depth = 0 + self.in_document = False + self.in_mame = False + + def startElement(self, name, attrs): + if not self.in_document: + raise xml.sax.SAXParseException('Unexpected start of element "%s"' % (name, ), None, self.locator) + elif self.ignored_depth > 0: + self.ignored_depth += 1 + elif not self.in_mame: + if name != 'mame': + raise xml.sax.SAXParseException('Unexpected start of element "%s"' % (name, ), None, self.locator) + self.in_mame = True + elif name != 'machine': + raise xml.sax.SAXParseException('Unexpected start of element "%s"' % (name, ), None, self.locator) + else: + runnable = attrs.get('runnable', 'yes') + if runnable == 'yes': + shortname = attrs['name'] + source = attrs['sourcefile'].replace('\\', '/') + declared = self.drivers.get(shortname) + if declared is None: + sys.stderr.write('Driver "%s" not declared in list file\n' % (shortname, )) + self.bad = True + elif declared != source: + sys.stderr.write('Driver "%s" found for source file "%s" but defined in source file "%s"\n' % (shortname, declared, source)) + self.bad = True + self.ignored_depth = 1 + + def endElement(self, name): + if self.ignored_depth > 0: + self.ignored_depth -= 1 + elif self.in_mame: + if name != 'mame': + raise xml.sax.SAXParseException('Unexpected end of element "%s"' % (name, ), None, self.locator) + self.in_mame = False + else: + raise xml.sax.SAXParseException('Unexpected end of element "%s"' % (name, ), None, self.locator) + + def startDocument(self): + if self.in_document: + raise xml.sax.SAXParseException('Unexpected start of document', None, self.locator) + self.in_document = True + + def endDocument(self): + if not self.in_document: + raise xml.sax.SAXParseException('Unexpected end of document', None, self.locator) + self.in_document = False + + def setDocumentLocator(self, locator): + self.locator = locator + + def startPrefixMapping(self, prefix, uri): + pass + + def endPrefixMapping(self, prefix): + pass + + def characters(self, content): + pass + + def ignorableWhitespace(self, whitespace): + pass + + def processingInstruction(self, target, data): + pass + + + def __init__(self, options, **kwargs): + super().__init__(**kwargs) + + def sourcefile(filename): + if (state['prevsource'] is not None) and (not state['prevdrivers']): + sys.stderr.write('No drivers for source file "%s"\n' % (state['prevsource'], )) + self.bad = True + if filename in self.sources: + sys.stderr.write('Duplicate source file "%s"\n' % (filename, )) + state['prevdrivers'] = self.sources[filename] + self.bad = True + else: + drivers = set() + state['prevdrivers'] = drivers + self.sources[filename] = drivers + state['prevsource'] = filename + + def driver(shortname): + if shortname in self.drivers: + sys.stderr.write('Duplicate driver "%s" for source file "%s" (previously seen for source file "%s")\n' % (shortname, state['prevsource'], self.drivers[shortname])) + self.bad = True + else: + self.drivers[shortname] = state['prevsource'] + drivers = state['prevdrivers'] + if drivers is None: + sys.stderr.write('Driver "%s" found outside source file section\n' % (shortname, )) + self.bad = True + else: + drivers.add(shortname) + + state = { 'prevsource': None, 'prevdrivers': None } + self.bad = False + self.sources = { } + self.drivers = { } + self.parse_list(options.list, sourcefile, driver) + + def reconcile_xml(self, xmlfile): + handler = self.InfoHandler(self.drivers) + try: + xml.sax.parse(xmlfile, handler=handler) + if handler.bad: + self.bad = True + except xml.sax.SAXException as err: + sys.stderr.write('Error parsing system information file: %s\n' % (err, )) + self.bad = True + + def split_path(path): path = os.path.normpath(path) result = [ ] @@ -619,6 +742,10 @@ def parse_command_line(): subparser.add_argument('-f', '--filter', metavar='', help='input filter file') subparser.add_argument('list', metavar='', help='input list file') + subparser = subparsers.add_parser('reconcilelist', help='reconcile driver list') + subparser.add_argument('-l', '--list', metavar='', required=True, help='master driver list file') + subparser.add_argument('infoxml', metavar='', nargs='?', help='XML system information file') + return parser.parse_args() @@ -905,3 +1032,17 @@ if __name__ == '__main__': write_filter(options, sys.stdout) elif options.command == 'driverlist': DriverLister(options).write_source(sys.stdout) + elif options.command == 'reconcilelist': + reconciler = DriverReconciler(options) + if options.infoxml == '-': + reconciler.reconcile_xml(sys.stdin) + elif options.infoxml is not None: + try: + xmlfile = io.open(options.infoxml, 'rb') + with xmlfile: + reconciler.reconcile_xml(xmlfile) + except IOError: + sys.stderr.write('Unable to open system information file "%s"\n' % (options.infoxml, )) + sys.exit(1) + if reconciler.bad: + sys.exit(1)