hvsupport.pl 13.8 KB
Newer Older
1
#!/usr/bin/env perl
2 3 4 5 6 7

use strict;
use warnings;

use File::Find;

8
die "syntax: $0 SRCDIR BUILDDIR\n" unless int(@ARGV) == 2;
9 10

my $srcdir = shift @ARGV;
11
my $builddir = shift @ARGV;
12

13 14 15
my $symslibvirt = "$srcdir/src/libvirt_public.syms";
my $symsqemu = "$srcdir/src/libvirt_qemu.syms";
my $symslxc = "$srcdir/src/libvirt_lxc.syms";
16
my @drivertable = (
17 18 19 20 21 22 23 24 25
    "$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",
26
    );
27 28

my %groupheaders = (
29
    "virHypervisorDriver" => "Hypervisor APIs",
30 31
    "virNetworkDriver" => "Virtual Network APIs",
    "virInterfaceDriver" => "Host Interface APIs",
32
    "virNodeDeviceDriver" => "Host Device APIs",
33 34 35 36 37 38 39 40 41
    "virStorageDriver" => "Storage Pool APIs",
    "virSecretDriver" => "Secret APIs",
    "virNWFilterDriver" => "Network Filter APIs",
    );


my @srcs;
find({
    wanted => sub {
42
        if (m!$srcdir/src/.*/\w+_(driver|common|tmpl|monitor|hal|udev)\.c$!) {
43 44
            push @srcs, $_ if $_ !~ /vbox_driver\.c/;
        }
45
    }, no_chdir => 1}, "$srcdir/src");
46

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
# 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;
}

J
Ján Tomko 已提交
73 74 75 76 77
sub parseSymsFile {
    my $apisref = shift;
    my $prefix = shift;
    my $filename = shift;
    my $xmlfilename = shift;
78

J
Ján Tomko 已提交
79 80 81
    my $line;
    my $vers;
    my $prevvers;
82

83
    my $filenames = getAPIFilenames($xmlfilename);
84

J
Ján Tomko 已提交
85 86
    open FILE, "<$filename"
        or die "cannot read $filename: $!";
87

J
Ján Tomko 已提交
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    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;
113
            $$apisref{$1}->{file} = $$filenames{$1};
J
Ján Tomko 已提交
114 115
        } else {
            die "unexpected data $line\n";
116
        }
117
    }
J
Ján Tomko 已提交
118 119

    close FILE;
120 121
}

J
Ján Tomko 已提交
122 123
my %apis;
# Get the list of all public APIs and their corresponding version
124
parseSymsFile(\%apis, "LIBVIRT", $symslibvirt, "$builddir/docs/libvirt-api.xml");
125

J
Ján Tomko 已提交
126
# And the same for the QEMU specific APIs
127
parseSymsFile(\%apis, "LIBVIRT_QEMU", $symsqemu, "$builddir/docs/libvirt-qemu-api.xml");
128 129

# And the same for the LXC specific APIs
130
parseSymsFile(\%apis, "LIBVIRT_LXC", $symslxc, "$builddir/docs/libvirt-lxc-api.xml");
131 132 133 134


# Some special things which aren't public APIs,
# but we want to report
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
$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";
156

157 158 159 160 161 162


# 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.

J
Ján Tomko 已提交
163 164
my $line;

165 166 167
# Group name -> hash of APIs { fields -> api name }
my %groups;
my $ingrp;
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
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";
188
                } elsif ($name =~ /\w+(Open|Close|URIProbe)/) {
189 190 191 192 193 194 195
                    next;
                } else {
                    die "driver $name does not have a public API";
                }
                $groups{$ingrp}->{apis}->{$field} = $api;
            } elsif ($line =~ /};/) {
                $ingrp = undef;
196 197
            }
        }
198 199
    }

200 201
    close FILE;
}
202 203 204 205 206 207 208


# 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
209
        die "cannot read $src: $!";
