Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'mauro' into docs-mw

Mauro says:

That's the final series to complete the migration of documentation
build: it converts get_feat from Perl to Python.

V2 is technically identical to v1: the only difference is that it
now uses tools/lib/python/feat to store the library logic.

With that, no Sphinx in-kernel extensions use fork anymore to call
ancillary scripts: everything is now importing Python methods
directly from the libraries.

There's nothing special on this conversion: it is a direct translation,
almost bug-compatible with the original version (*).

(*) I did solve two or three caveats on patch 1.

Most of the complexity of the script relies at the logic to produce
ReST tables. I do have here on my internal scripts a (somewhat) generic
formatter for ReST tables in Python. I was tempted to convert the logic
to use it, but, as this could cause regressions, I opted to not do it
right now, mainly because the matrix table logic is complex. Also,
I'm tempted to modify a little bit the output there, but extra tests
are required to see if PDF output would work with complex tables (I
remember I had a problem with that in the past). So, I'm postponing
such extra cleanup.

+732 -652
+13 -11
Documentation/sphinx/kernel_feat.py
··· 34 34 import codecs 35 35 import os 36 36 import re 37 - import subprocess 38 37 import sys 39 38 40 39 from docutils import nodes, statemachine 41 40 from docutils.statemachine import ViewList 42 41 from docutils.parsers.rst import directives, Directive 43 42 from sphinx.util.docutils import switch_source_input 43 + 44 + srctree = os.path.abspath(os.environ["srctree"]) 45 + sys.path.insert(0, os.path.join(srctree, "tools/lib/python")) 46 + 47 + from feat.parse_features import ParseFeature # pylint: disable=C0413 44 48 45 49 def ErrorString(exc): # Shamelessly stolen from docutils 46 50 return f'{exc.__class__.__name}: {exc}' ··· 88 84 89 85 srctree = os.path.abspath(os.environ["srctree"]) 90 86 91 - args = [ 92 - os.path.join(srctree, 'tools/docs/get_feat.pl'), 93 - 'rest', 94 - '--enable-fname', 95 - '--dir', 96 - os.path.join(srctree, 'Documentation', self.arguments[0]), 97 - ] 87 + feature_dir = os.path.join(srctree, 'Documentation', self.arguments[0]) 88 + 89 + feat = ParseFeature(feature_dir, False, True) 90 + feat.parse() 98 91 99 92 if len(self.arguments) > 1: 100 - args.extend(['--arch', self.arguments[1]]) 101 - 102 - lines = subprocess.check_output(args, cwd=os.path.dirname(doc.current_source)).decode('utf-8') 93 + arch = self.arguments[1] 94 + lines = feat.output_arch_table(arch) 95 + else: 96 + lines = feat.output_matrix() 103 97 104 98 line_regex = re.compile(r"^\.\. FILE (\S+)$") 105 99
-641
tools/docs/get_feat.pl
··· 1 - #!/usr/bin/env perl 2 - # SPDX-License-Identifier: GPL-2.0 3 - 4 - use strict; 5 - use Pod::Usage; 6 - use Getopt::Long; 7 - use File::Find; 8 - use Fcntl ':mode'; 9 - use Cwd 'abs_path'; 10 - 11 - my $help; 12 - my $man; 13 - my $debug; 14 - my $arch; 15 - my $feat; 16 - my $enable_fname; 17 - 18 - my $basename = abs_path($0); 19 - $basename =~ s,/[^/]+$,/,; 20 - 21 - my $prefix=$basename . "../../Documentation/features"; 22 - 23 - # Used only at for full features output. The script will auto-adjust 24 - # such values for the minimal possible values 25 - my $status_size = 1; 26 - my $description_size = 1; 27 - 28 - GetOptions( 29 - "debug|d+" => \$debug, 30 - "dir=s" => \$prefix, 31 - 'help|?' => \$help, 32 - 'arch=s' => \$arch, 33 - 'feat=s' => \$feat, 34 - 'feature=s' => \$feat, 35 - "enable-fname" => \$enable_fname, 36 - man => \$man 37 - ) or pod2usage(2); 38 - 39 - pod2usage(1) if $help; 40 - pod2usage(-exitstatus => 0, -verbose => 2) if $man; 41 - 42 - pod2usage(1) if (scalar @ARGV < 1 || @ARGV > 2); 43 - 44 - my ($cmd, $arg) = @ARGV; 45 - 46 - pod2usage(2) if ($cmd ne "current" && $cmd ne "rest" && $cmd ne "validate" 47 - && $cmd ne "ls" && $cmd ne "list"); 48 - 49 - require Data::Dumper if ($debug); 50 - 51 - my %data; 52 - my %archs; 53 - 54 - # 55 - # Displays an error message, printing file name and line 56 - # 57 - sub parse_error($$$$) { 58 - my ($file, $ln, $msg, $data) = @_; 59 - 60 - $data =~ s/\s+$/\n/; 61 - 62 - print STDERR "Warning: file $file#$ln:\n\t$msg"; 63 - 64 - if ($data ne "") { 65 - print STDERR ". Line\n\t\t$data"; 66 - } else { 67 - print STDERR "\n"; 68 - } 69 - } 70 - 71 - # 72 - # Parse a features file, storing its contents at %data 73 - # 74 - 75 - my $h_name = "Feature"; 76 - my $h_kconfig = "Kconfig"; 77 - my $h_description = "Description"; 78 - my $h_subsys = "Subsystem"; 79 - my $h_status = "Status"; 80 - my $h_arch = "Architecture"; 81 - 82 - my $max_size_name = length($h_name); 83 - my $max_size_kconfig = length($h_kconfig); 84 - my $max_size_description = length($h_description); 85 - my $max_size_subsys = length($h_subsys); 86 - my $max_size_status = length($h_status); 87 - 88 - my $max_size_arch = 0; 89 - my $max_size_arch_with_header; 90 - my $max_description_word = 0; 91 - 92 - sub parse_feat { 93 - my $file = $File::Find::name; 94 - 95 - my $mode = (stat($file))[2]; 96 - return if ($mode & S_IFDIR); 97 - return if ($file =~ m,($prefix)/arch-support.txt,); 98 - return if (!($file =~ m,arch-support.txt$,)); 99 - 100 - if ($enable_fname) { 101 - printf ".. FILE %s\n", abs_path($file); 102 - } 103 - 104 - my $subsys = ""; 105 - $subsys = $2 if ( m,.*($prefix)/([^/]+).*,); 106 - 107 - if (length($subsys) > $max_size_subsys) { 108 - $max_size_subsys = length($subsys); 109 - } 110 - 111 - my $name; 112 - my $kconfig; 113 - my $description; 114 - my $comments = ""; 115 - my $last_status; 116 - my $ln; 117 - my %arch_table; 118 - 119 - print STDERR "Opening $file\n" if ($debug > 1); 120 - open IN, $file; 121 - 122 - while(<IN>) { 123 - $ln++; 124 - 125 - if (m/^\#\s+Feature\s+name:\s*(.*\S)/) { 126 - $name = $1; 127 - if (length($name) > $max_size_name) { 128 - $max_size_name = length($name); 129 - } 130 - next; 131 - } 132 - if (m/^\#\s+Kconfig:\s*(.*\S)/) { 133 - $kconfig = $1; 134 - if (length($kconfig) > $max_size_kconfig) { 135 - $max_size_kconfig = length($kconfig); 136 - } 137 - next; 138 - } 139 - if (m/^\#\s+description:\s*(.*\S)/) { 140 - $description = $1; 141 - if (length($description) > $max_size_description) { 142 - $max_size_description = length($description); 143 - } 144 - 145 - foreach my $word (split /\s+/, $description) { 146 - if (length($word) > $max_description_word) { 147 - $max_description_word = length($word); 148 - } 149 - } 150 - 151 - next; 152 - } 153 - next if (m/^\\s*$/); 154 - next if (m/^\s*\-+\s*$/); 155 - next if (m/^\s*\|\s*arch\s*\|\s*status\s*\|\s*$/); 156 - 157 - if (m/^\#\s*(.*)/) { 158 - $comments .= "$1\n"; 159 - next; 160 - } 161 - if (m/^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$/) { 162 - my $a = $1; 163 - my $status = $2; 164 - 165 - if (length($status) > $max_size_status) { 166 - $max_size_status = length($status); 167 - } 168 - if (length($a) > $max_size_arch) { 169 - $max_size_arch = length($a); 170 - } 171 - 172 - $status = "---" if ($status =~ m/^\.\.$/); 173 - 174 - $archs{$a} = 1; 175 - $arch_table{$a} = $status; 176 - next; 177 - } 178 - 179 - #Everything else is an error 180 - parse_error($file, $ln, "line is invalid", $_); 181 - } 182 - close IN; 183 - 184 - if (!$name) { 185 - parse_error($file, $ln, "Feature name not found", ""); 186 - return; 187 - } 188 - 189 - parse_error($file, $ln, "Subsystem not found", "") if (!$subsys); 190 - parse_error($file, $ln, "Kconfig not found", "") if (!$kconfig); 191 - parse_error($file, $ln, "Description not found", "") if (!$description); 192 - 193 - if (!%arch_table) { 194 - parse_error($file, $ln, "Architecture table not found", ""); 195 - return; 196 - } 197 - 198 - $data{$name}->{where} = $file; 199 - $data{$name}->{subsys} = $subsys; 200 - $data{$name}->{kconfig} = $kconfig; 201 - $data{$name}->{description} = $description; 202 - $data{$name}->{comments} = $comments; 203 - $data{$name}->{table} = \%arch_table; 204 - 205 - $max_size_arch_with_header = $max_size_arch + length($h_arch); 206 - } 207 - 208 - # 209 - # Output feature(s) for a given architecture 210 - # 211 - sub output_arch_table { 212 - my $title = "Feature status on $arch architecture"; 213 - 214 - print "=" x length($title) . "\n"; 215 - print "$title\n"; 216 - print "=" x length($title) . "\n\n"; 217 - 218 - print "=" x $max_size_subsys; 219 - print " "; 220 - print "=" x $max_size_name; 221 - print " "; 222 - print "=" x $max_size_kconfig; 223 - print " "; 224 - print "=" x $max_size_status; 225 - print " "; 226 - print "=" x $max_size_description; 227 - print "\n"; 228 - printf "%-${max_size_subsys}s ", $h_subsys; 229 - printf "%-${max_size_name}s ", $h_name; 230 - printf "%-${max_size_kconfig}s ", $h_kconfig; 231 - printf "%-${max_size_status}s ", $h_status; 232 - printf "%-${max_size_description}s\n", $h_description; 233 - print "=" x $max_size_subsys; 234 - print " "; 235 - print "=" x $max_size_name; 236 - print " "; 237 - print "=" x $max_size_kconfig; 238 - print " "; 239 - print "=" x $max_size_status; 240 - print " "; 241 - print "=" x $max_size_description; 242 - print "\n"; 243 - 244 - foreach my $name (sort { 245 - ($data{$a}->{subsys} cmp $data{$b}->{subsys}) || 246 - ("\L$a" cmp "\L$b") 247 - } keys %data) { 248 - next if ($feat && $name ne $feat); 249 - 250 - my %arch_table = %{$data{$name}->{table}}; 251 - printf "%-${max_size_subsys}s ", $data{$name}->{subsys}; 252 - printf "%-${max_size_name}s ", $name; 253 - printf "%-${max_size_kconfig}s ", $data{$name}->{kconfig}; 254 - printf "%-${max_size_status}s ", $arch_table{$arch}; 255 - printf "%-s\n", $data{$name}->{description}; 256 - } 257 - 258 - print "=" x $max_size_subsys; 259 - print " "; 260 - print "=" x $max_size_name; 261 - print " "; 262 - print "=" x $max_size_kconfig; 263 - print " "; 264 - print "=" x $max_size_status; 265 - print " "; 266 - print "=" x $max_size_description; 267 - print "\n"; 268 - } 269 - 270 - # 271 - # list feature(s) for a given architecture 272 - # 273 - sub list_arch_features { 274 - print "#\n# Kernel feature support matrix of the '$arch' architecture:\n#\n"; 275 - 276 - foreach my $name (sort { 277 - ($data{$a}->{subsys} cmp $data{$b}->{subsys}) || 278 - ("\L$a" cmp "\L$b") 279 - } keys %data) { 280 - next if ($feat && $name ne $feat); 281 - 282 - my %arch_table = %{$data{$name}->{table}}; 283 - 284 - my $status = $arch_table{$arch}; 285 - $status = " " x ((4 - length($status)) / 2) . $status; 286 - 287 - printf " %${max_size_subsys}s/ ", $data{$name}->{subsys}; 288 - printf "%-${max_size_name}s: ", $name; 289 - printf "%-5s| ", $status; 290 - printf "%${max_size_kconfig}s # ", $data{$name}->{kconfig}; 291 - printf " %s\n", $data{$name}->{description}; 292 - } 293 - } 294 - 295 - # 296 - # Output a feature on all architectures 297 - # 298 - sub output_feature { 299 - my $title = "Feature $feat"; 300 - 301 - print "=" x length($title) . "\n"; 302 - print "$title\n"; 303 - print "=" x length($title) . "\n\n"; 304 - 305 - print ":Subsystem: $data{$feat}->{subsys} \n" if ($data{$feat}->{subsys}); 306 - print ":Kconfig: $data{$feat}->{kconfig} \n" if ($data{$feat}->{kconfig}); 307 - 308 - my $desc = $data{$feat}->{description}; 309 - $desc =~ s/^([a-z])/\U$1/; 310 - $desc =~ s/\.?\s*//; 311 - print "\n$desc.\n\n"; 312 - 313 - my $com = $data{$feat}->{comments}; 314 - $com =~ s/^\s+//; 315 - $com =~ s/\s+$//; 316 - if ($com) { 317 - print "Comments\n"; 318 - print "--------\n\n"; 319 - print "$com\n\n"; 320 - } 321 - 322 - print "=" x $max_size_arch_with_header; 323 - print " "; 324 - print "=" x $max_size_status; 325 - print "\n"; 326 - 327 - printf "%-${max_size_arch}s ", $h_arch; 328 - printf "%-${max_size_status}s", $h_status . "\n"; 329 - 330 - print "=" x $max_size_arch_with_header; 331 - print " "; 332 - print "=" x $max_size_status; 333 - print "\n"; 334 - 335 - my %arch_table = %{$data{$feat}->{table}}; 336 - foreach my $arch (sort keys %arch_table) { 337 - printf "%-${max_size_arch}s ", $arch; 338 - printf "%-${max_size_status}s\n", $arch_table{$arch}; 339 - } 340 - 341 - print "=" x $max_size_arch_with_header; 342 - print " "; 343 - print "=" x $max_size_status; 344 - print "\n"; 345 - } 346 - 347 - # 348 - # Output all features for all architectures 349 - # 350 - 351 - sub matrix_lines($$$) { 352 - my $desc_size = shift; 353 - my $status_size = shift; 354 - my $header = shift; 355 - my $fill; 356 - my $ln_marker; 357 - 358 - if ($header) { 359 - $ln_marker = "="; 360 - } else { 361 - $ln_marker = "-"; 362 - } 363 - 364 - $fill = $ln_marker; 365 - 366 - print "+"; 367 - print $fill x $max_size_name; 368 - print "+"; 369 - print $fill x $desc_size; 370 - print "+"; 371 - print $ln_marker x $status_size; 372 - print "+\n"; 373 - } 374 - 375 - sub output_matrix { 376 - my $title = "Feature status on all architectures"; 377 - my $notcompat = "Not compatible"; 378 - 379 - print "=" x length($title) . "\n"; 380 - print "$title\n"; 381 - print "=" x length($title) . "\n\n"; 382 - 383 - my $desc_title = "$h_kconfig / $h_description"; 384 - 385 - my $desc_size = $max_size_kconfig + 4; 386 - if (!$description_size) { 387 - $desc_size = $max_size_description if ($max_size_description > $desc_size); 388 - } else { 389 - $desc_size = $description_size if ($description_size > $desc_size); 390 - } 391 - $desc_size = $max_description_word if ($max_description_word > $desc_size); 392 - 393 - $desc_size = length($desc_title) if (length($desc_title) > $desc_size); 394 - 395 - $max_size_status = length($notcompat) if (length($notcompat) > $max_size_status); 396 - 397 - # Ensure that the status will fit 398 - my $min_status_size = $max_size_status + $max_size_arch + 6; 399 - $status_size = $min_status_size if ($status_size < $min_status_size); 400 - 401 - 402 - my $cur_subsys = ""; 403 - foreach my $name (sort { 404 - ($data{$a}->{subsys} cmp $data{$b}->{subsys}) or 405 - ("\L$a" cmp "\L$b") 406 - } keys %data) { 407 - 408 - if ($cur_subsys ne $data{$name}->{subsys}) { 409 - if ($cur_subsys ne "") { 410 - printf "\n"; 411 - } 412 - 413 - $cur_subsys = $data{$name}->{subsys}; 414 - 415 - my $title = "Subsystem: $cur_subsys"; 416 - print "$title\n"; 417 - print "=" x length($title) . "\n\n"; 418 - 419 - 420 - matrix_lines($desc_size, $status_size, 0); 421 - 422 - printf "|%-${max_size_name}s", $h_name; 423 - printf "|%-${desc_size}s", $desc_title; 424 - 425 - printf "|%-${status_size}s|\n", "Status per architecture"; 426 - matrix_lines($desc_size, $status_size, 1); 427 - } 428 - 429 - my %arch_table = %{$data{$name}->{table}}; 430 - my $cur_status = ""; 431 - 432 - my (@lines, @descs); 433 - my $line = ""; 434 - foreach my $arch (sort { 435 - ($arch_table{$b} cmp $arch_table{$a}) or 436 - ("\L$a" cmp "\L$b") 437 - } keys %arch_table) { 438 - 439 - my $status = $arch_table{$arch}; 440 - 441 - if ($status eq "---") { 442 - $status = $notcompat; 443 - } 444 - 445 - if ($status ne $cur_status) { 446 - if ($line ne "") { 447 - push @lines, $line; 448 - $line = ""; 449 - } 450 - $line = "- **" . $status . "**: " . $arch; 451 - } elsif (length($line) + length ($arch) + 2 < $status_size) { 452 - $line .= ", " . $arch; 453 - } else { 454 - push @lines, $line; 455 - $line = " " . $arch; 456 - } 457 - $cur_status = $status; 458 - } 459 - push @lines, $line if ($line ne ""); 460 - 461 - my $description = $data{$name}->{description}; 462 - while (length($description) > $desc_size) { 463 - my $d = substr $description, 0, $desc_size; 464 - 465 - # Ensure that it will end on a space 466 - # if it can't, it means that the size is too small 467 - # Instead of aborting it, let's print what we have 468 - if (!($d =~ s/^(.*)\s+.*/$1/)) { 469 - $d = substr $d, 0, -1; 470 - push @descs, "$d\\"; 471 - $description =~ s/^\Q$d\E//; 472 - } else { 473 - push @descs, $d; 474 - $description =~ s/^\Q$d\E\s+//; 475 - } 476 - } 477 - push @descs, $description; 478 - 479 - # Ensure that the full description will be printed 480 - push @lines, "" while (scalar(@lines) < 2 + scalar(@descs)); 481 - 482 - my $ln = 0; 483 - for my $line(@lines) { 484 - if (!$ln) { 485 - printf "|%-${max_size_name}s", $name; 486 - printf "|%-${desc_size}s", "``" . $data{$name}->{kconfig} . "``"; 487 - } elsif ($ln >= 2 && scalar(@descs)) { 488 - printf "|%-${max_size_name}s", ""; 489 - printf "|%-${desc_size}s", shift @descs; 490 - } else { 491 - printf "|%-${max_size_name}s", ""; 492 - printf "|%-${desc_size}s", ""; 493 - } 494 - 495 - printf "|%-${status_size}s|\n", $line; 496 - 497 - $ln++; 498 - } 499 - matrix_lines($desc_size, $status_size, 0); 500 - } 501 - } 502 - 503 - 504 - # 505 - # Parses all feature files located at $prefix dir 506 - # 507 - find({wanted =>\&parse_feat, no_chdir => 1}, $prefix); 508 - 509 - print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); 510 - 511 - # 512 - # Handles the command 513 - # 514 - if ($cmd eq "current") { 515 - $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/'); 516 - $arch =~s/\s+$//; 517 - } 518 - 519 - if ($cmd eq "ls" or $cmd eq "list") { 520 - if (!$arch) { 521 - $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/'); 522 - $arch =~s/\s+$//; 523 - } 524 - 525 - list_arch_features; 526 - 527 - exit; 528 - } 529 - 530 - if ($cmd ne "validate") { 531 - if ($arch) { 532 - output_arch_table; 533 - } elsif ($feat) { 534 - output_feature; 535 - } else { 536 - output_matrix; 537 - } 538 - } 539 - 540 - __END__ 541 - 542 - =head1 NAME 543 - 544 - get_feat.pl - parse the Linux Feature files and produce a ReST book. 545 - 546 - =head1 SYNOPSIS 547 - 548 - B<get_feat.pl> [--debug] [--man] [--help] [--dir=<dir>] [--arch=<arch>] 549 - [--feature=<feature>|--feat=<feature>] <COMAND> [<ARGUMENT>] 550 - 551 - Where <COMMAND> can be: 552 - 553 - =over 8 554 - 555 - B<current> - output table in ReST compatible ASCII format 556 - with features for this machine's architecture 557 - 558 - B<rest> - output table(s) in ReST compatible ASCII format 559 - with features in ReST markup language. The output 560 - is affected by --arch or --feat/--feature flags. 561 - 562 - B<validate> - validate the contents of the files under 563 - Documentation/features. 564 - 565 - B<ls> or B<list> - list features for this machine's architecture, 566 - using an easier to parse format. 567 - The output is affected by --arch flag. 568 - 569 - =back 570 - 571 - =head1 OPTIONS 572 - 573 - =over 8 574 - 575 - =item B<--arch> 576 - 577 - Output features for an specific architecture, optionally filtering for 578 - a single specific feature. 579 - 580 - =item B<--feat> or B<--feature> 581 - 582 - Output features for a single specific feature. 583 - 584 - =item B<--dir> 585 - 586 - Changes the location of the Feature files. By default, it uses 587 - the Documentation/features directory. 588 - 589 - =item B<--enable-fname> 590 - 591 - Prints the file name of the feature files. This can be used in order to 592 - track dependencies during documentation build. 593 - 594 - =item B<--debug> 595 - 596 - Put the script in verbose mode, useful for debugging. Can be called multiple 597 - times, to increase verbosity. 598 - 599 - =item B<--help> 600 - 601 - Prints a brief help message and exits. 602 - 603 - =item B<--man> 604 - 605 - Prints the manual page and exits. 606 - 607 - =back 608 - 609 - =head1 DESCRIPTION 610 - 611 - Parse the Linux feature files from Documentation/features (by default), 612 - optionally producing results at ReST format. 613 - 614 - It supports output data per architecture, per feature or a 615 - feature x arch matrix. 616 - 617 - When used with B<rest> command, it will use either one of the tree formats: 618 - 619 - If neither B<--arch> or B<--feature> arguments are used, it will output a 620 - matrix with features per architecture. 621 - 622 - If B<--arch> argument is used, it will output the features availability for 623 - a given architecture. 624 - 625 - If B<--feat> argument is used, it will output the content of the feature 626 - file using ReStructured Text markup. 627 - 628 - =head1 BUGS 629 - 630 - Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org> 631 - 632 - =head1 COPYRIGHT 633 - 634 - Copyright (c) 2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>. 635 - 636 - License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. 637 - 638 - This is free software: you are free to change and redistribute it. 639 - There is NO WARRANTY, to the extent permitted by law. 640 - 641 - =cut
+225
tools/docs/get_feat.py
··· 1 + #!/usr/bin/env python3 2 + # pylint: disable=R0902,R0911,R0912,R0914,R0915 3 + # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. 4 + # SPDX-License-Identifier: GPL-2.0 5 + 6 + 7 + """ 8 + Parse the Linux Feature files and produce a ReST book. 9 + """ 10 + 11 + import argparse 12 + import os 13 + import subprocess 14 + import sys 15 + 16 + from pprint import pprint 17 + 18 + LIB_DIR = "../../tools/lib/python" 19 + SRC_DIR = os.path.dirname(os.path.realpath(__file__)) 20 + 21 + sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) 22 + 23 + from feat.parse_features import ParseFeature # pylint: disable=C0413 24 + 25 + SRCTREE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../..") 26 + DEFAULT_DIR = "Documentation/features" 27 + 28 + 29 + class GetFeature: 30 + """Helper class to parse feature parsing parameters""" 31 + 32 + @staticmethod 33 + def get_current_arch(): 34 + """Detects the current architecture""" 35 + 36 + proc = subprocess.run(["uname", "-m"], check=True, 37 + capture_output=True, text=True) 38 + 39 + arch = proc.stdout.strip() 40 + if arch in ["x86_64", "i386"]: 41 + arch = "x86" 42 + elif arch == "s390x": 43 + arch = "s390" 44 + 45 + return arch 46 + 47 + def run_parser(self, args): 48 + """Execute the feature parser""" 49 + 50 + feat = ParseFeature(args.directory, args.debug, args.enable_fname) 51 + data = feat.parse() 52 + 53 + if args.debug > 2: 54 + pprint(data) 55 + 56 + return feat 57 + 58 + def run_rest(self, args): 59 + """ 60 + Generate tables in ReST format. Three types of tables are 61 + supported, depending on the calling arguments: 62 + 63 + - neither feature nor arch is passed: generates a full matrix; 64 + - arch provided: generates a table of supported tables for the 65 + guiven architecture, eventually filtered by feature; 66 + - only feature provided: generates a table with feature details, 67 + showing what architectures it is implemented. 68 + """ 69 + 70 + feat = self.run_parser(args) 71 + 72 + if args.arch: 73 + rst = feat.output_arch_table(args.arch, args.feat) 74 + elif args.feat: 75 + rst = feat.output_feature(args.feat) 76 + else: 77 + rst = feat.output_matrix() 78 + 79 + print(rst) 80 + 81 + def run_current(self, args): 82 + """ 83 + Instead of using a --arch parameter, get feature for the current 84 + architecture. 85 + """ 86 + 87 + args.arch = self.get_current_arch() 88 + 89 + self.run_rest(args) 90 + 91 + def run_list(self, args): 92 + """ 93 + Generate a list of features for a given architecture, in a format 94 + parseable by other scripts. The output format is not ReST. 95 + """ 96 + 97 + if not args.arch: 98 + args.arch = self.get_current_arch() 99 + 100 + feat = self.run_parser(args) 101 + msg = feat.list_arch_features(args.arch, args.feat) 102 + 103 + print(msg) 104 + 105 + def parse_arch(self, parser): 106 + """Add a --arch parsing argument""" 107 + 108 + parser.add_argument("--arch", 109 + help="Output features for an specific" 110 + " architecture, optionally filtering for a " 111 + "single specific feature.") 112 + 113 + def parse_feat(self, parser): 114 + """Add a --feat parsing argument""" 115 + 116 + parser.add_argument("--feat", "--feature", 117 + help="Output features for a single specific " 118 + "feature.") 119 + 120 + 121 + def current_args(self, subparsers): 122 + """Implementscurrent argparse subparser""" 123 + 124 + parser = subparsers.add_parser("current", 125 + formatter_class=argparse.RawTextHelpFormatter, 126 + description="Output table in ReST " 127 + "compatible ASCII format " 128 + "with features for this " 129 + "machine's architecture") 130 + 131 + self.parse_feat(parser) 132 + parser.set_defaults(func=self.run_current) 133 + 134 + def rest_args(self, subparsers): 135 + """Implement rest argparse subparser""" 136 + 137 + parser = subparsers.add_parser("rest", 138 + formatter_class=argparse.RawTextHelpFormatter, 139 + description="Output table(s) in ReST " 140 + "compatible ASCII format " 141 + "with features in ReST " 142 + "markup language. The " 143 + "output is affected by " 144 + "--arch or --feat/--feature" 145 + " flags.") 146 + 147 + self.parse_arch(parser) 148 + self.parse_feat(parser) 149 + parser.set_defaults(func=self.run_rest) 150 + 151 + def list_args(self, subparsers): 152 + """Implement list argparse subparser""" 153 + 154 + parser = subparsers.add_parser("list", 155 + formatter_class=argparse.RawTextHelpFormatter, 156 + description="List features for this " 157 + "machine's architecture, " 158 + "using an easier to parse " 159 + "format. The output is " 160 + "affected by --arch flag.") 161 + 162 + self.parse_arch(parser) 163 + self.parse_feat(parser) 164 + parser.set_defaults(func=self.run_list) 165 + 166 + def validate_args(self, subparsers): 167 + """Implement validate argparse subparser""" 168 + 169 + parser = subparsers.add_parser("validate", 170 + formatter_class=argparse.RawTextHelpFormatter, 171 + description="Validate the contents of " 172 + "the files under " 173 + f"{DEFAULT_DIR}.") 174 + 175 + parser.set_defaults(func=self.run_parser) 176 + 177 + def parser(self): 178 + """ 179 + Create an arparse with common options and several subparsers 180 + """ 181 + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) 182 + 183 + parser.add_argument("-d", "--debug", action="count", default=0, 184 + help="Put the script in verbose mode, useful for " 185 + "debugging. Can be called multiple times, to " 186 + "increase verbosity.") 187 + 188 + parser.add_argument("--directory", "--dir", default=DEFAULT_DIR, 189 + help="Changes the location of the Feature files. " 190 + f"By default, it uses the {DEFAULT_DIR} " 191 + "directory.") 192 + 193 + parser.add_argument("--enable-fname", action="store_true", 194 + help="Prints the file name of the feature files. " 195 + "This can be used in order to track " 196 + "dependencies during documentation build.") 197 + 198 + subparsers = parser.add_subparsers() 199 + 200 + self.current_args(subparsers) 201 + self.rest_args(subparsers) 202 + self.list_args(subparsers) 203 + self.validate_args(subparsers) 204 + 205 + args = parser.parse_args() 206 + 207 + return args 208 + 209 + 210 + def main(): 211 + """Main program""" 212 + 213 + feat = GetFeature() 214 + 215 + args = feat.parser() 216 + 217 + if "func" in args: 218 + args.func(args) 219 + else: 220 + sys.exit(f"Please specify a valid command for {sys.argv[0]}") 221 + 222 + 223 + # Call main method 224 + if __name__ == "__main__": 225 + main()
+494
tools/lib/python/feat/parse_features.py
··· 1 + #!/usr/bin/env python3 2 + # pylint: disable=R0902,R0911,R0912,R0914,R0915 3 + # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. 4 + # SPDX-License-Identifier: GPL-2.0 5 + 6 + 7 + """ 8 + Library to parse the Linux Feature files and produce a ReST book. 9 + """ 10 + 11 + import os 12 + import re 13 + import sys 14 + 15 + from glob import iglob 16 + 17 + 18 + class ParseFeature: 19 + """ 20 + Parses Documentation/features, allowing to generate ReST documentation 21 + from it. 22 + """ 23 + 24 + h_name = "Feature" 25 + h_kconfig = "Kconfig" 26 + h_description = "Description" 27 + h_subsys = "Subsystem" 28 + h_status = "Status" 29 + h_arch = "Architecture" 30 + 31 + # Sort order for status. Others will be mapped at the end. 32 + status_map = { 33 + "ok": 0, 34 + "TODO": 1, 35 + "N/A": 2, 36 + # The only missing status is "..", which was mapped as "---", 37 + # as this is an special ReST cell value. Let it get the 38 + # default order (99). 39 + } 40 + 41 + def __init__(self, prefix, debug=0, enable_fname=False): 42 + """ 43 + Sets internal variables 44 + """ 45 + 46 + self.prefix = prefix 47 + self.debug = debug 48 + self.enable_fname = enable_fname 49 + 50 + self.data = {} 51 + 52 + # Initial maximum values use just the headers 53 + self.max_size_name = len(self.h_name) 54 + self.max_size_kconfig = len(self.h_kconfig) 55 + self.max_size_description = len(self.h_description) 56 + self.max_size_desc_word = 0 57 + self.max_size_subsys = len(self.h_subsys) 58 + self.max_size_status = len(self.h_status) 59 + self.max_size_arch = len(self.h_arch) 60 + self.max_size_arch_with_header = self.max_size_arch + self.max_size_arch 61 + self.description_size = 1 62 + 63 + self.msg = "" 64 + 65 + def emit(self, msg="", end="\n"): 66 + self.msg += msg + end 67 + 68 + def parse_error(self, fname, ln, msg, data=None): 69 + """ 70 + Displays an error message, printing file name and line 71 + """ 72 + 73 + if ln: 74 + fname += f"#{ln}" 75 + 76 + print(f"Warning: file {fname}: {msg}", file=sys.stderr, end="") 77 + 78 + if data: 79 + data = data.rstrip() 80 + print(f":\n\t{data}", file=sys.stderr) 81 + else: 82 + print("", file=sys.stderr) 83 + 84 + def parse_feat_file(self, fname): 85 + """Parses a single arch-support.txt feature file""" 86 + 87 + if os.path.isdir(fname): 88 + return 89 + 90 + base = os.path.basename(fname) 91 + 92 + if base != "arch-support.txt": 93 + if self.debug: 94 + print(f"ignoring {fname}", file=sys.stderr) 95 + return 96 + 97 + subsys = os.path.dirname(fname).split("/")[-2] 98 + self.max_size_subsys = max(self.max_size_subsys, len(subsys)) 99 + 100 + feature_name = "" 101 + kconfig = "" 102 + description = "" 103 + comments = "" 104 + arch_table = {} 105 + 106 + if self.debug > 1: 107 + print(f"Opening {fname}", file=sys.stderr) 108 + 109 + if self.enable_fname: 110 + full_fname = os.path.abspath(fname) 111 + self.emit(f".. FILE {full_fname}") 112 + 113 + with open(fname, encoding="utf-8") as f: 114 + for ln, line in enumerate(f, start=1): 115 + line = line.strip() 116 + 117 + match = re.match(r"^\#\s+Feature\s+name:\s*(.*\S)", line) 118 + if match: 119 + feature_name = match.group(1) 120 + 121 + self.max_size_name = max(self.max_size_name, 122 + len(feature_name)) 123 + continue 124 + 125 + match = re.match(r"^\#\s+Kconfig:\s*(.*\S)", line) 126 + if match: 127 + kconfig = match.group(1) 128 + 129 + self.max_size_kconfig = max(self.max_size_kconfig, 130 + len(kconfig)) 131 + continue 132 + 133 + match = re.match(r"^\#\s+description:\s*(.*\S)", line) 134 + if match: 135 + description = match.group(1) 136 + 137 + self.max_size_description = max(self.max_size_description, 138 + len(description)) 139 + 140 + words = re.split(r"\s+", line)[1:] 141 + for word in words: 142 + self.max_size_desc_word = max(self.max_size_desc_word, 143 + len(word)) 144 + 145 + continue 146 + 147 + if re.search(r"^\\s*$", line): 148 + continue 149 + 150 + if re.match(r"^\s*\-+\s*$", line): 151 + continue 152 + 153 + if re.search(r"^\s*\|\s*arch\s*\|\s*status\s*\|\s*$", line): 154 + continue 155 + 156 + match = re.match(r"^\#\s*(.*)$", line) 157 + if match: 158 + comments += match.group(1) 159 + continue 160 + 161 + match = re.match(r"^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$", line) 162 + if match: 163 + arch = match.group(1) 164 + status = match.group(2) 165 + 166 + self.max_size_status = max(self.max_size_status, 167 + len(status)) 168 + self.max_size_arch = max(self.max_size_arch, len(arch)) 169 + 170 + if status == "..": 171 + status = "---" 172 + 173 + arch_table[arch] = status 174 + 175 + continue 176 + 177 + self.parse_error(fname, ln, "Line is invalid", line) 178 + 179 + if not feature_name: 180 + self.parse_error(fname, 0, "Feature name not found") 181 + return 182 + if not subsys: 183 + self.parse_error(fname, 0, "Subsystem not found") 184 + return 185 + if not kconfig: 186 + self.parse_error(fname, 0, "Kconfig not found") 187 + return 188 + if not description: 189 + self.parse_error(fname, 0, "Description not found") 190 + return 191 + if not arch_table: 192 + self.parse_error(fname, 0, "Architecture table not found") 193 + return 194 + 195 + self.data[feature_name] = { 196 + "where": fname, 197 + "subsys": subsys, 198 + "kconfig": kconfig, 199 + "description": description, 200 + "comments": comments, 201 + "table": arch_table, 202 + } 203 + 204 + self.max_size_arch_with_header = self.max_size_arch + len(self.h_arch) 205 + 206 + def parse(self): 207 + """Parses all arch-support.txt feature files inside self.prefix""" 208 + 209 + path = os.path.expanduser(self.prefix) 210 + 211 + if self.debug > 2: 212 + print(f"Running parser for {path}") 213 + 214 + example_path = os.path.join(path, "arch-support.txt") 215 + 216 + for fname in iglob(os.path.join(path, "**"), recursive=True): 217 + if fname != example_path: 218 + self.parse_feat_file(fname) 219 + 220 + return self.data 221 + 222 + def output_arch_table(self, arch, feat=None): 223 + """ 224 + Output feature(s) for a given architecture. 225 + """ 226 + 227 + title = f"Feature status on {arch} architecture" 228 + 229 + self.emit("=" * len(title)) 230 + self.emit(title) 231 + self.emit("=" * len(title)) 232 + self.emit() 233 + 234 + self.emit("=" * self.max_size_subsys + " ", end="") 235 + self.emit("=" * self.max_size_name + " ", end="") 236 + self.emit("=" * self.max_size_kconfig + " ", end="") 237 + self.emit("=" * self.max_size_status + " ", end="") 238 + self.emit("=" * self.max_size_description) 239 + 240 + self.emit(f"{self.h_subsys:<{self.max_size_subsys}} ", end="") 241 + self.emit(f"{self.h_name:<{self.max_size_name}} ", end="") 242 + self.emit(f"{self.h_kconfig:<{self.max_size_kconfig}} ", end="") 243 + self.emit(f"{self.h_status:<{self.max_size_status}} ", end="") 244 + self.emit(f"{self.h_description:<{self.max_size_description}}") 245 + 246 + self.emit("=" * self.max_size_subsys + " ", end="") 247 + self.emit("=" * self.max_size_name + " ", end="") 248 + self.emit("=" * self.max_size_kconfig + " ", end="") 249 + self.emit("=" * self.max_size_status + " ", end="") 250 + self.emit("=" * self.max_size_description) 251 + 252 + sorted_features = sorted(self.data.keys(), 253 + key=lambda x: (self.data[x]["subsys"], 254 + x.lower())) 255 + 256 + for name in sorted_features: 257 + if feat and name != feat: 258 + continue 259 + 260 + arch_table = self.data[name]["table"] 261 + 262 + if not arch in arch_table: 263 + continue 264 + 265 + self.emit(f"{self.data[name]['subsys']:<{self.max_size_subsys}} ", 266 + end="") 267 + self.emit(f"{name:<{self.max_size_name}} ", end="") 268 + self.emit(f"{self.data[name]['kconfig']:<{self.max_size_kconfig}} ", 269 + end="") 270 + self.emit(f"{arch_table[arch]:<{self.max_size_status}} ", 271 + end="") 272 + self.emit(f"{self.data[name]['description']}") 273 + 274 + self.emit("=" * self.max_size_subsys + " ", end="") 275 + self.emit("=" * self.max_size_name + " ", end="") 276 + self.emit("=" * self.max_size_kconfig + " ", end="") 277 + self.emit("=" * self.max_size_status + " ", end="") 278 + self.emit("=" * self.max_size_description) 279 + 280 + return self.msg 281 + 282 + def output_feature(self, feat): 283 + """ 284 + Output a feature on all architectures 285 + """ 286 + 287 + title = f"Feature {feat}" 288 + 289 + self.emit("=" * len(title)) 290 + self.emit(title) 291 + self.emit("=" * len(title)) 292 + self.emit() 293 + 294 + if not feat in self.data: 295 + return 296 + 297 + if self.data[feat]["subsys"]: 298 + self.emit(f":Subsystem: {self.data[feat]['subsys']}") 299 + if self.data[feat]["kconfig"]: 300 + self.emit(f":Kconfig: {self.data[feat]['kconfig']}") 301 + 302 + desc = self.data[feat]["description"] 303 + desc = desc[0].upper() + desc[1:] 304 + desc = desc.rstrip(". \t") 305 + self.emit(f"\n{desc}.\n") 306 + 307 + com = self.data[feat]["comments"].strip() 308 + if com: 309 + self.emit("Comments") 310 + self.emit("--------") 311 + self.emit(f"\n{com}\n") 312 + 313 + self.emit("=" * self.max_size_arch + " ", end="") 314 + self.emit("=" * self.max_size_status) 315 + 316 + self.emit(f"{self.h_arch:<{self.max_size_arch}} ", end="") 317 + self.emit(f"{self.h_status:<{self.max_size_status}}") 318 + 319 + self.emit("=" * self.max_size_arch + " ", end="") 320 + self.emit("=" * self.max_size_status) 321 + 322 + arch_table = self.data[feat]["table"] 323 + for arch in sorted(arch_table.keys()): 324 + self.emit(f"{arch:<{self.max_size_arch}} ", end="") 325 + self.emit(f"{arch_table[arch]:<{self.max_size_status}}") 326 + 327 + self.emit("=" * self.max_size_arch + " ", end="") 328 + self.emit("=" * self.max_size_status) 329 + 330 + return self.msg 331 + 332 + def matrix_lines(self, desc_size, max_size_status, header): 333 + """ 334 + Helper function to split element tables at the output matrix 335 + """ 336 + 337 + if header: 338 + ln_marker = "=" 339 + else: 340 + ln_marker = "-" 341 + 342 + self.emit("+" + ln_marker * self.max_size_name + "+", end="") 343 + self.emit(ln_marker * desc_size, end="") 344 + self.emit("+" + ln_marker * max_size_status + "+") 345 + 346 + def output_matrix(self): 347 + """ 348 + Generates a set of tables, groped by subsystem, containing 349 + what's the feature state on each architecture. 350 + """ 351 + 352 + title = "Feature status on all architectures" 353 + 354 + self.emit("=" * len(title)) 355 + self.emit(title) 356 + self.emit("=" * len(title)) 357 + self.emit() 358 + 359 + desc_title = f"{self.h_kconfig} / {self.h_description}" 360 + 361 + desc_size = self.max_size_kconfig + 4 362 + if not self.description_size: 363 + desc_size = max(self.max_size_description, desc_size) 364 + else: 365 + desc_size = max(self.description_size, desc_size) 366 + 367 + desc_size = max(self.max_size_desc_word, desc_size, len(desc_title)) 368 + 369 + notcompat = "Not compatible" 370 + self.max_size_status = max(self.max_size_status, len(notcompat)) 371 + 372 + min_status_size = self.max_size_status + self.max_size_arch + 4 373 + max_size_status = max(min_status_size, self.max_size_status) 374 + 375 + h_status_per_arch = "Status per architecture" 376 + max_size_status = max(max_size_status, len(h_status_per_arch)) 377 + 378 + cur_subsys = None 379 + for name in sorted(self.data.keys(), 380 + key=lambda x: (self.data[x]["subsys"], x.lower())): 381 + if not cur_subsys or cur_subsys != self.data[name]["subsys"]: 382 + if cur_subsys: 383 + self.emit() 384 + 385 + cur_subsys = self.data[name]["subsys"] 386 + 387 + title = f"Subsystem: {cur_subsys}" 388 + self.emit(title) 389 + self.emit("=" * len(title)) 390 + self.emit() 391 + 392 + self.matrix_lines(desc_size, max_size_status, 0) 393 + 394 + self.emit(f"|{self.h_name:<{self.max_size_name}}", end="") 395 + self.emit(f"|{desc_title:<{desc_size}}", end="") 396 + self.emit(f"|{h_status_per_arch:<{max_size_status}}|") 397 + 398 + self.matrix_lines(desc_size, max_size_status, 1) 399 + 400 + lines = [] 401 + descs = [] 402 + cur_status = "" 403 + line = "" 404 + 405 + arch_table = sorted(self.data[name]["table"].items(), 406 + key=lambda x: (self.status_map.get(x[1], 99), 407 + x[0].lower())) 408 + 409 + for arch, status in arch_table: 410 + if status == "---": 411 + status = notcompat 412 + 413 + if status != cur_status: 414 + if line != "": 415 + lines.append(line) 416 + line = "" 417 + line = f"- **{status}**: {arch}" 418 + elif len(line) + len(arch) + 2 < max_size_status: 419 + line += f", {arch}" 420 + else: 421 + lines.append(line) 422 + line = f" {arch}" 423 + cur_status = status 424 + 425 + if line != "": 426 + lines.append(line) 427 + 428 + description = self.data[name]["description"] 429 + while len(description) > desc_size: 430 + desc_line = description[:desc_size] 431 + 432 + last_space = desc_line.rfind(" ") 433 + if last_space != -1: 434 + desc_line = desc_line[:last_space] 435 + descs.append(desc_line) 436 + description = description[last_space + 1:] 437 + else: 438 + desc_line = desc_line[:-1] 439 + descs.append(desc_line + "\\") 440 + description = description[len(desc_line):] 441 + 442 + if description: 443 + descs.append(description) 444 + 445 + while len(lines) < 2 + len(descs): 446 + lines.append("") 447 + 448 + for ln, line in enumerate(lines): 449 + col = ["", ""] 450 + 451 + if not ln: 452 + col[0] = name 453 + col[1] = f"``{self.data[name]['kconfig']}``" 454 + else: 455 + if ln >= 2 and descs: 456 + col[1] = descs.pop(0) 457 + 458 + self.emit(f"|{col[0]:<{self.max_size_name}}", end="") 459 + self.emit(f"|{col[1]:<{desc_size}}", end="") 460 + self.emit(f"|{line:<{max_size_status}}|") 461 + 462 + self.matrix_lines(desc_size, max_size_status, 0) 463 + 464 + return self.msg 465 + 466 + def list_arch_features(self, arch, feat): 467 + """ 468 + Print a matrix of kernel feature support for the chosen architecture. 469 + """ 470 + self.emit("#") 471 + self.emit(f"# Kernel feature support matrix of the '{arch}' architecture:") 472 + self.emit("#") 473 + 474 + # Sort by subsystem, then by feature name (case‑insensitive) 475 + for name in sorted(self.data.keys(), 476 + key=lambda n: (self.data[n]["subsys"].lower(), 477 + n.lower())): 478 + if feat and name != feat: 479 + continue 480 + 481 + feature = self.data[name] 482 + arch_table = feature["table"] 483 + status = arch_table.get(arch, "") 484 + status = " " * ((4 - len(status)) // 2) + status 485 + 486 + self.emit(f"{feature['subsys']:>{self.max_size_subsys + 1}}/ ", 487 + end="") 488 + self.emit(f"{name:<{self.max_size_name}}: ", end="") 489 + self.emit(f"{status:<5}| ", end="") 490 + self.emit(f"{feature['kconfig']:>{self.max_size_kconfig}} ", 491 + end="") 492 + self.emit(f"# {feature['description']}") 493 + 494 + return self.msg