Back to home page

OSCL-LXR

 
 

    


0001 #!/usr/bin/env perl
0002 # SPDX-License-Identifier: GPL-2.0
0003 
0004 use strict;
0005 use Pod::Usage;
0006 use Getopt::Long;
0007 use File::Find;
0008 use Fcntl ':mode';
0009 use Cwd 'abs_path';
0010 
0011 my $help;
0012 my $man;
0013 my $debug;
0014 my $arch;
0015 my $feat;
0016 my $enable_fname;
0017 
0018 my $basename = abs_path($0);
0019 $basename =~ s,/[^/]+$,/,;
0020 
0021 my $prefix=$basename . "../Documentation/features";
0022 
0023 # Used only at for full features output. The script will auto-adjust
0024 # such values for the minimal possible values
0025 my $status_size = 1;
0026 my $description_size = 1;
0027 
0028 GetOptions(
0029     "debug|d+" => \$debug,
0030     "dir=s" => \$prefix,
0031     'help|?' => \$help,
0032     'arch=s' => \$arch,
0033     'feat=s' => \$feat,
0034     'feature=s' => \$feat,
0035     "enable-fname" => \$enable_fname,
0036     man => \$man
0037 ) or pod2usage(2);
0038 
0039 pod2usage(1) if $help;
0040 pod2usage(-exitstatus => 0, -verbose => 2) if $man;
0041 
0042 pod2usage(1) if (scalar @ARGV < 1 || @ARGV > 2);
0043 
0044 my ($cmd, $arg) = @ARGV;
0045 
0046 pod2usage(2) if ($cmd ne "current" && $cmd ne "rest" && $cmd ne "validate"
0047         && $cmd ne "ls" && $cmd ne "list");
0048 
0049 require Data::Dumper if ($debug);
0050 
0051 my %data;
0052 my %archs;
0053 
0054 #
0055 # Displays an error message, printing file name and line
0056 #
0057 sub parse_error($$$$) {
0058     my ($file, $ln, $msg, $data) = @_;
0059 
0060     $data =~ s/\s+$/\n/;
0061 
0062     print STDERR "Warning: file $file#$ln:\n\t$msg";
0063 
0064     if ($data ne "") {
0065         print STDERR ". Line\n\t\t$data";
0066     } else {
0067         print STDERR "\n";
0068     }
0069 }
0070 
0071 #
0072 # Parse a features file, storing its contents at %data
0073 #
0074 
0075 my $h_name = "Feature";
0076 my $h_kconfig = "Kconfig";
0077 my $h_description = "Description";
0078 my $h_subsys = "Subsystem";
0079 my $h_status = "Status";
0080 my $h_arch = "Architecture";
0081 
0082 my $max_size_name = length($h_name);
0083 my $max_size_kconfig = length($h_kconfig);
0084 my $max_size_description = length($h_description);
0085 my $max_size_subsys = length($h_subsys);
0086 my $max_size_status = length($h_status);
0087 
0088 my $max_size_arch = 0;
0089 my $max_size_arch_with_header;
0090 my $max_description_word = 0;
0091 
0092 sub parse_feat {
0093     my $file = $File::Find::name;
0094 
0095     my $mode = (stat($file))[2];
0096     return if ($mode & S_IFDIR);
0097     return if ($file =~ m,($prefix)/arch-support.txt,);
0098     return if (!($file =~ m,arch-support.txt$,));
0099 
0100     if ($enable_fname) {
0101         printf ".. FILE %s\n", abs_path($file);
0102     }
0103 
0104     my $subsys = "";
0105     $subsys = $2 if ( m,.*($prefix)/([^/]+).*,);
0106 
0107     if (length($subsys) > $max_size_subsys) {
0108         $max_size_subsys = length($subsys);
0109     }
0110 
0111     my $name;
0112     my $kconfig;
0113     my $description;
0114     my $comments = "";
0115     my $last_status;
0116     my $ln;
0117     my %arch_table;
0118 
0119     print STDERR "Opening $file\n" if ($debug > 1);
0120     open IN, $file;
0121 
0122     while(<IN>) {
0123         $ln++;
0124 
0125         if (m/^\#\s+Feature\s+name:\s*(.*\S)/) {
0126             $name = $1;
0127             if (length($name) > $max_size_name) {
0128                 $max_size_name = length($name);
0129             }
0130             next;
0131         }
0132         if (m/^\#\s+Kconfig:\s*(.*\S)/) {
0133             $kconfig = $1;
0134             if (length($kconfig) > $max_size_kconfig) {
0135                 $max_size_kconfig = length($kconfig);
0136             }
0137             next;
0138         }
0139         if (m/^\#\s+description:\s*(.*\S)/) {
0140             $description = $1;
0141             if (length($description) > $max_size_description) {
0142                 $max_size_description = length($description);
0143             }
0144 
0145             foreach my $word (split /\s+/, $description) {
0146                 if (length($word) > $max_description_word) {
0147                     $max_description_word = length($word);
0148                 }
0149             }
0150 
0151             next;
0152         }
0153         next if (m/^\\s*$/);
0154         next if (m/^\s*\-+\s*$/);
0155         next if (m/^\s*\|\s*arch\s*\|\s*status\s*\|\s*$/);
0156 
0157         if (m/^\#\s*(.*)/) {
0158             $comments .= "$1\n";
0159             next;
0160         }
0161         if (m/^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$/) {
0162             my $a = $1;
0163             my $status = $2;
0164 
0165             if (length($status) > $max_size_status) {
0166                 $max_size_status = length($status);
0167             }
0168             if (length($a) > $max_size_arch) {
0169                 $max_size_arch = length($a);
0170             }
0171 
0172             $status = "---" if ($status =~ m/^\.\.$/);
0173 
0174             $archs{$a} = 1;
0175             $arch_table{$a} = $status;
0176             next;
0177         }
0178 
0179         #Everything else is an error
0180         parse_error($file, $ln, "line is invalid", $_);
0181     }
0182     close IN;
0183 
0184     if (!$name) {
0185         parse_error($file, $ln, "Feature name not found", "");
0186         return;
0187     }
0188 
0189     parse_error($file, $ln, "Subsystem not found", "") if (!$subsys);
0190     parse_error($file, $ln, "Kconfig not found", "") if (!$kconfig);
0191     parse_error($file, $ln, "Description not found", "") if (!$description);
0192 
0193     if (!%arch_table) {
0194         parse_error($file, $ln, "Architecture table not found", "");
0195         return;
0196     }
0197 
0198     $data{$name}->{where} = $file;
0199     $data{$name}->{subsys} = $subsys;
0200     $data{$name}->{kconfig} = $kconfig;
0201     $data{$name}->{description} = $description;
0202     $data{$name}->{comments} = $comments;
0203     $data{$name}->{table} = \%arch_table;
0204 
0205     $max_size_arch_with_header = $max_size_arch + length($h_arch);
0206 }
0207 
0208 #
0209 # Output feature(s) for a given architecture
0210 #
0211 sub output_arch_table {
0212     my $title = "Feature status on $arch architecture";
0213 
0214     print "=" x length($title) . "\n";
0215     print "$title\n";
0216     print "=" x length($title) . "\n\n";
0217 
0218     print "=" x $max_size_subsys;
0219     print "  ";
0220     print "=" x $max_size_name;
0221     print "  ";
0222     print "=" x $max_size_kconfig;
0223     print "  ";
0224     print "=" x $max_size_status;
0225     print "  ";
0226     print "=" x $max_size_description;
0227     print "\n";
0228     printf "%-${max_size_subsys}s  ", $h_subsys;
0229     printf "%-${max_size_name}s  ", $h_name;
0230     printf "%-${max_size_kconfig}s  ", $h_kconfig;
0231     printf "%-${max_size_status}s  ", $h_status;
0232     printf "%-${max_size_description}s\n", $h_description;
0233     print "=" x $max_size_subsys;
0234     print "  ";
0235     print "=" x $max_size_name;
0236     print "  ";
0237     print "=" x $max_size_kconfig;
0238     print "  ";
0239     print "=" x $max_size_status;
0240     print "  ";
0241     print "=" x $max_size_description;
0242     print "\n";
0243 
0244     foreach my $name (sort {
0245                 ($data{$a}->{subsys} cmp $data{$b}->{subsys}) ||
0246                 ("\L$a" cmp "\L$b")
0247                    } keys %data) {
0248         next if ($feat && $name ne $feat);
0249 
0250         my %arch_table = %{$data{$name}->{table}};
0251         printf "%-${max_size_subsys}s  ", $data{$name}->{subsys};
0252         printf "%-${max_size_name}s  ", $name;
0253         printf "%-${max_size_kconfig}s  ", $data{$name}->{kconfig};
0254         printf "%-${max_size_status}s  ", $arch_table{$arch};
0255         printf "%-s\n", $data{$name}->{description};
0256     }
0257 
0258     print "=" x $max_size_subsys;
0259     print "  ";
0260     print "=" x $max_size_name;
0261     print "  ";
0262     print "=" x $max_size_kconfig;
0263     print "  ";
0264     print "=" x $max_size_status;
0265     print "  ";
0266     print "=" x $max_size_description;
0267     print "\n";
0268 }
0269 
0270 #
0271 # list feature(s) for a given architecture
0272 #
0273 sub list_arch_features {
0274     print "#\n# Kernel feature support matrix of the '$arch' architecture:\n#\n";
0275 
0276     foreach my $name (sort {
0277                 ($data{$a}->{subsys} cmp $data{$b}->{subsys}) ||
0278                 ("\L$a" cmp "\L$b")
0279                    } keys %data) {
0280         next if ($feat && $name ne $feat);
0281 
0282         my %arch_table = %{$data{$name}->{table}};
0283 
0284         my $status = $arch_table{$arch};
0285         $status = " " x ((4 - length($status)) / 2) . $status;
0286 
0287         printf " %${max_size_subsys}s/ ", $data{$name}->{subsys};
0288         printf "%-${max_size_name}s: ", $name;
0289         printf "%-5s|   ", $status;
0290         printf "%${max_size_kconfig}s # ", $data{$name}->{kconfig};
0291         printf " %s\n", $data{$name}->{description};
0292     }
0293 }
0294 
0295 #
0296 # Output a feature on all architectures
0297 #
0298 sub output_feature {
0299     my $title = "Feature $feat";
0300 
0301     print "=" x length($title) . "\n";
0302     print "$title\n";
0303     print "=" x length($title) . "\n\n";
0304 
0305     print ":Subsystem: $data{$feat}->{subsys} \n" if ($data{$feat}->{subsys});
0306     print ":Kconfig: $data{$feat}->{kconfig} \n" if ($data{$feat}->{kconfig});
0307 
0308     my $desc = $data{$feat}->{description};
0309     $desc =~ s/^([a-z])/\U$1/;
0310     $desc =~ s/\.?\s*//;
0311     print "\n$desc.\n\n";
0312 
0313     my $com = $data{$feat}->{comments};
0314     $com =~ s/^\s+//;
0315     $com =~ s/\s+$//;
0316     if ($com) {
0317         print "Comments\n";
0318         print "--------\n\n";
0319         print "$com\n\n";
0320     }
0321 
0322     print "=" x $max_size_arch_with_header;
0323     print "  ";
0324     print "=" x $max_size_status;
0325     print "\n";
0326 
0327     printf "%-${max_size_arch}s  ", $h_arch;
0328     printf "%-${max_size_status}s", $h_status . "\n";
0329 
0330     print "=" x $max_size_arch_with_header;
0331     print "  ";
0332     print "=" x $max_size_status;
0333     print "\n";
0334 
0335     my %arch_table = %{$data{$feat}->{table}};
0336     foreach my $arch (sort keys %arch_table) {
0337         printf "%-${max_size_arch}s  ", $arch;
0338         printf "%-${max_size_status}s\n", $arch_table{$arch};
0339     }
0340 
0341     print "=" x $max_size_arch_with_header;
0342     print "  ";
0343     print "=" x $max_size_status;
0344     print "\n";
0345 }
0346 
0347 #
0348 # Output all features for all architectures
0349 #
0350 
0351 sub matrix_lines($$$) {
0352     my $desc_size = shift;
0353     my $status_size = shift;
0354     my $header = shift;
0355     my $fill;
0356     my $ln_marker;
0357 
0358     if ($header) {
0359         $ln_marker = "=";
0360     } else {
0361         $ln_marker = "-";
0362     }
0363 
0364     $fill = $ln_marker;
0365 
0366     print "+";
0367     print $fill x $max_size_name;
0368     print "+";
0369     print $fill x $desc_size;
0370     print "+";
0371     print $ln_marker x $status_size;
0372     print "+\n";
0373 }
0374 
0375 sub output_matrix {
0376     my $title = "Feature status on all architectures";
0377     my $notcompat = "Not compatible";
0378 
0379     print "=" x length($title) . "\n";
0380     print "$title\n";
0381     print "=" x length($title) . "\n\n";
0382 
0383     my $desc_title = "$h_kconfig / $h_description";
0384 
0385     my $desc_size = $max_size_kconfig + 4;
0386     if (!$description_size) {
0387         $desc_size = $max_size_description if ($max_size_description > $desc_size);
0388     } else {
0389         $desc_size = $description_size if ($description_size > $desc_size);
0390     }
0391     $desc_size = $max_description_word if ($max_description_word > $desc_size);
0392 
0393     $desc_size = length($desc_title) if (length($desc_title) > $desc_size);
0394 
0395     $max_size_status = length($notcompat) if (length($notcompat) > $max_size_status);
0396 
0397     # Ensure that the status will fit
0398     my $min_status_size = $max_size_status + $max_size_arch + 6;
0399     $status_size = $min_status_size if ($status_size < $min_status_size);
0400 
0401 
0402     my $cur_subsys = "";
0403     foreach my $name (sort {
0404                 ($data{$a}->{subsys} cmp $data{$b}->{subsys}) or
0405                 ("\L$a" cmp "\L$b")
0406                    } keys %data) {
0407 
0408         if ($cur_subsys ne $data{$name}->{subsys}) {
0409             if ($cur_subsys ne "") {
0410                 printf "\n";
0411             }
0412 
0413             $cur_subsys = $data{$name}->{subsys};
0414 
0415             my $title = "Subsystem: $cur_subsys";
0416             print "$title\n";
0417             print "=" x length($title) . "\n\n";
0418 
0419 
0420             matrix_lines($desc_size, $status_size, 0);
0421 
0422             printf "|%-${max_size_name}s", $h_name;
0423             printf "|%-${desc_size}s", $desc_title;
0424 
0425             printf "|%-${status_size}s|\n", "Status per architecture";
0426             matrix_lines($desc_size, $status_size, 1);
0427         }
0428 
0429         my %arch_table = %{$data{$name}->{table}};
0430         my $cur_status = "";
0431 
0432         my (@lines, @descs);
0433         my $line = "";
0434         foreach my $arch (sort {
0435                     ($arch_table{$b} cmp $arch_table{$a}) or
0436                     ("\L$a" cmp "\L$b")
0437                        } keys %arch_table) {
0438 
0439             my $status = $arch_table{$arch};
0440 
0441             if ($status eq "---") {
0442                 $status = $notcompat;
0443             }
0444 
0445             if ($status ne $cur_status) {
0446                 if ($line ne "") {
0447                     push @lines, $line;
0448                     $line = "";
0449                 }
0450                 $line = "- **" . $status . "**: " . $arch;
0451             } elsif (length($line) + length ($arch) + 2 < $status_size) {
0452                 $line .= ", " . $arch;
0453             } else {
0454                 push @lines, $line;
0455                 $line = "  " . $arch;
0456             }
0457             $cur_status = $status;
0458         }
0459         push @lines, $line if ($line ne "");
0460 
0461         my $description = $data{$name}->{description};
0462         while (length($description) > $desc_size) {
0463             my $d = substr $description, 0, $desc_size;
0464 
0465             # Ensure that it will end on a space
0466             # if it can't, it means that the size is too small
0467             # Instead of aborting it, let's print what we have
0468             if (!($d =~ s/^(.*)\s+.*/$1/)) {
0469                 $d = substr $d, 0, -1;
0470                 push @descs, "$d\\";
0471                 $description =~ s/^\Q$d\E//;
0472             } else {
0473                 push @descs, $d;
0474                 $description =~ s/^\Q$d\E\s+//;
0475             }
0476         }
0477         push @descs, $description;
0478 
0479         # Ensure that the full description will be printed
0480         push @lines, "" while (scalar(@lines) < 2 + scalar(@descs));
0481 
0482         my $ln = 0;
0483         for my $line(@lines) {
0484             if (!$ln) {
0485                 printf "|%-${max_size_name}s", $name;
0486                 printf "|%-${desc_size}s", "``" . $data{$name}->{kconfig} . "``";
0487             } elsif ($ln >= 2 && scalar(@descs)) {
0488                 printf "|%-${max_size_name}s", "";
0489                 printf "|%-${desc_size}s", shift @descs;
0490             } else {
0491                 printf "|%-${max_size_name}s", "";
0492                 printf "|%-${desc_size}s", "";
0493             }
0494 
0495             printf "|%-${status_size}s|\n", $line;
0496 
0497             $ln++;
0498         }
0499         matrix_lines($desc_size, $status_size, 0);
0500     }
0501 }
0502 
0503 
0504 #
0505 # Parses all feature files located at $prefix dir
0506 #
0507 find({wanted =>\&parse_feat, no_chdir => 1}, $prefix);
0508 
0509 print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug);
0510 
0511 #
0512 # Handles the command
0513 #
0514 if ($cmd eq "current") {
0515     $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/');
0516     $arch =~s/\s+$//;
0517 }
0518 
0519 if ($cmd eq "ls" or $cmd eq "list") {
0520     if (!$arch) {
0521         $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/');
0522         $arch =~s/\s+$//;
0523     }
0524 
0525     list_arch_features;
0526 
0527     exit;
0528 }
0529 
0530 if ($cmd ne "validate") {
0531     if ($arch) {
0532         output_arch_table;
0533     } elsif ($feat) {
0534         output_feature;
0535     } else {
0536         output_matrix;
0537     }
0538 }
0539 
0540 __END__
0541 
0542 =head1 NAME
0543 
0544 get_feat.pl - parse the Linux Feature files and produce a ReST book.
0545 
0546 =head1 SYNOPSIS
0547 
0548 B<get_feat.pl> [--debug] [--man] [--help] [--dir=<dir>] [--arch=<arch>]
0549            [--feature=<feature>|--feat=<feature>] <COMAND> [<ARGUMENT>]
0550 
0551 Where <COMMAND> can be:
0552 
0553 =over 8
0554 
0555 B<current>               - output table in ReST compatible ASCII format
0556                with features for this machine's architecture
0557 
0558 B<rest>                  - output table(s)  in ReST compatible ASCII format
0559                with features in ReST markup language. The output
0560                is affected by --arch or --feat/--feature flags.
0561 
0562 B<validate>              - validate the contents of the files under
0563                Documentation/features.
0564 
0565 B<ls> or B<list>         - list features for this machine's architecture,
0566                using an easier to parse format.
0567                The output is affected by --arch flag.
0568 
0569 =back
0570 
0571 =head1 OPTIONS
0572 
0573 =over 8
0574 
0575 =item B<--arch>
0576 
0577 Output features for an specific architecture, optionally filtering for
0578 a single specific feature.
0579 
0580 =item B<--feat> or B<--feature>
0581 
0582 Output features for a single specific feature.
0583 
0584 =item B<--dir>
0585 
0586 Changes the location of the Feature files. By default, it uses
0587 the Documentation/features directory.
0588 
0589 =item B<--enable-fname>
0590 
0591 Prints the file name of the feature files. This can be used in order to
0592 track dependencies during documentation build.
0593 
0594 =item B<--debug>
0595 
0596 Put the script in verbose mode, useful for debugging. Can be called multiple
0597 times, to increase verbosity.
0598 
0599 =item B<--help>
0600 
0601 Prints a brief help message and exits.
0602 
0603 =item B<--man>
0604 
0605 Prints the manual page and exits.
0606 
0607 =back
0608 
0609 =head1 DESCRIPTION
0610 
0611 Parse the Linux feature files from Documentation/features (by default),
0612 optionally producing results at ReST format.
0613 
0614 It supports output data per architecture, per feature or a
0615 feature x arch matrix.
0616 
0617 When used with B<rest> command, it will use either one of the tree formats:
0618 
0619 If neither B<--arch> or B<--feature> arguments are used, it will output a
0620 matrix with features per architecture.
0621 
0622 If B<--arch> argument is used, it will output the features availability for
0623 a given architecture.
0624 
0625 If B<--feat> argument is used, it will output the content of the feature
0626 file using ReStructured Text markup.
0627 
0628 =head1 BUGS
0629 
0630 Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
0631 
0632 =head1 COPYRIGHT
0633 
0634 Copyright (c) 2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>.
0635 
0636 License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>.
0637 
0638 This is free software: you are free to change and redistribute it.
0639 There is NO WARRANTY, to the extent permitted by law.
0640 
0641 =cut