linux/scripts/get_feat.pl
<<
>>
Prefs
   1#!/usr/bin/perl
   2# SPDX-License-Identifier: GPL-2.0
   3
   4use strict;
   5use Pod::Usage;
   6use Getopt::Long;
   7use File::Find;
   8use Fcntl ':mode';
   9use Cwd 'abs_path';
  10
  11my $help;
  12my $man;
  13my $debug;
  14my $arch;
  15my $feat;
  16
  17my $basename = abs_path($0);
  18$basename =~ s,/[^/]+$,/,;
  19
  20my $prefix=$basename . "../Documentation/features";
  21
  22# Used only at for full features output. The script will auto-adjust
  23# such values for the minimal possible values
  24my $status_size = 1;
  25my $description_size = 1;
  26
  27GetOptions(
  28        "debug|d+" => \$debug,
  29        "dir=s" => \$prefix,
  30        'help|?' => \$help,
  31        'arch=s' => \$arch,
  32        'feat=s' => \$feat,
  33        'feature=s' => \$feat,
  34        man => \$man
  35) or pod2usage(2);
  36
  37pod2usage(1) if $help;
  38pod2usage(-exitstatus => 0, -verbose => 2) if $man;
  39
  40pod2usage(1) if (scalar @ARGV < 1 || @ARGV > 2);
  41
  42my ($cmd, $arg) = @ARGV;
  43
  44pod2usage(2) if ($cmd ne "current" && $cmd ne "rest" && $cmd ne "validate"
  45                && $cmd ne "ls" && $cmd ne "list");
  46
  47require Data::Dumper if ($debug);
  48
  49my %data;
  50my %archs;
  51
  52#
  53# Displays an error message, printing file name and line
  54#
  55sub parse_error($$$$) {
  56        my ($file, $ln, $msg, $data) = @_;
  57
  58        $data =~ s/\s+$/\n/;
  59
  60        print STDERR "Warning: file $file#$ln:\n\t$msg";
  61
  62        if ($data ne "") {
  63                print STDERR ". Line\n\t\t$data";
  64        } else {
  65            print STDERR "\n";
  66        }
  67}
  68
  69#
  70# Parse a features file, storing its contents at %data
  71#
  72
  73my $h_name = "Feature";
  74my $h_kconfig = "Kconfig";
  75my $h_description = "Description";
  76my $h_subsys = "Subsystem";
  77my $h_status = "Status";
  78my $h_arch = "Architecture";
  79
  80my $max_size_name = length($h_name);
  81my $max_size_kconfig = length($h_kconfig);
  82my $max_size_description = length($h_description);
  83my $max_size_subsys = length($h_subsys);
  84my $max_size_status = length($h_status);
  85
  86my $max_size_arch = 0;
  87my $max_size_arch_with_header;
  88my $max_description_word = 0;
  89
  90sub parse_feat {
  91        my $file = $File::Find::name;
  92
  93        my $mode = (stat($file))[2];
  94        return if ($mode & S_IFDIR);
  95        return if ($file =~ m,($prefix)/arch-support.txt,);
  96        return if (!($file =~ m,arch-support.txt$,));
  97
  98        my $subsys = "";
  99        $subsys = $2 if ( m,.*($prefix)/([^/]+).*,);
 100
 101        if (length($subsys) > $max_size_subsys) {
 102                $max_size_subsys = length($subsys);
 103        }
 104
 105        my $name;
 106        my $kconfig;
 107        my $description;
 108        my $comments = "";
 109        my $last_status;
 110        my $ln;
 111        my %arch_table;
 112
 113        print STDERR "Opening $file\n" if ($debug > 1);
 114        open IN, $file;
 115
 116        while(<IN>) {
 117                $ln++;
 118
 119                if (m/^\#\s+Feature\s+name:\s*(.*\S)/) {
 120                        $name = $1;
 121                        if (length($name) > $max_size_name) {
 122                                $max_size_name = length($name);
 123                        }
 124                        next;
 125                }
 126                if (m/^\#\s+Kconfig:\s*(.*\S)/) {
 127                        $kconfig = $1;
 128                        if (length($kconfig) > $max_size_kconfig) {
 129                                $max_size_kconfig = length($kconfig);
 130                        }
 131                        next;
 132                }
 133                if (m/^\#\s+description:\s*(.*\S)/) {
 134                        $description = $1;
 135                        if (length($description) > $max_size_description) {
 136                                $max_size_description = length($description);
 137                        }
 138
 139                        foreach my $word (split /\s+/, $description) {
 140                                if (length($word) > $max_description_word) {
 141                                        $max_description_word = length($word);
 142                                }
 143                        }
 144
 145                        next;
 146                }
 147                next if (m/^\\s*$/);
 148                next if (m/^\s*\-+\s*$/);
 149                next if (m/^\s*\|\s*arch\s*\|\s*status\s*\|\s*$/);
 150
 151                if (m/^\#\s*(.*)/) {
 152                        $comments .= "$1\n";
 153                        next;
 154                }
 155                if (m/^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$/) {
 156                        my $a = $1;
 157                        my $status = $2;
 158
 159                        if (length($status) > $max_size_status) {
 160                                $max_size_status = length($status);
 161                        }
 162                        if (length($a) > $max_size_arch) {
 163                                $max_size_arch = length($a);
 164                        }
 165
 166                        $status = "---" if ($status =~ m/^\.\.$/);
 167
 168                        $archs{$a} = 1;
 169                        $arch_table{$a} = $status;
 170                        next;
 171                }
 172
 173                #Everything else is an error
 174                parse_error($file, $ln, "line is invalid", $_);
 175        }
 176        close IN;
 177
 178        if (!$name) {
 179                parse_error($file, $ln, "Feature name not found", "");
 180                return;
 181        }
 182
 183        parse_error($file, $ln, "Subsystem not found", "") if (!$subsys);
 184        parse_error($file, $ln, "Kconfig not found", "") if (!$kconfig);
 185        parse_error($file, $ln, "Description not found", "") if (!$description);
 186
 187        if (!%arch_table) {
 188                parse_error($file, $ln, "Architecture table not found", "");
 189                return;
 190        }
 191
 192        $data{$name}->{where} = $file;
 193        $data{$name}->{subsys} = $subsys;
 194        $data{$name}->{kconfig} = $kconfig;
 195        $data{$name}->{description} = $description;
 196        $data{$name}->{comments} = $comments;
 197        $data{$name}->{table} = \%arch_table;
 198
 199        $max_size_arch_with_header = $max_size_arch + length($h_arch);
 200}
 201
 202#
 203# Output feature(s) for a given architecture
 204#
 205sub output_arch_table {
 206        my $title = "Feature status on $arch architecture";
 207
 208        print "=" x length($title) . "\n";
 209        print "$title\n";
 210        print "=" x length($title) . "\n\n";
 211
 212        print "=" x $max_size_subsys;
 213        print "  ";
 214        print "=" x $max_size_name;
 215        print "  ";
 216        print "=" x $max_size_kconfig;
 217        print "  ";
 218        print "=" x $max_size_status;
 219        print "  ";
 220        print "=" x $max_size_description;
 221        print "\n";
 222        printf "%-${max_size_subsys}s  ", $h_subsys;
 223        printf "%-${max_size_name}s  ", $h_name;
 224        printf "%-${max_size_kconfig}s  ", $h_kconfig;
 225        printf "%-${max_size_status}s  ", $h_status;
 226        printf "%-${max_size_description}s\n", $h_description;
 227        print "=" x $max_size_subsys;
 228        print "  ";
 229        print "=" x $max_size_name;
 230        print "  ";
 231        print "=" x $max_size_kconfig;
 232        print "  ";
 233        print "=" x $max_size_status;
 234        print "  ";
 235        print "=" x $max_size_description;
 236        print "\n";
 237
 238        foreach my $name (sort {
 239                                ($data{$a}->{subsys} cmp $data{$b}->{subsys}) ||
 240                                ("\L$a" cmp "\L$b")
 241                               } keys %data) {
 242                next if ($feat && $name ne $feat);
 243
 244                my %arch_table = %{$data{$name}->{table}};
 245                printf "%-${max_size_subsys}s  ", $data{$name}->{subsys};
 246                printf "%-${max_size_name}s  ", $name;
 247                printf "%-${max_size_kconfig}s  ", $data{$name}->{kconfig};
 248                printf "%-${max_size_status}s  ", $arch_table{$arch};
 249                printf "%-s\n", $data{$name}->{description};
 250        }
 251
 252        print "=" x $max_size_subsys;
 253        print "  ";
 254        print "=" x $max_size_name;
 255        print "  ";
 256        print "=" x $max_size_kconfig;
 257        print "  ";
 258        print "=" x $max_size_status;
 259        print "  ";
 260        print "=" x $max_size_description;
 261        print "\n";
 262}
 263
 264#
 265# list feature(s) for a given architecture
 266#
 267sub list_arch_features {
 268        print "#\n# Kernel feature support matrix of the '$arch' architecture:\n#\n";
 269
 270        foreach my $name (sort {
 271                                ($data{$a}->{subsys} cmp $data{$b}->{subsys}) ||
 272                                ("\L$a" cmp "\L$b")
 273                               } keys %data) {
 274                next if ($feat && $name ne $feat);
 275
 276                my %arch_table = %{$data{$name}->{table}};
 277
 278                my $status = $arch_table{$arch};
 279                $status = " " x ((4 - length($status)) / 2) . $status;
 280
 281                printf " %${max_size_subsys}s/ ", $data{$name}->{subsys};
 282                printf "%-${max_size_name}s: ", $name;
 283                printf "%-5s|   ", $status;
 284                printf "%${max_size_kconfig}s # ", $data{$name}->{kconfig};
 285                printf " %s\n", $data{$name}->{description};
 286        }
 287}
 288
 289#
 290# Output a feature on all architectures
 291#
 292sub output_feature {
 293        my $title = "Feature $feat";
 294
 295        print "=" x length($title) . "\n";
 296        print "$title\n";
 297        print "=" x length($title) . "\n\n";
 298
 299        print ":Subsystem: $data{$feat}->{subsys} \n" if ($data{$feat}->{subsys});
 300        print ":Kconfig: $data{$feat}->{kconfig} \n" if ($data{$feat}->{kconfig});
 301
 302        my $desc = $data{$feat}->{description};
 303        $desc =~ s/^([a-z])/\U$1/;
 304        $desc =~ s/\.?\s*//;
 305        print "\n$desc.\n\n";
 306
 307        my $com = $data{$feat}->{comments};
 308        $com =~ s/^\s+//;
 309        $com =~ s/\s+$//;
 310        if ($com) {
 311                print "Comments\n";
 312                print "--------\n\n";
 313                print "$com\n\n";
 314        }
 315
 316        print "=" x $max_size_arch_with_header;
 317        print "  ";
 318        print "=" x $max_size_status;
 319        print "\n";
 320
 321        printf "%-${max_size_arch}s  ", $h_arch;
 322        printf "%-${max_size_status}s", $h_status . "\n";
 323
 324        print "=" x $max_size_arch_with_header;
 325        print "  ";
 326        print "=" x $max_size_status;
 327        print "\n";
 328
 329        my %arch_table = %{$data{$feat}->{table}};
 330        foreach my $arch (sort keys %arch_table) {
 331                printf "%-${max_size_arch}s  ", $arch;
 332                printf "%-${max_size_status}s\n", $arch_table{$arch};
 333        }
 334
 335        print "=" x $max_size_arch_with_header;
 336        print "  ";
 337        print "=" x $max_size_status;
 338        print "\n";
 339}
 340
 341#
 342# Output all features for all architectures
 343#
 344
 345sub matrix_lines($$$) {
 346        my $desc_size = shift;
 347        my $status_size = shift;
 348        my $header = shift;
 349        my $fill;
 350        my $ln_marker;
 351
 352        if ($header) {
 353                $ln_marker = "=";
 354        } else {
 355                $ln_marker = "-";
 356        }
 357
 358        $fill = $ln_marker;
 359
 360        print "+";
 361        print $fill x $max_size_name;
 362        print "+";
 363        print $fill x $desc_size;
 364        print "+";
 365        print $ln_marker x $status_size;
 366        print "+\n";
 367}
 368
 369sub output_matrix {
 370        my $title = "Feature status on all architectures";
 371        my $notcompat = "Not compatible";
 372
 373        print "=" x length($title) . "\n";
 374        print "$title\n";
 375        print "=" x length($title) . "\n\n";
 376
 377        my $desc_title = "$h_kconfig / $h_description";
 378
 379        my $desc_size = $max_size_kconfig + 4;
 380        if (!$description_size) {
 381                $desc_size = $max_size_description if ($max_size_description > $desc_size);
 382        } else {
 383                $desc_size = $description_size if ($description_size > $desc_size);
 384        }
 385        $desc_size = $max_description_word if ($max_description_word > $desc_size);
 386
 387        $desc_size = length($desc_title) if (length($desc_title) > $desc_size);
 388
 389        $max_size_status = length($notcompat) if (length($notcompat) > $max_size_status);
 390
 391        # Ensure that the status will fit
 392        my $min_status_size = $max_size_status + $max_size_arch + 6;
 393        $status_size = $min_status_size if ($status_size < $min_status_size);
 394
 395
 396        my $cur_subsys = "";
 397        foreach my $name (sort {
 398                                ($data{$a}->{subsys} cmp $data{$b}->{subsys}) or
 399                                ("\L$a" cmp "\L$b")
 400                               } keys %data) {
 401
 402                if ($cur_subsys ne $data{$name}->{subsys}) {
 403                        if ($cur_subsys ne "") {
 404                                printf "\n";
 405                        }
 406
 407                        $cur_subsys = $data{$name}->{subsys};
 408
 409                        my $title = "Subsystem: $cur_subsys";
 410                        print "$title\n";
 411                        print "=" x length($title) . "\n\n";
 412
 413
 414                        matrix_lines($desc_size, $status_size, 0);
 415
 416                        printf "|%-${max_size_name}s", $h_name;
 417                        printf "|%-${desc_size}s", $desc_title;
 418
 419                        printf "|%-${status_size}s|\n", "Status per architecture";
 420                        matrix_lines($desc_size, $status_size, 1);
 421                }
 422
 423                my %arch_table = %{$data{$name}->{table}};
 424                my $cur_status = "";
 425
 426                my (@lines, @descs);
 427                my $line = "";
 428                foreach my $arch (sort {
 429                                        ($arch_table{$b} cmp $arch_table{$a}) or
 430                                        ("\L$a" cmp "\L$b")
 431                                       } keys %arch_table) {
 432
 433                        my $status = $arch_table{$arch};
 434
 435                        if ($status eq "---") {
 436                                $status = $notcompat;
 437                        }
 438
 439                        if ($status ne $cur_status) {
 440                                if ($line ne "") {
 441                                        push @lines, $line;
 442                                        $line = "";
 443                                }
 444                                $line = "- **" . $status . "**: " . $arch;
 445                        } elsif (length($line) + length ($arch) + 2 < $status_size) {
 446                                $line .= ", " . $arch;
 447                        } else {
 448                                push @lines, $line;
 449                                $line = "  " . $arch;
 450                        }
 451                        $cur_status = $status;
 452                }
 453                push @lines, $line if ($line ne "");
 454
 455                my $description = $data{$name}->{description};
 456                while (length($description) > $desc_size) {
 457                        my $d = substr $description, 0, $desc_size;
 458
 459                        # Ensure that it will end on a space
 460                        # if it can't, it means that the size is too small
 461                        # Instead of aborting it, let's print what we have
 462                        if (!($d =~ s/^(.*)\s+.*/$1/)) {
 463                                $d = substr $d, 0, -1;
 464                                push @descs, "$d\\";
 465                                $description =~ s/^\Q$d\E//;
 466                        } else {
 467                                push @descs, $d;
 468                                $description =~ s/^\Q$d\E\s+//;
 469                        }
 470                }
 471                push @descs, $description;
 472
 473                # Ensure that the full description will be printed
 474                push @lines, "" while (scalar(@lines) < 2 + scalar(@descs));
 475
 476                my $ln = 0;
 477                for my $line(@lines) {
 478                        if (!$ln) {
 479                                printf "|%-${max_size_name}s", $name;
 480                                printf "|%-${desc_size}s", "``" . $data{$name}->{kconfig} . "``";
 481                        } elsif ($ln >= 2 && scalar(@descs)) {
 482                                printf "|%-${max_size_name}s", "";
 483                                printf "|%-${desc_size}s", shift @descs;
 484                        } else {
 485                                printf "|%-${max_size_name}s", "";
 486                                printf "|%-${desc_size}s", "";
 487                        }
 488
 489                        printf "|%-${status_size}s|\n", $line;
 490
 491                        $ln++;
 492                }
 493                matrix_lines($desc_size, $status_size, 0);
 494        }
 495}
 496
 497
 498#
 499# Parses all feature files located at $prefix dir
 500#
 501find({wanted =>\&parse_feat, no_chdir => 1}, $prefix);
 502
 503print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug);
 504
 505#
 506# Handles the command
 507#
 508if ($cmd eq "current") {
 509        $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/');
 510        $arch =~s/\s+$//;
 511}
 512
 513if ($cmd eq "ls" or $cmd eq "list") {
 514        if (!$arch) {
 515                $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/');
 516                $arch =~s/\s+$//;
 517        }
 518
 519        list_arch_features;
 520
 521        exit;
 522}
 523
 524if ($cmd ne "validate") {
 525        if ($arch) {
 526                output_arch_table;
 527        } elsif ($feat) {
 528                output_feature;
 529        } else {
 530                output_matrix;
 531        }
 532}
 533
 534__END__
 535
 536=head1 NAME
 537
 538get_feat.pl - parse the Linux Feature files and produce a ReST book.
 539
 540=head1 SYNOPSIS
 541
 542B<get_feat.pl> [--debug] [--man] [--help] [--dir=<dir>] [--arch=<arch>]
 543               [--feature=<feature>|--feat=<feature>] <COMAND> [<ARGUMENT>]
 544
 545Where <COMMAND> can be:
 546
 547=over 8
 548
 549B<current>               - output table in ReST compatible ASCII format
 550                           with features for this machine's architecture
 551
 552B<rest>                  - output table(s)  in ReST compatible ASCII format
 553                           with features in ReST markup language. The output
 554                           is affected by --arch or --feat/--feature flags.
 555
 556B<validate>              - validate the contents of the files under
 557                           Documentation/features.
 558
 559B<ls> or B<list>         - list features for this machine's architecture,
 560                           using an easier to parse format.
 561                           The output is affected by --arch flag.
 562
 563=back
 564
 565=head1 OPTIONS
 566
 567=over 8
 568
 569=item B<--arch>
 570
 571Output features for an specific architecture, optionally filtering for
 572a single specific feature.
 573
 574=item B<--feat> or B<--feature>
 575
 576Output features for a single specific feature.
 577
 578=item B<--dir>
 579
 580Changes the location of the Feature files. By default, it uses
 581the Documentation/features directory.
 582
 583=item B<--debug>
 584
 585Put the script in verbose mode, useful for debugging. Can be called multiple
 586times, to increase verbosity.
 587
 588=item B<--help>
 589
 590Prints a brief help message and exits.
 591
 592=item B<--man>
 593
 594Prints the manual page and exits.
 595
 596=back
 597
 598=head1 DESCRIPTION
 599
 600Parse the Linux feature files from Documentation/features (by default),
 601optionally producing results at ReST format.
 602
 603It supports output data per architecture, per feature or a
 604feature x arch matrix.
 605
 606When used with B<rest> command, it will use either one of the tree formats:
 607
 608If neither B<--arch> or B<--feature> arguments are used, it will output a
 609matrix with features per architecture.
 610
 611If B<--arch> argument is used, it will output the features availability for
 612a given architecture.
 613
 614If B<--feat> argument is used, it will output the content of the feature
 615file using ReStructured Text markup.
 616
 617=head1 BUGS
 618
 619Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
 620
 621=head1 COPYRIGHT
 622
 623Copyright (c) 2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>.
 624
 625License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>.
 626
 627This is free software: you are free to change and redistribute it.
 628There is NO WARRANTY, to the extent permitted by law.
 629
 630=cut
 631