210

211
    my $groups_regex = join("|", keys %groups);
212 213 214
    $ingrp = undef;
    my $impl;
    while (defined($line = <FILE>)) {
215
        if (!$ingrp) {
216 217 218
            # skip non-matching lines early to save time
            next if not $line =~ /$groups_regex/;

219 220 221 222 223 224 225 226 227
            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,;
228
                }
229 230 231 232 233 234

                if ($groups{$ingrp}->{drivers}->{$impl}) {
                    die "Group $ingrp already contains $impl";
                }

                $groups{$ingrp}->{drivers}->{$impl} = {};
235 236 237
            }

        } else {
238
            if ($line =~ m!\s*\.(\w+)\s*=\s*(\w+)\s*,?\s*(?:/\*\s*(\d+\.\d+\.\d+)\s*(?:-\s*(\d+\.\d+\.\d+))?\s*\*/\s*)?$!) {
239 240 241
                my $api = $1;
                my $meth = $2;
                my $vers = $3;
242
                my $deleted = $4;
243 244 245

                next if $api eq "no" || $api eq "name";

246 247 248 249 250 251
                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";
                }
252

253
                die "Method $meth in $src is missing version" unless defined $vers || $api eq "connectURIProbe";
254 255

                if (!exists($groups{$ingrp}->{apis}->{$api})) {
256
                    next if $api =~ /\w(Open|Close|URIProbe)/;
257

258 259 260
                    die "Found unexpected method $api in $ingrp\n";
                }

261 262
                $groups{$ingrp}->{drivers}->{$impl}->{$api} = {};
                $groups{$ingrp}->{drivers}->{$impl}->{$api}->{vers} = $vers;
263
                $groups{$ingrp}->{drivers}->{$impl}->{$api}->{deleted}  = $deleted;
264 265 266
                if ($api eq "domainMigratePrepare" ||
                    $api eq "domainMigratePrepare2" ||
                    $api eq "domainMigratePrepare3") {
267 268 269 270
                    if (!$groups{$ingrp}->{drivers}->{$impl}->{"domainMigrate"}) {
                        $groups{$ingrp}->{drivers}->{$impl}->{"domainMigrate"} = {};
                        $groups{$ingrp}->{drivers}->{$impl}->{"domainMigrate"}->{vers} = $vers;
                    }
271 272 273 274 275 276
                }

            } elsif ($line =~ /}/) {
                $ingrp = undef;
            }
        }
277 278 279 280 281 282 283 284 285 286
    }

    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

287 288 289
$groups{virHypervisorDriver}->{apis}->{"openAuth"} = "virConnectOpenAuth";
$groups{virHypervisorDriver}->{apis}->{"openReadOnly"} = "virConnectOpenReadOnly";
$groups{virHypervisorDriver}->{apis}->{"domainMigrate"} = "virDomainMigrate";
290 291 292

my $openAuthVers = (0 * 1000 * 1000) + (4 * 1000) + 0;

293
foreach my $drv (keys %{$groups{"virHypervisorDriver"}->{drivers}}) {
294
    my $openVersStr = $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpen"}->{vers};
295 296
    my $openVers;
    if ($openVersStr =~ /(\d+)\.(\d+)\.(\d+)/) {
297
        $openVers = ($1 * 1000 * 1000) + ($2 * 1000) + $3;
298 299 300
    }

    # virConnectOpenReadOnly always matches virConnectOpen version
301 302
    $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenReadOnly"} =
        $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpen"};
303

304 305
    $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenAuth"} = {};

306 307 308 309
    # 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" ||
310
        $openVers >= $openAuthVers) {
311
        $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenAuth"}->{vers} = $openVersStr;
312
    } else {
313
        $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"connectOpenAuth"}->{vers} = "0.4.0";
314 315 316 317 318 319
    }
}


