get_feat.pl 15 KB

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