mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-22 13:45:38 +00:00
docs: rewrite hvsupport.html page generator in python
As part of a 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. Tested-by: Cole Robinson <crobinso@redhat.com> Reviewed-by: Cole Robinson <crobinso@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
parent
06e6efe294
commit
52a2c5b06b
@ -62,6 +62,7 @@ EXTRA_DIST = \
|
||||
scripts/gensystemtap.py \
|
||||
scripts/group-qemu-caps.py \
|
||||
scripts/header-ifdef.py \
|
||||
scripts/hvsupport.py \
|
||||
scripts/hyperv_wmi_generator.py \
|
||||
scripts/minimize-po.py \
|
||||
scripts/mock-noinline.py \
|
||||
|
@ -354,7 +354,6 @@ EXTRA_DIST= \
|
||||
$(kbase_html_in) $(kbase_rst) \
|
||||
$(manpages_rst) \
|
||||
aclperms.htmlinc \
|
||||
hvsupport.pl \
|
||||
$(schema_DATA)
|
||||
|
||||
acl_generated = aclperms.htmlinc
|
||||
@ -389,12 +388,12 @@ timestamp="$(shell if test -n "$$SOURCE_DATE_EPOCH"; \
|
||||
|
||||
hvsupport.html: hvsupport.html.in
|
||||
|
||||
hvsupport.html.in: $(srcdir)/hvsupport.pl $(api_DATA) \
|
||||
hvsupport.html.in: $(top_srcdir)/scripts/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) $(top_builddir) > $@ \
|
||||
|| { rm $@ && exit 1; }
|
||||
$(AM_V_GEN)$(RUNUTF8) $(PYTHON) $(top_srcdir)/scripts/hvsupport.py \
|
||||
$(top_srcdir) $(top_builddir) > $@ || { rm $@ && exit 1; }
|
||||
|
||||
news.html.in: \
|
||||
$(srcdir)/news.xml \
|
||||
|
@ -1,459 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Find;
|
||||
|
||||
die "syntax: $0 SRCDIR BUILDDIR\n" unless int(@ARGV) == 2;
|
||||
|
||||
my $srcdir = shift @ARGV;
|
||||
my $builddir = shift @ARGV;
|
||||
|
||||
my $symslibvirt = "$srcdir/src/libvirt_public.syms";
|
||||
my $symsqemu = "$srcdir/src/libvirt_qemu.syms";
|
||||
my $symslxc = "$srcdir/src/libvirt_lxc.syms";
|
||||
my @drivertable = (
|
||||
"$srcdir/src/driver-hypervisor.h",
|
||||
"$srcdir/src/driver-interface.h",
|
||||
"$srcdir/src/driver-network.h",
|
||||
"$srcdir/src/driver-nodedev.h",
|
||||
"$srcdir/src/driver-nwfilter.h",
|
||||
"$srcdir/src/driver-secret.h",
|
||||
"$srcdir/src/driver-state.h",
|
||||
"$srcdir/src/driver-storage.h",
|
||||
"$srcdir/src/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/src/.*/\w+_(driver|common|tmpl|monitor|hal|udev)\.c$!) {
|
||||
push @srcs, $_ if $_ !~ /vbox_driver\.c/;
|
||||
}
|
||||
}, no_chdir => 1}, "$srcdir/src");
|
||||
|
||||
# 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, "$builddir/docs/libvirt-api.xml");
|
||||
|
||||
# And the same for the QEMU specific APIs
|
||||
parseSymsFile(\%apis, "LIBVIRT_QEMU", $symsqemu, "$builddir/docs/libvirt-qemu-api.xml");
|
||||
|
||||
# And the same for the LXC specific APIs
|
||||
parseSymsFile(\%apis, "LIBVIRT_LXC", $symslxc, "$builddir/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
|
502
scripts/hvsupport.py
Executable file
502
scripts/hvsupport.py
Executable file
@ -0,0 +1,502 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (C) 2011-2019 Red Hat, Inc.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import re
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("syntax: %s TOP-SRCDIR TOP-BUILDDIR\n" % sys.argv[0], file=sys.stderr)
|
||||
|
||||
srcdir = sys.argv[1]
|
||||
builddir = sys.argv[2]
|
||||
|
||||
symslibvirt = os.path.join(srcdir, "src", "libvirt_public.syms")
|
||||
symsqemu = os.path.join(srcdir, "src", "libvirt_qemu.syms")
|
||||
symslxc = os.path.join(srcdir, "src", "libvirt_lxc.syms")
|
||||
drivertablefiles = [
|
||||
os.path.join(srcdir, "src", "driver-hypervisor.h"),
|
||||
os.path.join(srcdir, "src", "driver-interface.h"),
|
||||
os.path.join(srcdir, "src", "driver-network.h"),
|
||||
os.path.join(srcdir, "src", "driver-nodedev.h"),
|
||||
os.path.join(srcdir, "src", "driver-nwfilter.h"),
|
||||
os.path.join(srcdir, "src", "driver-secret.h"),
|
||||
os.path.join(srcdir, "src", "driver-state.h"),
|
||||
os.path.join(srcdir, "src", "driver-storage.h"),
|
||||
os.path.join(srcdir, "src", "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(os.path.join(srcdir, "src")):
|
||||
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:
|
||||
for line in fh:
|
||||
res = re.search(r"function name='([^']+)' file='([^']+)'", 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:
|
||||
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 = re.search(r"^\s*%s_(\d+\.\d+\.\d+)\s*{\s*$" %
|
||||
prefix, line)
|
||||
groupendmatch1 = re.search(r"^\s*}\s*;\s*$", line)
|
||||
groupendmatch2 = re.search(r"^\s*}\s*%s_(\d+\.\d+\.\d+)\s*;\s*$" %
|
||||
prefix, line)
|
||||
symbolmatch = re.search(r"^\s*(\w+)\s*;\s*$", 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(builddir, "docs", "libvirt-api.xml"))
|
||||
|
||||
# And the same for the QEMU specific APIs
|
||||
parseSymsFile(apis, "LIBVIRT_QEMU", symsqemu,
|
||||
os.path.join(builddir, "docs", "libvirt-qemu-api.xml"))
|
||||
|
||||
# And the same for the LXC specific APIs
|
||||
parseSymsFile(apis, "LIBVIRT_LXC", symslxc,
|
||||
os.path.join(builddir, "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:
|
||||
for line in fh:
|
||||
starttablematch = re.search(r"struct _(vir\w*Driver)", line)
|
||||
if starttablematch is not None:
|
||||
grp = starttablematch.group(1)
|
||||
if grp != "virStateDriver" and grp != "virStreamDriver":
|
||||
ingrp = grp
|
||||
groups[ingrp] = {
|
||||
"apis": {},
|
||||
"drivers": {}
|
||||
}
|
||||
elif ingrp is not None:
|
||||
callbackmatch = re.search(r"^\s*vir(?:Drv)(\w+)\s+(\w+);\s*$",
|
||||
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 re.search(r"\w+(Open|Close|URIProbe)", api) is not None:
|
||||
continue
|
||||
else:
|
||||
raise Exception(("driver %s does not have " +
|
||||
"a public API") % name)
|
||||
elif re.search(r"};", 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())
|
||||
|
||||
ingrp = None
|
||||
impl = None
|
||||
for line in fh:
|
||||
if ingrp is None:
|
||||
m = re.search(r"^\s*(static\s+)?(" +
|
||||
groupsre +
|
||||
r")\s+(\w+)\s*=\s*{", line)
|
||||
if m is None:
|
||||
m = re.search(r"^\s*(static\s+)?(" +
|
||||
groupsre +
|
||||
r")\s+NAME\(\w+\)\s*=\s*{", line)
|
||||
if m is not None:
|
||||
ingrp = m.group(2)
|
||||
impl = src
|
||||
|
||||
implmatch = re.search(r".*/node_device_(\w+)\.c", impl)
|
||||
if implmatch is None:
|
||||
implmatch = re.search(r".*/(\w+?)_((\w+)_)?(\w+)\.c", 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 = re.search(r"\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*" +
|
||||
r"(?:/\*\s*(\d+\.\d+\.\d+)\s*" +
|
||||
r"(?:-\s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$",
|
||||
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 re.search(r"\w+(Open|Close|URIProbe)", 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
|
||||
|
||||
drivers = groups["virHypervisorDriver"]["drivers"]
|
||||
for drv in drivers.keys():
|
||||
openVersStr = 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
|
||||
drivers[drv]["connectOpenReadOnly"] = \
|
||||
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"
|
||||
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 drivers.keys():
|
||||
if "domainCreateXML" not in drivers[drv]:
|
||||
continue
|
||||
createVersStr = 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"
|
||||
|
||||
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>")
|
Loading…
Reference in New Issue
Block a user