# Another special case for the virDomainCreateLinux which was replaced
# with virDomainCreateXML
320
$groups{virHypervisorDriver}->{apis}->{"domainCreateLinux"} = "virDomainCreateLinux";
321 322 323

my $createAPIVers = (0 * 1000 * 1000) + (0 * 1000) + 3;

324
foreach my $drv (keys %{$groups{"virHypervisorDriver"}->{drivers}}) {
325
    my $createVersStr = $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateXML"}->{vers};
326 327 328
    next unless defined $createVersStr;
    my $createVers;
    if ($createVersStr =~ /(\d+)\.(\d+)\.(\d+)/) {
329
        $createVers = ($1 * 1000 * 1000) + ($2 * 1000) + $3;
330 331
    }

332 333
    $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateLinux"} = {};

334 335 336 337
    # 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" ||
338
        $createVers >= $createAPIVers) {
339
        $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateLinux"}->{vers} = $createVersStr;
340
    } else {
341
        $groups{"virHypervisorDriver"}->{drivers}->{$drv}->{"domainCreateLinux"}->{vers} = "0.0.3";
342 343 344 345 346 347 348
    }
}


# Finally we generate the HTML file with the tables

print <<EOF;
349
<?xml version="1.0" encoding="UTF-8"?>
350
<!DOCTYPE html>
351
<html xmlns="http://www.w3.org/1999/xhtml">
352
<body class="hvsupport">
353 354 355 356 357 358 359
<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
360 361 362
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>).
363 364 365 366
</p>

EOF

367
    foreach my $grp (sort { $a cmp $b } keys %groups) {
368
    print "<h2><a id=\"$grp\">", $groupheaders{$grp}, "</a></h2>\n";
369 370 371 372 373 374 375 376 377
    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}}) {
378
        print "  <th>$drv</th>\n";
379 380 381 382 383 384 385 386 387 388
    }

    print <<EOF;
</tr>
</thead>
<tbody>
EOF

    my $row = 0;
    foreach my $field (sort {
389 390 391 392 393
        $groups{$grp}->{apis}->{$a}
        cmp
        $groups{$grp}->{apis}->{$b}
        } keys %{$groups{$grp}->{apis}}) {
        my $api = $groups{$grp}->{apis}->{$field};
394 395
        my $vers = $apis{$api}->{vers};
        my $htmlgrp = $apis{$api}->{file};
396
        print <<EOF;
397
<tr>
398 399 400 401 402 403 404 405 406 407 408 409 410
<td>
EOF

        if (defined $htmlgrp) {
            print <<EOF;
<a href=\"html/libvirt-$htmlgrp.html#$api\">$api</a>
EOF

        } else {
            print $api;
        }
        print <<EOF;
</td>
411 412 413 414
<td>$vers</td>
EOF

        foreach my $drv (sort {$a cmp $b } keys %{$groups{$grp}->{drivers}}) {
415
            print "<td>";
416
            if (exists $groups{$grp}->{drivers}->{$drv}->{$field}) {
417 418 419
                if ($groups{$grp}->{drivers}->{$drv}->{$field}->{vers}) {
                    print $groups{$grp}->{drivers}->{$drv}->{$field}->{vers};
                }
420 421
                if ($groups{$grp}->{drivers}->{$drv}->{$field}->{deleted}) {
                    print " - <span class=\"removedhv\">", $groups{$grp}->{drivers}->{$drv}->{$field}->{deleted}, "</span>";
422
                }
423
            }
424
            print "</td>\n";
425 426
        }

427
        print <<EOF;
428 429 430 431 432 433 434 435 436 437 438 439
</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}}) {
440
                print "  <th>$drv</th>\n";
441 442 443 444 445
            }

        print <<EOF;
</tr>
EOF
446
        }
447 448 449 450 451 452 453 454 455 456 457 458 459

    }

    print <<EOF;
</tbody>
</table>
EOF
}

print <<EOF;
</body>
</html>
EOF