[libvirt] [PATCH] docs: rewrite hvsupport.html page generator in python
As part of an goal to eliminate Perl from libvirt build tools, rewrite the hvsupport.pl tool in Python. This was a straight conversion, manually going line-by-line to change the syntax from Perl to Python. Thus the overall structure of the file and approach is the same. The new impl generates byte-for-byte identical output to the old impl. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- docs/Makefile.am | 6 +- docs/hvsupport.pl | 458 --------------------------------------------- docs/hvsupport.py | 463 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 466 insertions(+), 461 deletions(-) delete mode 100755 docs/hvsupport.pl create mode 100755 docs/hvsupport.py diff --git a/docs/Makefile.am b/docs/Makefile.am index 1cdb584b0b..f7aba5499d 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -194,7 +194,7 @@ EXTRA_DIST= \ $(internals_html_in) $(internals_html) $(fonts) \ $(kbase_html_in) $(kbase_html) \ aclperms.htmlinc \ - hvsupport.pl \ + hvsupport.py \ $(schema_DATA) acl_generated = aclperms.htmlinc @@ -230,11 +230,11 @@ web: $(dot_html) $(internals_html) $(kbase_html) \ hvsupport.html: $(srcdir)/hvsupport.html.in -$(srcdir)/hvsupport.html.in: $(srcdir)/hvsupport.pl $(api_DATA) \ +$(srcdir)/hvsupport.html.in: $(srcdir)/hvsupport.py $(api_DATA) \ $(top_srcdir)/src/libvirt_public.syms \ $(top_srcdir)/src/libvirt_qemu.syms $(top_srcdir)/src/libvirt_lxc.syms \ $(top_srcdir)/src/driver.h - $(AM_V_GEN)$(PERL) $(srcdir)/hvsupport.pl $(top_srcdir)/src > $@ \ + $(AM_V_GEN)$(PYTHON) $(srcdir)/hvsupport.py $(top_srcdir)/src > $@ \ || { rm $@ && exit 1; } news.html.in: \ diff --git a/docs/hvsupport.pl b/docs/hvsupport.pl deleted file mode 100755 index 494b8a27ec..0000000000 --- a/docs/hvsupport.pl +++ /dev/null @@ -1,458 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; - -use File::Find; - -die "syntax: $0 SRCDIR\n" unless int(@ARGV) == 1; - -my $srcdir = shift @ARGV; - -my $symslibvirt = "$srcdir/libvirt_public.syms"; -my $symsqemu = "$srcdir/libvirt_qemu.syms"; -my $symslxc = "$srcdir/libvirt_lxc.syms"; -my @drivertable = ( - "$srcdir/driver-hypervisor.h", - "$srcdir/driver-interface.h", - "$srcdir/driver-network.h", - "$srcdir/driver-nodedev.h", - "$srcdir/driver-nwfilter.h", - "$srcdir/driver-secret.h", - "$srcdir/driver-state.h", - "$srcdir/driver-storage.h", - "$srcdir/driver-stream.h", - ); - -my %groupheaders = ( - "virHypervisorDriver" => "Hypervisor APIs", - "virNetworkDriver" => "Virtual Network APIs", - "virInterfaceDriver" => "Host Interface APIs", - "virNodeDeviceDriver" => "Host Device APIs", - "virStorageDriver" => "Storage Pool APIs", - "virSecretDriver" => "Secret APIs", - "virNWFilterDriver" => "Network Filter APIs", - ); - - -my @srcs; -find({ - wanted => sub { - if (m!$srcdir/.*/\w+_(driver|common|tmpl|monitor|hal|udev)\.c$!) { - push @srcs, $_ if $_ !~ /vbox_driver\.c/; - } - }, no_chdir => 1}, $srcdir); - -# Map API functions to the header and documentation files they're in -# so that we can generate proper hyperlinks to their documentation. -# -# The function names are grep'd from the XML output of apibuild.py. -sub getAPIFilenames { - my $filename = shift; - - my %files; - my $line; - - open FILE, "<", $filename or die "cannot read $filename: $!"; - - while (defined($line = <FILE>)) { - if ($line =~ /function name='([^']+)' file='([^']+)'/) { - $files{$1} = $2; - } - } - - close FILE; - - if (keys %files == 0) { - die "No functions found in $filename. Has the apibuild.py output changed?"; - } - return \%files; -} - -sub parseSymsFile { - my $apisref = shift; - my $prefix = shift; - my $filename = shift; - my $xmlfilename = shift; - - my $line; - my $vers; - my $prevvers; - - my $filenames = getAPIFilenames($xmlfilename); - - open FILE, "<$filename" - or die "cannot read $filename: $!"; - - while (defined($line = <FILE>)) { - chomp $line; - next if $line =~ /^\s*#/; - next if $line =~ /^\s*$/; - next if $line =~ /^\s*(global|local):/; - if ($line =~ /^\s*${prefix}_(\d+\.\d+\.\d+)\s*{\s*$/) { - if (defined $vers) { - die "malformed syms file"; - } - $vers = $1; - } elsif ($line =~ /\s*}\s*;\s*$/) { - if (defined $prevvers) { - die "malformed syms file"; - } - $prevvers = $vers; - $vers = undef; - } elsif ($line =~ /\s*}\s*${prefix}_(\d+\.\d+\.\d+)\s*;\s*$/) { - if ($1 ne $prevvers) { - die "malformed syms file $1 != $vers"; - } - $prevvers = $vers; - $vers = undef; - } elsif ($line =~ /\s*(\w+)\s*;\s*$/) { - $$apisref{$1} = {}; - $$apisref{$1}->{vers} = $vers; - $$apisref{$1}->{file} = $$filenames{$1}; - } else { - die "unexpected data $line\n"; - } - } - - close FILE; -} - -my %apis; -# Get the list of all public APIs and their corresponding version -parseSymsFile(\%apis, "LIBVIRT", $symslibvirt, "$srcdir/../docs/libvirt-api.xml"); - -# And the same for the QEMU specific APIs -parseSymsFile(\%apis, "LIBVIRT_QEMU", $symsqemu, "$srcdir/../docs/libvirt-qemu-api.xml"); - -# And the same for the LXC specific APIs -parseSymsFile(\%apis, "LIBVIRT_LXC", $symslxc, "$srcdir/../docs/libvirt-lxc-api.xml"); - - -# Some special things which aren't public APIs, -# but we want to report -$apis{virConnectSupportsFeature}->{vers} = "0.3.2"; -$apis{virDomainMigratePrepare}->{vers} = "0.3.2"; -$apis{virDomainMigratePerform}->{vers} = "0.3.2"; -$apis{virDomainMigrateFinish}->{vers} = "0.3.2"; -$apis{virDomainMigratePrepare2}->{vers} = "0.5.0"; -$apis{virDomainMigrateFinish2}->{vers} = "0.5.0"; -$apis{virDomainMigratePrepareTunnel}->{vers} = "0.7.2"; - -$apis{virDomainMigrateBegin3}->{vers} = "0.9.2"; -$apis{virDomainMigratePrepare3}->{vers} = "0.9.2"; -$apis{virDomainMigratePrepareTunnel3}->{vers} = "0.9.2"; -$apis{virDomainMigratePerform3}->{vers} = "0.9.2"; -$apis{virDomainMigrateFinish3}->{vers} = "0.9.2"; -$apis{virDomainMigrateConfirm3}->{vers} = "0.9.2"; - -$apis{virDomainMigrateBegin3Params}->{vers} = "1.1.0"; -$apis{virDomainMigratePrepare3Params}->{vers} = "1.1.0"; -$apis{virDomainMigratePrepareTunnel3Params}->{vers} = "1.1.0"; -$apis{virDomainMigratePerform3Params}->{vers} = "1.1.0"; -$apis{virDomainMigrateFinish3Params}->{vers} = "1.1.0"; -$apis{virDomainMigrateConfirm3Params}->{vers} = "1.1.0"; - - - -# Now we want to get the mapping between public APIs -# and driver struct fields. This lets us later match -# update the driver impls with the public APis. - -my $line; - -# Group name -> hash of APIs { fields -> api name } -my %groups; -my $ingrp; -foreach my $drivertable (@drivertable) { - open FILE, "<$drivertable" - or die "cannot read $drivertable: $!"; - - while (defined($line = <FILE>)) { - if ($line =~ /struct _(vir\w*Driver)/) { - my $grp = $1; - if ($grp ne "virStateDriver" && - $grp ne "virStreamDriver") { - $ingrp = $grp; - $groups{$ingrp} = { apis => {}, drivers => {} }; - } - } elsif ($ingrp) { - if ($line =~ /^\s*vir(?:Drv)(\w+)\s+(\w+);\s*$/) { - my $field = $2; - my $name = $1; - - my $api; - if (exists $apis{"vir$name"}) { - $api = "vir$name"; - } elsif ($name =~ /\w+(Open|Close|URIProbe)/) { - next; - } else { - die "driver $name does not have a public API"; - } - $groups{$ingrp}->{apis}->{$field} = $api; - } elsif ($line =~ /};/) { - $ingrp = undef; - } - } - } - - close FILE; -} - - -# Finally, we read all the primary driver files and extract -# the driver API tables from each one. - -foreach my $src (@srcs) { - open FILE, "<$src" or - die "cannot read $src: $!"; - - my $groups_regex = join("|", keys %groups); - $ingrp = undef; - my $impl; - while (defined($line = <FILE>)) { - if (!$ingrp) { - # skip non-matching lines early to save time - next if not $line =~ /$groups_regex/; - - if ($line =~ /^\s*(?:static\s+)?($groups_regex)\s+(\w+)\s*=\s*{/ || - $line =~ /^\s*(?:static\s+)?($groups_regex)\s+NAME\(\w+\)\s*=\s*{/) { - $ingrp = $1; - $impl = $src; - - if ($impl =~ m,.*/node_device_(\w+)\.c,) { - $impl = $1; - } else { - $impl =~ s,.*/(\w+?)_((\w+)_)?(\w+)\.c,$1,; - } - - if ($groups{$ingrp}->{drivers}->{$impl}) { - die "Group $ingrp already contains $impl"; - } - - $groups{$ingrp}->{drivers}->{$impl} = {}; - } - - } else { - if ($line =~ m!\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*(?:/\*\s*(\d+\.\d+\.\d+)\s*(?:-\s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$!) { - my $api = $1; - my $meth = $2; - my $vers = $3; - my $deleted = $4; - - next if $api eq "no" || $api eq "name"; - - if ($meth eq "NULL" && !defined $deleted) { - die "Method impl for $api is NULL, but no deleted version is provided"; - } - if ($meth ne "NULL" && defined $deleted) { - die "Method impl for $api is non-NULL, but deleted version is provided"; - } - - die "Method $meth in $src is missing version" unless defined $vers || $api eq "connectURIProbe"; - - if (!exists($groups{$ingrp}->{apis}->{$api})) { - next if $api =~ /\w(Open|Close|URIProbe)/; - - die "Found unexpected method $api in $ingrp\n"; - } - - $groups{$ingrp}->{drivers}->{$impl}->{$api} = {}; - $groups{$ingrp}->{drivers}->{$impl}->{$api}->{vers} = $vers; - $groups{$ingrp}->{drivers}->{$impl}->{$api}->{deleted} = $deleted; - if ($api eq "domainMigratePrepare" || - $api eq "domainMigratePrepare2" || - $api eq "domainMigratePrepare3") { - if (!$groups{$ingrp}->{drivers}->{$impl}->{"domainMigrate"}) { - $groups{$ingrp}->{drivers}->{$impl}->{"domainMigrate"} = {}; - $groups{$ingrp}->{drivers}->{$impl}->{"domainMigrate"}->{vers} = $vers; - } - } - - } elsif ($line =~ /}/) { - $ingrp = undef; - } - } - } - - close FILE; -} - - -# The '.open' driver method is used for 3 public APIs, so we -# have a bit of manual fixup todo with the per-driver versioning -# and support matrix - -$groups{virHypervisorDriver}->{apis}->{"openAuth"} = "virConnectOpenAuth"; -$groups{virHypervisorDriver}->{apis}->{"openReadOnly"} = "virConnectOpenReadOnly"; -$groups{virHypervisorDriver}->{apis}->{"domainMigrate"} = "virDomainMigrate"; - -my $openAuthVers = (0 * 1000 * 1000) + (4 * 1000) + 0; - -foreach my $drv (keys %{$groups{"virHypervisorDriver"}->{drivers}}) { - my $openVersStr = $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpen"}->{vers}; - my $openVers; - if ($openVersStr =~ /(\d+)\.(\d+)\.(\d+)/) { - $openVers = ($1 * 1000 * 1000) + ($2 * 1000) + $3; - } - - # virConnectOpenReadOnly always matches virConnectOpen version - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenReadOnly"} = - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpen"}; - - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenAuth"} = {}; - - # virConnectOpenAuth is always 0.4.0 if the driver existed - # before this time, otherwise it matches the version of - # the driver's virConnectOpen entry - if ($openVersStr eq "Y" || - $openVers >= $openAuthVers) { - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenAuth"}->{vers} = $openVersStr; - } else { - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenAuth"}->{vers} = "0.4.0"; - } -} - - -# Another special case for the virDomainCreateLinux which was replaced -# with virDomainCreateXML -$groups{virHypervisorDriver}->{apis}->{"domainCreateLinux"} = "virDomainCreateLinux"; - -my $createAPIVers = (0 * 1000 * 1000) + (0 * 1000) + 3; - -foreach my $drv (keys %{$groups{"virHypervisorDriver"}->{drivers}}) { - my $createVersStr = $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateXML"}->{vers}; - next unless defined $createVersStr; - my $createVers; - if ($createVersStr =~ /(\d+)\.(\d+)\.(\d+)/) { - $createVers = ($1 * 1000 * 1000) + ($2 * 1000) + $3; - } - - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateLinux"} = {}; - - # virCreateLinux is always 0.0.3 if the driver existed - # before this time, otherwise it matches the version of - # the driver's virCreateXML entry - if ($createVersStr eq "Y" || - $createVers >= $createAPIVers) { - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateLinux"}->{vers} = $createVersStr; - } else { - $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateLinux"}->{vers} = "0.0.3"; - } -} - - -# Finally we generate the HTML file with the tables - -print <<EOF; -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml"> -<body class="hvsupport"> -<h1>libvirt API support matrix</h1> - -<ul id="toc"></ul> - -<p> -This page documents which <a href="html/">libvirt calls</a> work on -which libvirt drivers / hypervisors, and which version the API appeared -in. If a hypervisor driver later dropped support for the API, the version -when it was removed is also mentioned (highlighted in -<span class="removedhv">dark red</span>). -</p> - -EOF - - foreach my $grp (sort { $a cmp $b } keys %groups) { - print "<h2><a id=\"$grp\">", $groupheaders{$grp}, "</a></h2>\n"; - print <<EOF; -<table class="top_table"> -<thead> -<tr> -<th>API</th> -<th>Version</th> -EOF - - foreach my $drv (sort { $a cmp $b } keys %{$groups{$grp}->{drivers}}) { - print " <th>$drv</th>\n"; - } - - print <<EOF; -</tr> -</thead> -<tbody> -EOF - - my $row = 0; - foreach my $field (sort { - $groups{$grp}->{apis}->{$a} - cmp - $groups{$grp}->{apis}->{$b} - } keys %{$groups{$grp}->{apis}}) { - my $api = $groups{$grp}->{apis}->{$field}; - my $vers = $apis{$api}->{vers}; - my $htmlgrp = $apis{$api}->{file}; - print <<EOF; -<tr> -<td> -EOF - - if (defined $htmlgrp) { - print <<EOF; -<a href=\"html/libvirt-$htmlgrp.html#$api\">$api</a> -EOF - - } else { - print $api; - } - print <<EOF; -</td> -<td>$vers</td> -EOF - - foreach my $drv (sort {$a cmp $b } keys %{$groups{$grp}->{drivers}}) { - print "<td>"; - if (exists $groups{$grp}->{drivers}->{$drv}->{$field}) { - if ($groups{$grp}->{drivers}->{$drv}->{$field}->{vers}) { - print $groups{$grp}->{drivers}->{$drv}->{$field}->{vers}; - } - if ($groups{$grp}->{drivers}->{$drv}->{$field}->{deleted}) { - print " - <span class=\"removedhv\">", $groups{$grp}->{drivers}->{$drv}->{$field}->{deleted}, "</span>"; - } - } - print "</td>\n"; - } - - print <<EOF; -</tr> -EOF - - $row++; - if (($row % 15) == 0) { - print <<EOF; -<tr> -<th>API</th> -<th>Version</th> -EOF - - foreach my $drv (sort { $a cmp $b } keys %{$groups{$grp}->{drivers}}) { - print " <th>$drv</th>\n"; - } - - print <<EOF; -</tr> -EOF - } - - } - - print <<EOF; -</tbody> -</table> -EOF -} - -print <<EOF; -</body> -</html> -EOF diff --git a/docs/hvsupport.py b/docs/hvsupport.py new file mode 100755 index 0000000000..502389c25f --- /dev/null +++ b/docs/hvsupport.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import sys +import os.path +import re + +if len(sys.argv) != 2: + print("syntax: %s SRCDIR\n" % sys.argv[0], fh=sys.stderr) + +srcdir = sys.argv[1] + +symslibvirt = os.path.join(srcdir, "libvirt_public.syms") +symsqemu = os.path.join(srcdir, "libvirt_qemu.syms") +symslxc = os.path.join(srcdir, "libvirt_lxc.syms") +drivertablefiles = [ + os.path.join(srcdir, "driver-hypervisor.h"), + os.path.join(srcdir, "driver-interface.h"), + os.path.join(srcdir, "driver-network.h"), + os.path.join(srcdir, "driver-nodedev.h"), + os.path.join(srcdir, "driver-nwfilter.h"), + os.path.join(srcdir, "driver-secret.h"), + os.path.join(srcdir, "driver-state.h"), + os.path.join(srcdir, "driver-storage.h"), + os.path.join(srcdir, "driver-stream.h"), +] + +groupheaders = { + "virHypervisorDriver": "Hypervisor APIs", + "virNetworkDriver": "Virtual Network APIs", + "virInterfaceDriver": "Host Interface APIs", + "virNodeDeviceDriver": "Host Device APIs", + "virStorageDriver": "Storage Pool APIs", + "virSecretDriver": "Secret APIs", + "virNWFilterDriver": "Network Filter APIs", +} + + +srcs = [] +for root, dirs, files in os.walk(srcdir): + for file in files: + if ((file.endswith("driver.c") and + not file.endswith("vbox_driver.c")) or + file.endswith("common.c") or + file.endswith("tmpl.c") or + file.endswith("monitor.c") or + file.endswith("hal.c") or + file.endswith("udev.c")): + srcs.append(os.path.join(root, file)) + + +# Map API functions to the header and documentation files they're in +# so that we can generate proper hyperlinks to their documentation. +# +# The function names are grep'd from the XML output of apibuild.py. +def getAPIFilenames(filename): + files = {} + + with open(filename) as fh: + prog = re.compile(r"\s*<function name='([^']+)' file='([^']+)'.*") + for line in fh: + res = prog.match(line) + if res is not None: + files[res.group(1)] = res.group(2) + + if len(files) == 0: + raise Exception("No functions found in %s. Has the apibuild.py output changed?" % filename) + + return files; + + +def parseSymsFile(apisref, prefix, filename, xmlfilename): + vers = None + prevvers = None + + filenames = getAPIFilenames(xmlfilename) + + with open(filename) as fh: + groupstartprog = re.compile(r"^\s*%s_(\d+\.\d+\.\d+)\s*{\s*$" % prefix) + groupendprog1 = re.compile(r"^\s*}\s*;\s*$") + groupendprog2 = re.compile(r"^\s*}\s*%s_(\d+\.\d+\.\d+)\s*;\s*$" % prefix) + symbolprog = re.compile(r"^\s*(\w+)\s*;\s*$") + for line in fh: + line = line.strip() + + if line == "": + continue + if line[0] == '#': + continue + if line.startswith("global:"): + continue + if line.startswith("local:"): + continue + + groupstartmatch = groupstartprog.match(line) + groupendmatch1 = groupendprog1.match(line) + groupendmatch2 = groupendprog2.match(line) + symbolmatch = symbolprog.match(line) + if groupstartmatch is not None: + if vers is not None: + raise Exception("malformed syms file when starting group") + + vers = groupstartmatch.group(1) + elif groupendmatch1 is not None: + if prevvers is not None: + raise Exception("malformed syms file when ending group") + + prevvers = vers + vers = None + elif groupendmatch2 is not None: + if groupendmatch2.group(1) != prevvers: + raise Exception("malformed syms file %s != %s when ending group" % ( + groupendmatch2.group(1), prevvers)) + + prevvers = vers + vers = None + elif symbolmatch is not None: + name = symbolmatch.group(1) + apisref[name] = { + "vers": vers, + "file": filenames.get(name), + } + else: + raise Exception("unexpected data %s" % line) + + +apis = {} +# Get the list of all public APIs and their corresponding version +parseSymsFile(apis, "LIBVIRT", symslibvirt, os.path.join(srcdir, "../docs/libvirt-api.xml")) + +# And the same for the QEMU specific APIs +parseSymsFile(apis, "LIBVIRT_QEMU", symsqemu, os.path.join(srcdir, "../docs/libvirt-qemu-api.xml")) + +# And the same for the LXC specific APIs +parseSymsFile(apis, "LIBVIRT_LXC", symslxc, os.path.join(srcdir, "../docs/libvirt-lxc-api.xml")) + + +# Some special things which aren't public APIs, +# but we want to report +apis["virConnectSupportsFeature"] = { + "vers": "0.3.2" +} +apis["virDomainMigratePrepare"] = { + "vers": "0.3.2" +} +apis["virDomainMigratePerform"] = { + "vers": "0.3.2" +} +apis["virDomainMigrateFinish"] = { + "vers": "0.3.2" +} +apis["virDomainMigratePrepare2"] = { + "vers": "0.5.0" +} +apis["virDomainMigrateFinish2"] = { + "vers": "0.5.0" +} +apis["virDomainMigratePrepareTunnel"] = { + "vers": "0.7.2" +} + +apis["virDomainMigrateBegin3"] = { + "vers": "0.9.2" +} +apis["virDomainMigratePrepare3"] = { + "vers": "0.9.2" +} +apis["virDomainMigratePrepareTunnel3"] = { + "vers": "0.9.2" +} +apis["virDomainMigratePerform3"] = { + "vers": "0.9.2" +} +apis["virDomainMigrateFinish3"] = { + "vers": "0.9.2" +} +apis["virDomainMigrateConfirm3"] = { + "vers": "0.9.2" +} + +apis["virDomainMigrateBegin3Params"] = { + "vers": "1.1.0" +} +apis["virDomainMigratePrepare3Params"] = { + "vers": "1.1.0" +} +apis["virDomainMigratePrepareTunnel3Params"] = { + "vers": "1.1.0" +} +apis["virDomainMigratePerform3Params"] = { + "vers": "1.1.0" +} +apis["virDomainMigrateFinish3Params"] = { + "vers": "1.1.0" +} +apis["virDomainMigrateConfirm3Params"] = { + "vers": "1.1.0" +} + + +# Now we want to get the mapping between public APIs +# and driver struct fields. This lets us later match +# update the driver impls with the public APis. + +# Group name -> hash of APIs { fields -> api name } +groups = {} +ingrp = None +for drivertablefile in drivertablefiles: + with open(drivertablefile) as fh: + starttableprog = re.compile(r"struct _(vir\w*Driver)") + endtableprog = re.compile(r"};") + callbackprog = re.compile(r"^\s*vir(?:Drv)(\w+)\s+(\w+);\s*$") + ignoreapiprog = re.compile(r"\w+(Open|Close|URIProbe)") + for line in fh: + starttablematch = starttableprog.match(line) + if starttablematch is not None: + grp = starttablematch.group(1) + if grp != "virStateDriver" and grp != "virStreamDriver": + ingrp = grp + groups[ingrp] = { + "apis": {}, + "drivers": {} + } + elif ingrp != None: + callbackmatch = callbackprog.match(line) + if callbackmatch is not None: + name = callbackmatch.group(1) + field = callbackmatch.group(2) + + api = "vir" + name + if api in apis: + groups[ingrp]["apis"][field] = api + elif ignoreapiprog.match(api) != None: + continue + else: + raise Exception("driver %s does not have a public API" % name) + elif endtableprog.match(line): + ingrp = None + + +# Finally, we read all the primary driver files and extract +# the driver API tables from each one. + +for src in srcs: + with open(src) as fh: + groupsre = "|".join(groups.keys()) + groupsprog2 = re.compile(r"^\s*(static\s+)?(" + groupsre + r")\s+(\w+)\s*=\s*{") + groupsprog3 = re.compile(r"^\s*(static\s+)?(" + groupsre + r")\s+NAME\(\w+\)\s*=\s*{") + nodedevimplprog = re.compile(r".*/node_device_(\w+)\.c") + miscimplprog = re.compile(r".*/(\w+?)_((\w+)_)?(\w+)\.c") + callbackprog = re.compile(r"\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*(?:/\*\s*(\d+\.\d+\.\d+)\s*(?:-\s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$") + skipapiprog = re.compile(r"\w+(Open|Close|URIProbe)") + + ingrp = None + impl = None + for line in fh: + if ingrp is None: + m = groupsprog2.match(line) + if m is None: + m = groupsprog3.match(line) + if m is not None: + ingrp = m.group(2) + impl = src + + implmatch = nodedevimplprog.match(impl) + if implmatch is None: + implmatch = miscimplprog.match(impl) + if implmatch is None: + raise Exception("Unexpected impl format '%s'" % impl) + impl = implmatch.group(1) + + if impl in groups[ingrp]["drivers"]: + raise Exception("Group %s already contains %s" % (ingrp, impl)) + + groups[ingrp]["drivers"][impl] = {} + else: + callbackmatch = callbackprog.match(line) + if callbackmatch is not None: + api = callbackmatch.group(1) + meth = callbackmatch.group(2) + vers = callbackmatch.group(3) + deleted = callbackmatch.group(4) + + if api == "no" or api == "name": + continue + + if meth == "NULL" and deleted is None: + raise Exception("Method impl for %s is NULL, but no deleted version is provided" % api) + + if meth != "NULL" and deleted is not None: + raise Exception("Method impl for %s is non-NULL, but deleted version is provided" % api) + + if vers is None and api != "connectURIProbe": + raise Exception("Method %s in %s is missing version" % (meth, src)) + + if api not in groups[ingrp]["apis"]: + if skipapiprog.match(api): + continue + + raise Exception("Found unexpected method %s in %s" % (api, ingrp)) + + groups[ingrp]["drivers"][impl][api] = { + "vers": vers, + "deleted": deleted, + } + + if (api == "domainMigratePrepare" or + api == "domainMigratePrepare2" or + api == "domainMigratePrepare3"): + if "domainMigrate" not in groups[ingrp]["drivers"][impl]: + groups[ingrp]["drivers"][impl]["domainMigrate"] = { + "vers": vers, + } + elif line.find("}") != -1: + ingrp = None + + +# The '.open' driver method is used for 3 public APIs, so we +# have a bit of manual fixup todo with the per-driver versioning +# and support matrix + +groups["virHypervisorDriver"]["apis"]["openAuth"] = "virConnectOpenAuth" +groups["virHypervisorDriver"]["apis"]["openReadOnly"] = "virConnectOpenReadOnly" +groups["virHypervisorDriver"]["apis"]["domainMigrate"] = "virDomainMigrate" + +openAuthVers = (0 * 1000 * 1000) + (4 * 1000) + 0; + +for drv in groups["virHypervisorDriver"]["drivers"].keys(): + openVersStr = groups["virHypervisorDriver"]["drivers"][drv]["connectOpen"]["vers"] + openVers = 0 + if openVersStr != "Y": + openVersBits = openVersStr.split(".") + if len(openVersBits) != 3: + raise Exception("Expected 3 digit version for %s" % openVersStr) + openVers = (int(openVersBits[0]) * 1000 * 1000) + (int(openVersBits[1]) * 1000) + int(openVersBits[2]) + + # virConnectOpenReadOnly always matches virConnectOpen version + groups["virHypervisorDriver"]["drivers"][drv]["connectOpenReadOnly"] = \ + groups["virHypervisorDriver"]["drivers"][drv]["connectOpen"] + + # virConnectOpenAuth is always 0.4.0 if the driver existed + # before this time, otherwise it matches the version of + # the driver's virConnectOpen entry + if openVersStr == "Y" or openVers >= openAuthVers: + vers = openVersStr + else: + vers = "0.4.0" + groups["virHypervisorDriver"]["drivers"][drv]["connectOpenAuth"] = { + "vers": vers, + } + + +# Another special case for the virDomainCreateLinux which was replaced +# with virDomainCreateXML +groups["virHypervisorDriver"]["apis"]["domainCreateLinux"] = "virDomainCreateLinux"; + +createAPIVers = (0 * 1000 * 1000) + (0 * 1000) + 3; + +for drv in groups["virHypervisorDriver"]["drivers"].keys(): + if "domainCreateXML" not in groups["virHypervisorDriver"]["drivers"][drv]: + continue + createVersStr = groups["virHypervisorDriver"]["drivers"][drv]["domainCreateXML"]["vers"] + createVers = 0 + if createVersStr != "Y": + createVersBits = createVersStr.split(".") + if len(createVersBits) != 3: + raise Exception("Expected 3 digit version for %s" % createVersStr) + createVers = (int(createVersBits[0]) * 1000 * 1000) + (int(createVersBits[1]) * 1000) + int(createVersBits[2]) + + # virCreateLinux is always 0.0.3 if the driver existed + # before this time, otherwise it matches the version of + # the driver's virCreateXML entry + if createVersStr == "Y" or createVers >= createAPIVers: + vers = createVersStr + else: + vers = "0.0.3" + + groups["virHypervisorDriver"]["drivers"][drv]["domainCreateLinux"] = { + "vers": vers, + } + + +# Finally we generate the HTML file with the tables + +print('''<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> +<body class="hvsupport"> +<h1>libvirt API support matrix</h1> + +<ul id="toc"></ul> + +<p> +This page documents which <a href="html/">libvirt calls</a> work on +which libvirt drivers / hypervisors, and which version the API appeared +in. If a hypervisor driver later dropped support for the API, the version +when it was removed is also mentioned (highlighted in +<span class="removedhv">dark red</span>). +</p> +''') + +for grp in sorted(groups.keys()): + print("<h2><a id=\"%s\">%s</a></h2>" % (grp, groupheaders[grp])) + print('''<table class="top_table"> +<thead> +<tr> +<th>API</th> +<th>Version</th>''') + + for drv in sorted(groups[grp]["drivers"].keys()): + print(" <th>%s</th>" % drv) + + + print('''</tr> +</thead> +<tbody>''') + + row = 0 + def sortkey(field): + return groups[grp]["apis"][field] + for field in sorted(groups[grp]["apis"].keys(), key=sortkey): + api = groups[grp]["apis"][field] + vers = apis[api]["vers"] + htmlgrp = apis[api].get("file") + print("<tr>") + + if htmlgrp is not None: + print('''<td>\n<a href=\"html/libvirt-%s.html#%s\">%s</a>\n</td>''' % (htmlgrp, api, api)) + else: + print("<td>\n%s</td>" % api) + + print("<td>%s</td>" % vers) + + for drv in sorted(groups[grp]["drivers"].keys()): + info = "" + if field in groups[grp]["drivers"][drv]: + vers = groups[grp]["drivers"][drv][field]["vers"] + if vers is not None: + info = info + vers + + deleted = groups[grp]["drivers"][drv][field].get("deleted") + if deleted is not None: + info = info + (''' - <span class="removedhv">%s</span>''' % deleted) + + print("<td>%s</td>" % info) + + print("</tr>") + + row = row + 1 + if (row % 15) == 0: + print('''<tr> +<th>API</th> +<th>Version</th>''') + + for drv in sorted(groups[grp]["drivers"].keys()): + print(" <th>%s</th>" % drv) + + print("</tr>") + + print("</tbody>\n</table>") + +print("</body>\n</html>") -- 2.21.0
On Fri, 2019-08-30 at 13:25 +0100, Daniel P. Berrangé wrote:
> As part of an goal to eliminate Perl from libvirt build tools,
> rewrite the hvsupport.pl tool in Python.
>
> This was a straight conversion, manually going line-by-line to
> change the syntax from Perl to Python. Thus the overall structure
> of the file and approach is the same.
>
> The new impl generates byte-for-byte identical output to the
> old impl.
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
> docs/Makefile.am | 6 +-
> docs/hvsupport.pl | 458 --------------------------------------------
> -
> docs/hvsupport.py | 463
> ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 466 insertions(+), 461 deletions(-)
> delete mode 100755 docs/hvsupport.pl
> create mode 100755 docs/hvsupport.py
>
> diff --git a/docs/Makefile.am b/docs/Makefile.am
> index 1cdb584b0b..f7aba5499d 100644
> --- a/docs/Makefile.am
> +++ b/docs/Makefile.am
> @@ -194,7 +194,7 @@ EXTRA_DIST= \
> $(internals_html_in) $(internals_html) $(fonts) \
> $(kbase_html_in) $(kbase_html) \
> aclperms.htmlinc \
> - hvsupport.pl \
> + hvsupport.py \
> $(schema_DATA)
>
> acl_generated = aclperms.htmlinc
> @@ -230,11 +230,11 @@ web: $(dot_html) $(internals_html)
> $(kbase_html) \
>
> hvsupport.html: $(srcdir)/hvsupport.html.in
>
> -$(srcdir)/hvsupport.html.in: $(srcdir)/hvsupport.pl $(api_DATA) \
> +$(srcdir)/hvsupport.html.in: $(srcdir)/hvsupport.py $(api_DATA) \
> $(top_srcdir)/src/libvirt_public.syms \
> $(top_srcdir)/src/libvirt_qemu.syms
> $(top_srcdir)/src/libvirt_lxc.syms \
> $(top_srcdir)/src/driver.h
> - $(AM_V_GEN)$(PERL) $(srcdir)/hvsupport.pl $(top_srcdir)/src >
> $@ \
> + $(AM_V_GEN)$(PYTHON) $(srcdir)/hvsupport.py $(top_srcdir)/src >
> $@ \
> || { rm $@ && exit 1; }
>
> news.html.in: \
> diff --git a/docs/hvsupport.pl b/docs/hvsupport.pl
> deleted file mode 100755
> index 494b8a27ec..0000000000
> --- a/docs/hvsupport.pl
> +++ /dev/null
> @@ -1,458 +0,0 @@
> -#!/usr/bin/env perl
> -
> -use strict;
> -use warnings;
> -
> -use File::Find;
> -
> -die "syntax: $0 SRCDIR\n" unless int(@ARGV) == 1;
> -
> -my $srcdir = shift @ARGV;
> -
> -my $symslibvirt = "$srcdir/libvirt_public.syms";
> -my $symsqemu = "$srcdir/libvirt_qemu.syms";
> -my $symslxc = "$srcdir/libvirt_lxc.syms";
> -my @drivertable = (
> - "$srcdir/driver-hypervisor.h",
> - "$srcdir/driver-interface.h",
> - "$srcdir/driver-network.h",
> - "$srcdir/driver-nodedev.h",
> - "$srcdir/driver-nwfilter.h",
> - "$srcdir/driver-secret.h",
> - "$srcdir/driver-state.h",
> - "$srcdir/driver-storage.h",
> - "$srcdir/driver-stream.h",
> - );
> -
> -my %groupheaders = (
> - "virHypervisorDriver" => "Hypervisor APIs",
> - "virNetworkDriver" => "Virtual Network APIs",
> - "virInterfaceDriver" => "Host Interface APIs",
> - "virNodeDeviceDriver" => "Host Device APIs",
> - "virStorageDriver" => "Storage Pool APIs",
> - "virSecretDriver" => "Secret APIs",
> - "virNWFilterDriver" => "Network Filter APIs",
> - );
> -
> -
> -my @srcs;
> -find({
> - wanted => sub {
> - if
> (m!$srcdir/.*/\w+_(driver|common|tmpl|monitor|hal|udev)\.c$!) {
> - push @srcs, $_ if $_ !~ /vbox_driver\.c/;
> - }
> - }, no_chdir => 1}, $srcdir);
> -
> -# Map API functions to the header and documentation files they're in
> -# so that we can generate proper hyperlinks to their documentation.
> -#
> -# The function names are grep'd from the XML output of apibuild.py.
> -sub getAPIFilenames {
> - my $filename = shift;
> -
> - my %files;
> - my $line;
> -
> - open FILE, "<", $filename or die "cannot read $filename: $!";
> -
> - while (defined($line = <FILE>)) {
> - if ($line =~ /function name='([^']+)' file='([^']+)'/) {
> - $files{$1} = $2;
> - }
> - }
> -
> - close FILE;
> -
> - if (keys %files == 0) {
> - die "No functions found in $filename. Has the apibuild.py
> output changed?";
> - }
> - return \%files;
> -}
> -
> -sub parseSymsFile {
> - my $apisref = shift;
> - my $prefix = shift;
> - my $filename = shift;
> - my $xmlfilename = shift;
> -
> - my $line;
> - my $vers;
> - my $prevvers;
> -
> - my $filenames = getAPIFilenames($xmlfilename);
> -
> - open FILE, "<$filename"
> - or die "cannot read $filename: $!";
> -
> - while (defined($line = <FILE>)) {
> - chomp $line;
> - next if $line =~ /^\s*#/;
> - next if $line =~ /^\s*$/;
> - next if $line =~ /^\s*(global|local):/;
> - if ($line =~ /^\s*${prefix}_(\d+\.\d+\.\d+)\s*{\s*$/) {
> - if (defined $vers) {
> - die "malformed syms file";
> - }
> - $vers = $1;
> - } elsif ($line =~ /\s*}\s*;\s*$/) {
> - if (defined $prevvers) {
> - die "malformed syms file";
> - }
> - $prevvers = $vers;
> - $vers = undef;
> - } elsif ($line =~
> /\s*}\s*${prefix}_(\d+\.\d+\.\d+)\s*;\s*$/) {
> - if ($1 ne $prevvers) {
> - die "malformed syms file $1 != $vers";
> - }
> - $prevvers = $vers;
> - $vers = undef;
> - } elsif ($line =~ /\s*(\w+)\s*;\s*$/) {
> - $$apisref{$1} = {};
> - $$apisref{$1}->{vers} = $vers;
> - $$apisref{$1}->{file} = $$filenames{$1};
> - } else {
> - die "unexpected data $line\n";
> - }
> - }
> -
> - close FILE;
> -}
> -
> -my %apis;
> -# Get the list of all public APIs and their corresponding version
> -parseSymsFile(\%apis, "LIBVIRT", $symslibvirt,
> "$srcdir/../docs/libvirt-api.xml");
> -
> -# And the same for the QEMU specific APIs
> -parseSymsFile(\%apis, "LIBVIRT_QEMU", $symsqemu,
> "$srcdir/../docs/libvirt-qemu-api.xml");
> -
> -# And the same for the LXC specific APIs
> -parseSymsFile(\%apis, "LIBVIRT_LXC", $symslxc,
> "$srcdir/../docs/libvirt-lxc-api.xml");
> -
> -
> -# Some special things which aren't public APIs,
> -# but we want to report
> -$apis{virConnectSupportsFeature}->{vers} = "0.3.2";
> -$apis{virDomainMigratePrepare}->{vers} = "0.3.2";
> -$apis{virDomainMigratePerform}->{vers} = "0.3.2";
> -$apis{virDomainMigrateFinish}->{vers} = "0.3.2";
> -$apis{virDomainMigratePrepare2}->{vers} = "0.5.0";
> -$apis{virDomainMigrateFinish2}->{vers} = "0.5.0";
> -$apis{virDomainMigratePrepareTunnel}->{vers} = "0.7.2";
> -
> -$apis{virDomainMigrateBegin3}->{vers} = "0.9.2";
> -$apis{virDomainMigratePrepare3}->{vers} = "0.9.2";
> -$apis{virDomainMigratePrepareTunnel3}->{vers} = "0.9.2";
> -$apis{virDomainMigratePerform3}->{vers} = "0.9.2";
> -$apis{virDomainMigrateFinish3}->{vers} = "0.9.2";
> -$apis{virDomainMigrateConfirm3}->{vers} = "0.9.2";
> -
> -$apis{virDomainMigrateBegin3Params}->{vers} = "1.1.0";
> -$apis{virDomainMigratePrepare3Params}->{vers} = "1.1.0";
> -$apis{virDomainMigratePrepareTunnel3Params}->{vers} = "1.1.0";
> -$apis{virDomainMigratePerform3Params}->{vers} = "1.1.0";
> -$apis{virDomainMigrateFinish3Params}->{vers} = "1.1.0";
> -$apis{virDomainMigrateConfirm3Params}->{vers} = "1.1.0";
> -
> -
> -
> -# Now we want to get the mapping between public APIs
> -# and driver struct fields. This lets us later match
> -# update the driver impls with the public APis.
> -
> -my $line;
> -
> -# Group name -> hash of APIs { fields -> api name }
> -my %groups;
> -my $ingrp;
> -foreach my $drivertable (@drivertable) {
> - open FILE, "<$drivertable"
> - or die "cannot read $drivertable: $!";
> -
> - while (defined($line = <FILE>)) {
> - if ($line =~ /struct _(vir\w*Driver)/) {
> - my $grp = $1;
> - if ($grp ne "virStateDriver" &&
> - $grp ne "virStreamDriver") {
> - $ingrp = $grp;
> - $groups{$ingrp} = { apis => {}, drivers => {} };
> - }
> - } elsif ($ingrp) {
> - if ($line =~ /^\s*vir(?:Drv)(\w+)\s+(\w+);\s*$/) {
> - my $field = $2;
> - my $name = $1;
> -
> - my $api;
> - if (exists $apis{"vir$name"}) {
> - $api = "vir$name";
> - } elsif ($name =~ /\w+(Open|Close|URIProbe)/) {
> - next;
> - } else {
> - die "driver $name does not have a public API";
> - }
> - $groups{$ingrp}->{apis}->{$field} = $api;
> - } elsif ($line =~ /};/) {
> - $ingrp = undef;
> - }
> - }
> - }
> -
> - close FILE;
> -}
> -
> -
> -# Finally, we read all the primary driver files and extract
> -# the driver API tables from each one.
> -
> -foreach my $src (@srcs) {
> - open FILE, "<$src" or
> - die "cannot read $src: $!";
> -
> - my $groups_regex = join("|", keys %groups);
> - $ingrp = undef;
> - my $impl;
> - while (defined($line = <FILE>)) {
> - if (!$ingrp) {
> - # skip non-matching lines early to save time
> - next if not $line =~ /$groups_regex/;
> -
> - if ($line =~
> /^\s*(?:static\s+)?($groups_regex)\s+(\w+)\s*=\s*{/ ||
> - $line =~
> /^\s*(?:static\s+)?($groups_regex)\s+NAME\(\w+\)\s*=\s*{/) {
> - $ingrp = $1;
> - $impl = $src;
> -
> - if ($impl =~ m,.*/node_device_(\w+)\.c,) {
> - $impl = $1;
> - } else {
> - $impl =~ s,.*/(\w+?)_((\w+)_)?(\w+)\.c,$1,;
> - }
> -
> - if ($groups{$ingrp}->{drivers}->{$impl}) {
> - die "Group $ingrp already contains $impl";
> - }
> -
> - $groups{$ingrp}->{drivers}->{$impl} = {};
> - }
> -
> - } else {
> - if ($line =~
> m!\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*(?:/\*\s*(\d+\.\d+\.\d+)\s*(?:-
> \s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$!) {
> - my $api = $1;
> - my $meth = $2;
> - my $vers = $3;
> - my $deleted = $4;
> -
> - next if $api eq "no" || $api eq "name";
> -
> - if ($meth eq "NULL" && !defined $deleted) {
> - die "Method impl for $api is NULL, but no
> deleted version is provided";
> - }
> - if ($meth ne "NULL" && defined $deleted) {
> - die "Method impl for $api is non-NULL, but
> deleted version is provided";
> - }
> -
> - die "Method $meth in $src is missing version" unless
> defined $vers || $api eq "connectURIProbe";
> -
> - if (!exists($groups{$ingrp}->{apis}->{$api})) {
> - next if $api =~ /\w(Open|Close|URIProbe)/;
> -
> - die "Found unexpected method $api in $ingrp\n";
> - }
> -
> - $groups{$ingrp}->{drivers}->{$impl}->{$api} = {};
> - $groups{$ingrp}->{drivers}->{$impl}->{$api}->{vers}
> = $vers;
> - $groups{$ingrp}->{drivers}->{$impl}->{$api}-
> >{deleted} = $deleted;
> - if ($api eq "domainMigratePrepare" ||
> - $api eq "domainMigratePrepare2" ||
> - $api eq "domainMigratePrepare3") {
> - if (!$groups{$ingrp}->{drivers}->{$impl}-
> >{"domainMigrate"}) {
> - $groups{$ingrp}->{drivers}->{$impl}-
> >{"domainMigrate"} = {};
> - $groups{$ingrp}->{drivers}->{$impl}-
> >{"domainMigrate"}->{vers} = $vers;
> - }
> - }
> -
> - } elsif ($line =~ /}/) {
> - $ingrp = undef;
> - }
> - }
> - }
> -
> - close FILE;
> -}
> -
> -
> -# The '.open' driver method is used for 3 public APIs, so we
> -# have a bit of manual fixup todo with the per-driver versioning
> -# and support matrix
> -
> -$groups{virHypervisorDriver}->{apis}->{"openAuth"} =
> "virConnectOpenAuth";
> -$groups{virHypervisorDriver}->{apis}->{"openReadOnly"} =
> "virConnectOpenReadOnly";
> -$groups{virHypervisorDriver}->{apis}->{"domainMigrate"} =
> "virDomainMigrate";
> -
> -my $openAuthVers = (0 * 1000 * 1000) + (4 * 1000) + 0;
> -
> -foreach my $drv (keys %{$groups{"virHypervisorDriver"}->{drivers}})
> {
> - my $openVersStr = $groups{"virHypervisorDriver"}->{drivers}-
> >{$drv}->{"connectOpen"}->{vers};
> - my $openVers;
> - if ($openVersStr =~ /(\d+)\.(\d+)\.(\d+)/) {
> - $openVers = ($1 * 1000 * 1000) + ($2 * 1000) + $3;
> - }
> -
> - # virConnectOpenReadOnly always matches virConnectOpen version
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"connectOpenReadOnly"} =
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"connectOpen"};
> -
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"connectOpenAuth"} = {};
> -
> - # virConnectOpenAuth is always 0.4.0 if the driver existed
> - # before this time, otherwise it matches the version of
> - # the driver's virConnectOpen entry
> - if ($openVersStr eq "Y" ||
> - $openVers >= $openAuthVers) {
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"connectOpenAuth"}->{vers} = $openVersStr;
> - } else {
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"connectOpenAuth"}->{vers} = "0.4.0";
> - }
> -}
> -
> -
> -# Another special case for the virDomainCreateLinux which was
> replaced
> -# with virDomainCreateXML
> -$groups{virHypervisorDriver}->{apis}->{"domainCreateLinux"} =
> "virDomainCreateLinux";
> -
> -my $createAPIVers = (0 * 1000 * 1000) + (0 * 1000) + 3;
> -
> -foreach my $drv (keys %{$groups{"virHypervisorDriver"}->{drivers}})
> {
> - my $createVersStr = $groups{"virHypervisorDriver"}->{drivers}-
> >{$drv}->{"domainCreateXML"}->{vers};
> - next unless defined $createVersStr;
> - my $createVers;
> - if ($createVersStr =~ /(\d+)\.(\d+)\.(\d+)/) {
> - $createVers = ($1 * 1000 * 1000) + ($2 * 1000) + $3;
> - }
> -
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"domainCreateLinux"} = {};
> -
> - # virCreateLinux is always 0.0.3 if the driver existed
> - # before this time, otherwise it matches the version of
> - # the driver's virCreateXML entry
> - if ($createVersStr eq "Y" ||
> - $createVers >= $createAPIVers) {
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"domainCreateLinux"}->{vers} = $createVersStr;
> - } else {
> - $groups{"virHypervisorDriver"}->{drivers}->{$drv}-
> >{"domainCreateLinux"}->{vers} = "0.0.3";
> - }
> -}
> -
> -
> -# Finally we generate the HTML file with the tables
> -
> -print <<EOF;
> -<?xml version="1.0" encoding="UTF-8"?>
> -<!DOCTYPE html>
> -<html xmlns="http://www.w3.org/1999/xhtml">
> -<body class="hvsupport">
> -<h1>libvirt API support matrix</h1>
> -
> -<ul id="toc"></ul>
> -
> -<p>
> -This page documents which <a href="html/">libvirt calls</a> work on
> -which libvirt drivers / hypervisors, and which version the API
> appeared
> -in. If a hypervisor driver later dropped support for the API, the
> version
> -when it was removed is also mentioned (highlighted in
> -<span class="removedhv">dark red</span>).
> -</p>
> -
> -EOF
> -
> - foreach my $grp (sort { $a cmp $b } keys %groups) {
> - print "<h2><a id=\"$grp\">", $groupheaders{$grp}, "</a></h2>\n";
> - print <<EOF;
> -<table class="top_table">
> -<thead>
> -<tr>
> -<th>API</th>
> -<th>Version</th>
> -EOF
> -
> - foreach my $drv (sort { $a cmp $b } keys %{$groups{$grp}-
> >{drivers}}) {
> - print " <th>$drv</th>\n";
> - }
> -
> - print <<EOF;
> -</tr>
> -</thead>
> -<tbody>
> -EOF
> -
> - my $row = 0;
> - foreach my $field (sort {
> - $groups{$grp}->{apis}->{$a}
> - cmp
> - $groups{$grp}->{apis}->{$b}
> - } keys %{$groups{$grp}->{apis}}) {
> - my $api = $groups{$grp}->{apis}->{$field};
> - my $vers = $apis{$api}->{vers};
> - my $htmlgrp = $apis{$api}->{file};
> - print <<EOF;
> -<tr>
> -<td>
> -EOF
> -
> - if (defined $htmlgrp) {
> - print <<EOF;
> -<a href=\"html/libvirt-$htmlgrp.html#$api\">$api</a>
> -EOF
> -
> - } else {
> - print $api;
> - }
> - print <<EOF;
> -</td>
> -<td>$vers</td>
> -EOF
> -
> - foreach my $drv (sort {$a cmp $b } keys %{$groups{$grp}-
> >{drivers}}) {
> - print "<td>";
> - if (exists $groups{$grp}->{drivers}->{$drv}->{$field}) {
> - if ($groups{$grp}->{drivers}->{$drv}->{$field}-
> >{vers}) {
> - print $groups{$grp}->{drivers}->{$drv}-
> >{$field}->{vers};
> - }
> - if ($groups{$grp}->{drivers}->{$drv}->{$field}-
> >{deleted}) {
> - print " - <span class=\"removedhv\">",
> $groups{$grp}->{drivers}->{$drv}->{$field}->{deleted}, "</span>";
> - }
> - }
> - print "</td>\n";
> - }
> -
> - print <<EOF;
> -</tr>
> -EOF
> -
> - $row++;
> - if (($row % 15) == 0) {
> - print <<EOF;
> -<tr>
> -<th>API</th>
> -<th>Version</th>
> -EOF
> -
> - foreach my $drv (sort { $a cmp $b } keys
> %{$groups{$grp}->{drivers}}) {
> - print " <th>$drv</th>\n";
> - }
> -
> - print <<EOF;
> -</tr>
> -EOF
> - }
> -
> - }
> -
> - print <<EOF;
> -</tbody>
> -</table>
> -EOF
> -}
> -
> -print <<EOF;
> -</body>
> -</html>
> -EOF
> diff --git a/docs/hvsupport.py b/docs/hvsupport.py
> new file mode 100755
> index 0000000000..502389c25f
> --- /dev/null
> +++ b/docs/hvsupport.py
> @@ -0,0 +1,463 @@
> +#!/usr/bin/env python
> +
> +from __future__ import print_function
> +
> +import sys
> +import os.path
> +import re
> +
> +if len(sys.argv) != 2:
> + print("syntax: %s SRCDIR\n" % sys.argv[0], fh=sys.stderr)
fh is not a valid parameter for the print function. I guess you meant
file=sys.stderr instead.
> +
> +srcdir = sys.argv[1]
> +
> +symslibvirt = os.path.join(srcdir, "libvirt_public.syms")
> +symsqemu = os.path.join(srcdir, "libvirt_qemu.syms")
> +symslxc = os.path.join(srcdir, "libvirt_lxc.syms")
> +drivertablefiles = [
> + os.path.join(srcdir, "driver-hypervisor.h"),
> + os.path.join(srcdir, "driver-interface.h"),
> + os.path.join(srcdir, "driver-network.h"),
> + os.path.join(srcdir, "driver-nodedev.h"),
> + os.path.join(srcdir, "driver-nwfilter.h"),
> + os.path.join(srcdir, "driver-secret.h"),
> + os.path.join(srcdir, "driver-state.h"),
> + os.path.join(srcdir, "driver-storage.h"),
> + os.path.join(srcdir, "driver-stream.h"),
> +]
> +
> +groupheaders = {
> + "virHypervisorDriver": "Hypervisor APIs",
> + "virNetworkDriver": "Virtual Network APIs",
> + "virInterfaceDriver": "Host Interface APIs",
> + "virNodeDeviceDriver": "Host Device APIs",
> + "virStorageDriver": "Storage Pool APIs",
> + "virSecretDriver": "Secret APIs",
> + "virNWFilterDriver": "Network Filter APIs",
> +}
> +
> +
> +srcs = []
> +for root, dirs, files in os.walk(srcdir):
> + for file in files:
> + if ((file.endswith("driver.c") and
> + not file.endswith("vbox_driver.c")) or
> + file.endswith("common.c") or
> + file.endswith("tmpl.c") or
> + file.endswith("monitor.c") or
> + file.endswith("hal.c") or
> + file.endswith("udev.c")):
> + srcs.append(os.path.join(root, file))
> +
> +
> +# Map API functions to the header and documentation files they're in
> +# so that we can generate proper hyperlinks to their documentation.
> +#
> +# The function names are grep'd from the XML output of apibuild.py.
> +def getAPIFilenames(filename):
> + files = {}
> +
> + with open(filename) as fh:
> + prog = re.compile(r"\s*<function name='([^']+)'
> file='([^']+)'.*")
> + for line in fh:
> + res = prog.match(line)
> + if res is not None:
> + files[res.group(1)] = res.group(2)
> +
> + if len(files) == 0:
> + raise Exception("No functions found in %s. Has the
> apibuild.py output changed?" % filename)
> +
> + return files;
> +
> +
> +def parseSymsFile(apisref, prefix, filename, xmlfilename):
> + vers = None
> + prevvers = None
> +
> + filenames = getAPIFilenames(xmlfilename)
> +
> + with open(filename) as fh:
> + groupstartprog =
> re.compile(r"^\s*%s_(\d+\.\d+\.\d+)\s*{\s*$" % prefix)
> + groupendprog1 = re.compile(r"^\s*}\s*;\s*$")
> + groupendprog2 =
> re.compile(r"^\s*}\s*%s_(\d+\.\d+\.\d+)\s*;\s*$" % prefix)
> + symbolprog = re.compile(r"^\s*(\w+)\s*;\s*$")
> + for line in fh:
> + line = line.strip()
> +
> + if line == "":
> + continue
> + if line[0] == '#':
> + continue
> + if line.startswith("global:"):
> + continue
> + if line.startswith("local:"):
> + continue
> +
> + groupstartmatch = groupstartprog.match(line)
> + groupendmatch1 = groupendprog1.match(line)
> + groupendmatch2 = groupendprog2.match(line)
> + symbolmatch = symbolprog.match(line)
> + if groupstartmatch is not None:
> + if vers is not None:
> + raise Exception("malformed syms file when
> starting group")
> +
> + vers = groupstartmatch.group(1)
> + elif groupendmatch1 is not None:
> + if prevvers is not None:
> + raise Exception("malformed syms file when ending
> group")
> +
> + prevvers = vers
> + vers = None
> + elif groupendmatch2 is not None:
> + if groupendmatch2.group(1) != prevvers:
> + raise Exception("malformed syms file %s != %s
> when ending group" % (
> + groupendmatch2.group(1), prevvers))
> +
> + prevvers = vers
> + vers = None
> + elif symbolmatch is not None:
> + name = symbolmatch.group(1)
> + apisref[name] = {
> + "vers": vers,
> + "file": filenames.get(name),
> + }
> + else:
> + raise Exception("unexpected data %s" % line)
> +
> +
> +apis = {}
> +# Get the list of all public APIs and their corresponding version
> +parseSymsFile(apis, "LIBVIRT", symslibvirt, os.path.join(srcdir,
> "../docs/libvirt-api.xml"))
> +
> +# And the same for the QEMU specific APIs
> +parseSymsFile(apis, "LIBVIRT_QEMU", symsqemu, os.path.join(srcdir,
> "../docs/libvirt-qemu-api.xml"))
> +
> +# And the same for the LXC specific APIs
> +parseSymsFile(apis, "LIBVIRT_LXC", symslxc, os.path.join(srcdir,
> "../docs/libvirt-lxc-api.xml"))
> +
> +
> +# Some special things which aren't public APIs,
> +# but we want to report
> +apis["virConnectSupportsFeature"] = {
> + "vers": "0.3.2"
> +}
> +apis["virDomainMigratePrepare"] = {
> + "vers": "0.3.2"
> +}
> +apis["virDomainMigratePerform"] = {
> + "vers": "0.3.2"
> +}
> +apis["virDomainMigrateFinish"] = {
> + "vers": "0.3.2"
> +}
> +apis["virDomainMigratePrepare2"] = {
> + "vers": "0.5.0"
> +}
> +apis["virDomainMigrateFinish2"] = {
> + "vers": "0.5.0"
> +}
> +apis["virDomainMigratePrepareTunnel"] = {
> + "vers": "0.7.2"
> +}
> +
> +apis["virDomainMigrateBegin3"] = {
> + "vers": "0.9.2"
> +}
> +apis["virDomainMigratePrepare3"] = {
> + "vers": "0.9.2"
> +}
> +apis["virDomainMigratePrepareTunnel3"] = {
> + "vers": "0.9.2"
> +}
> +apis["virDomainMigratePerform3"] = {
> + "vers": "0.9.2"
> +}
> +apis["virDomainMigrateFinish3"] = {
> + "vers": "0.9.2"
> +}
> +apis["virDomainMigrateConfirm3"] = {
> + "vers": "0.9.2"
> +}
> +
> +apis["virDomainMigrateBegin3Params"] = {
> + "vers": "1.1.0"
> +}
> +apis["virDomainMigratePrepare3Params"] = {
> + "vers": "1.1.0"
> +}
> +apis["virDomainMigratePrepareTunnel3Params"] = {
> + "vers": "1.1.0"
> +}
> +apis["virDomainMigratePerform3Params"] = {
> + "vers": "1.1.0"
> +}
> +apis["virDomainMigrateFinish3Params"] = {
> + "vers": "1.1.0"
> +}
> +apis["virDomainMigrateConfirm3Params"] = {
> + "vers": "1.1.0"
> +}
> +
> +
> +# Now we want to get the mapping between public APIs
> +# and driver struct fields. This lets us later match
> +# update the driver impls with the public APis.
> +
> +# Group name -> hash of APIs { fields -> api name }
> +groups = {}
> +ingrp = None
> +for drivertablefile in drivertablefiles:
> + with open(drivertablefile) as fh:
> + starttableprog = re.compile(r"struct _(vir\w*Driver)")
> + endtableprog = re.compile(r"};")
> + callbackprog =
> re.compile(r"^\s*vir(?:Drv)(\w+)\s+(\w+);\s*$")
> + ignoreapiprog = re.compile(r"\w+(Open|Close|URIProbe)")
> + for line in fh:
> + starttablematch = starttableprog.match(line)
> + if starttablematch is not None:
> + grp = starttablematch.group(1)
> + if grp != "virStateDriver" and grp !=
> "virStreamDriver":
> + ingrp = grp
> + groups[ingrp] = {
> + "apis": {},
> + "drivers": {}
> + }
> + elif ingrp != None:
> + callbackmatch = callbackprog.match(line)
> + if callbackmatch is not None:
> + name = callbackmatch.group(1)
> + field = callbackmatch.group(2)
> +
> + api = "vir" + name
> + if api in apis:
> + groups[ingrp]["apis"][field] = api
> + elif ignoreapiprog.match(api) != None:
> + continue
> + else:
> + raise Exception("driver %s does not have a
> public API" % name)
> + elif endtableprog.match(line):
> + ingrp = None
> +
> +
> +# Finally, we read all the primary driver files and extract
> +# the driver API tables from each one.
> +
> +for src in srcs:
> + with open(src) as fh:
> + groupsre = "|".join(groups.keys())
> + groupsprog2 = re.compile(r"^\s*(static\s+)?(" + groupsre +
> r")\s+(\w+)\s*=\s*{")
> + groupsprog3 = re.compile(r"^\s*(static\s+)?(" + groupsre +
> r")\s+NAME\(\w+\)\s*=\s*{")
> + nodedevimplprog = re.compile(r".*/node_device_(\w+)\.c")
> + miscimplprog = re.compile(r".*/(\w+?)_((\w+)_)?(\w+)\.c")
> + callbackprog =
> re.compile(r"\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*(?:/\*\s*(\d+\.\d+\.\d+)\s
> *(?:-\s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$")
> + skipapiprog = re.compile(r"\w+(Open|Close|URIProbe)")
> +
> + ingrp = None
> + impl = None
> + for line in fh:
> + if ingrp is None:
> + m = groupsprog2.match(line)
> + if m is None:
> + m = groupsprog3.match(line)
> + if m is not None:
> + ingrp = m.group(2)
> + impl = src
> +
> + implmatch = nodedevimplprog.match(impl)
> + if implmatch is None:
> + implmatch = miscimplprog.match(impl)
> + if implmatch is None:
> + raise Exception("Unexpected impl format
> '%s'" % impl)
> + impl = implmatch.group(1)
> +
> + if impl in groups[ingrp]["drivers"]:
> + raise Exception("Group %s already contains
> %s" % (ingrp, impl))
> +
> + groups[ingrp]["drivers"][impl] = {}
> + else:
> + callbackmatch = callbackprog.match(line)
> + if callbackmatch is not None:
> + api = callbackmatch.group(1)
> + meth = callbackmatch.group(2)
> + vers = callbackmatch.group(3)
> + deleted = callbackmatch.group(4)
> +
> + if api == "no" or api == "name":
> + continue
> +
> + if meth == "NULL" and deleted is None:
> + raise Exception("Method impl for %s is NULL,
> but no deleted version is provided" % api)
> +
> + if meth != "NULL" and deleted is not None:
> + raise Exception("Method impl for %s is non-
> NULL, but deleted version is provided" % api)
> +
> + if vers is None and api != "connectURIProbe":
> + raise Exception("Method %s in %s is missing
> version" % (meth, src))
> +
> + if api not in groups[ingrp]["apis"]:
> + if skipapiprog.match(api):
> + continue
> +
> + raise Exception("Found unexpected method %s
> in %s" % (api, ingrp))
> +
> + groups[ingrp]["drivers"][impl][api] = {
> + "vers": vers,
> + "deleted": deleted,
> + }
> +
> + if (api == "domainMigratePrepare" or
> + api == "domainMigratePrepare2" or
> + api == "domainMigratePrepare3"):
> + if "domainMigrate" not in
> groups[ingrp]["drivers"][impl]:
> + groups[ingrp]["drivers"][impl]["domainMi
> grate"] = {
> + "vers": vers,
> + }
> + elif line.find("}") != -1:
> + ingrp = None
> +
> +
> +# The '.open' driver method is used for 3 public APIs, so we
> +# have a bit of manual fixup todo with the per-driver versioning
> +# and support matrix
> +
> +groups["virHypervisorDriver"]["apis"]["openAuth"] =
> "virConnectOpenAuth"
> +groups["virHypervisorDriver"]["apis"]["openReadOnly"] =
> "virConnectOpenReadOnly"
> +groups["virHypervisorDriver"]["apis"]["domainMigrate"] =
> "virDomainMigrate"
> +
> +openAuthVers = (0 * 1000 * 1000) + (4 * 1000) + 0;
> +
> +for drv in groups["virHypervisorDriver"]["drivers"].keys():
> + openVersStr =
> groups["virHypervisorDriver"]["drivers"][drv]["connectOpen"]["vers"]
> + openVers = 0
> + if openVersStr != "Y":
> + openVersBits = openVersStr.split(".")
> + if len(openVersBits) != 3:
> + raise Exception("Expected 3 digit version for %s" %
> openVersStr)
> + openVers = (int(openVersBits[0]) * 1000 * 1000) +
> (int(openVersBits[1]) * 1000) + int(openVersBits[2])
> +
> + # virConnectOpenReadOnly always matches virConnectOpen version
> + groups["virHypervisorDriver"]["drivers"][drv]["connectOpenReadOn
> ly"] = \
> + groups["virHypervisorDriver"]["drivers"][drv]["connectOpen"]
> +
> + # virConnectOpenAuth is always 0.4.0 if the driver existed
> + # before this time, otherwise it matches the version of
> + # the driver's virConnectOpen entry
> + if openVersStr == "Y" or openVers >= openAuthVers:
> + vers = openVersStr
> + else:
> + vers = "0.4.0"
> + groups["virHypervisorDriver"]["drivers"][drv]["connectOpenAuth"]
> = {
> + "vers": vers,
> + }
> +
> +
> +# Another special case for the virDomainCreateLinux which was
> replaced
> +# with virDomainCreateXML
> +groups["virHypervisorDriver"]["apis"]["domainCreateLinux"] =
> "virDomainCreateLinux";
> +
> +createAPIVers = (0 * 1000 * 1000) + (0 * 1000) + 3;
> +
> +for drv in groups["virHypervisorDriver"]["drivers"].keys():
> + if "domainCreateXML" not in
> groups["virHypervisorDriver"]["drivers"][drv]:
> + continue
> + createVersStr =
> groups["virHypervisorDriver"]["drivers"][drv]["domainCreateXML"]["ver
> s"]
> + createVers = 0
> + if createVersStr != "Y":
> + createVersBits = createVersStr.split(".")
> + if len(createVersBits) != 3:
> + raise Exception("Expected 3 digit version for %s" %
> createVersStr)
> + createVers = (int(createVersBits[0]) * 1000 * 1000) +
> (int(createVersBits[1]) * 1000) + int(createVersBits[2])
> +
> + # virCreateLinux is always 0.0.3 if the driver existed
> + # before this time, otherwise it matches the version of
> + # the driver's virCreateXML entry
> + if createVersStr == "Y" or createVers >= createAPIVers:
> + vers = createVersStr
> + else:
> + vers = "0.0.3"
> +
> + groups["virHypervisorDriver"]["drivers"][drv]["domainCreateLinux
> "] = {
> + "vers": vers,
> + }
> +
> +
> +# Finally we generate the HTML file with the tables
> +
> +print('''<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE html>
> +<html xmlns="http://www.w3.org/1999/xhtml">
> +<body class="hvsupport">
> +<h1>libvirt API support matrix</h1>
> +
> +<ul id="toc"></ul>
> +
> +<p>
> +This page documents which <a href="html/">libvirt calls</a> work on
> +which libvirt drivers / hypervisors, and which version the API
> appeared
> +in. If a hypervisor driver later dropped support for the API, the
> version
> +when it was removed is also mentioned (highlighted in
> +<span class="removedhv">dark red</span>).
> +</p>
> +''')
> +
> +for grp in sorted(groups.keys()):
> + print("<h2><a id=\"%s\">%s</a></h2>" % (grp, groupheaders[grp]))
> + print('''<table class="top_table">
> +<thead>
> +<tr>
> +<th>API</th>
> +<th>Version</th>''')
> +
> + for drv in sorted(groups[grp]["drivers"].keys()):
> + print(" <th>%s</th>" % drv)
> +
> +
> + print('''</tr>
> +</thead>
> +<tbody>''')
> +
> + row = 0
> + def sortkey(field):
> + return groups[grp]["apis"][field]
> + for field in sorted(groups[grp]["apis"].keys(), key=sortkey):
> + api = groups[grp]["apis"][field]
> + vers = apis[api]["vers"]
> + htmlgrp = apis[api].get("file")
> + print("<tr>")
> +
> + if htmlgrp is not None:
> + print('''<td>\n<a href=\"html/libvirt-
> %s.html#%s\">%s</a>\n</td>''' % (htmlgrp, api, api))
> + else:
> + print("<td>\n%s</td>" % api)
> +
> + print("<td>%s</td>" % vers)
> +
> + for drv in sorted(groups[grp]["drivers"].keys()):
> + info = ""
> + if field in groups[grp]["drivers"][drv]:
> + vers = groups[grp]["drivers"][drv][field]["vers"]
> + if vers is not None:
> + info = info + vers
> +
> + deleted =
> groups[grp]["drivers"][drv][field].get("deleted")
> + if deleted is not None:
> + info = info + (''' - <span
> class="removedhv">%s</span>''' % deleted)
> +
> + print("<td>%s</td>" % info)
> +
> + print("</tr>")
> +
> + row = row + 1
> + if (row % 15) == 0:
> + print('''<tr>
> +<th>API</th>
> +<th>Version</th>''')
> +
> + for drv in sorted(groups[grp]["drivers"].keys()):
> + print(" <th>%s</th>" % drv)
> +
> + print("</tr>")
> +
> + print("</tbody>\n</table>")
> +
> +print("</body>\n</html>")
Appart from that tiny error, all is fine for me.
--
Cedric
participants (2)
-
Cedric Bosdonnat -
Daniel P. Berrangé