#!/usr/bin/perl -w #< cmakeopts.pl - Given a directory, search for CMakeLists.txt files, recursively, # and show each option(NAME "test" ON) item # 19/05/2013 - Add -o for output file, and put the ON/OFF first # 20/07/2012 - If no file given, but there is a CMakeLists.txt, then use that # 01/04/2012 - If given a file, also search for and follow 'add_subdirectory(...)' # 2012-01-29 - Convert to also run in Windows # 2012-01-12 - Change to lib_utils.pl, and add a 'purpose' statement. use strict; use warnings; use File::Basename; # split path ($name,$dir) = fileparse($ff); or ($nm,$dir,$ext) = fileparse($fil, qr/\.[^.]*/); use Cwd; use File::stat; my $os = $^O; my $perl_dir = '/home/geoff/bin'; my $PATH_SEP = '/'; my $temp_dir = '/tmp'; my $is_os_win = ($os =~ /win/i) ? 1 : 0; if ($is_os_win) { $perl_dir = 'C:\GTools\perl'; $temp_dir = $perl_dir; $PATH_SEP = "\\"; } unshift(@INC, $perl_dir); require "lib_utils.pl" or die "ERROR: Unable to load 'lib_utils.pl"; # log file stuff our ($LF); my $pgmname = $0; if ($pgmname =~ /(\/|\\)/) { my @tmpsp = split(/(\/|\\)/,$pgmname); $pgmname = $tmpsp[-1]; } my $outfile = $temp_dir.$PATH_SEP."temp.".$pgmname.".txt"; open_log($outfile); my $pgm_vers = "0.0.3 2012-04-01"; # my $pgm_vers = "0.0.2 2012-01-29"; # $pgm_vers = "0.0.1 2011-12-15"; my @in_files = (); my @warnings = (); my $load_log = 0; my $verbosity = 0; my $out_file = ''; my $final_msg = ''; my $total_dirs = 0; my $total_files = 0; my $total_lines = 0; my $total_bytes = 0; my $total_options = 0; my %options_found = (); # debug my $dbg01 = 0; # show each directory processed... my $dbg02 = 0; # show sub-directory processed... my $debug_on = 0; my $def_file = 'C:\FG\30\flightgear'; sub VERB1() { return $verbosity >= 1; } sub VERB2() { return $verbosity >= 2; } sub VERB5() { return $verbosity >= 5; } sub VERB9() { return $verbosity >= 9; } sub process_dir($$$); sub prtw($) { my ($tx) = shift; $tx =~ s/\n$//; prt("$tx\n"); push(@warnings,$tx); } sub show_warnings() { if (@warnings) { prt( "Got ".scalar @warnings." WARNINGS..." ); if (!VERB5()) { prt(" Use -v5 to list..."); } prt("\n"); if (VERB5()) { foreach my $itm (@warnings) { prt("$itm\n"); } prt("\n"); } } else { #prt( "\nNo warnings issued.\n\n" ); } } sub pgm_exit($$) { my ($val,$msg) = @_; show_warnings(); if (length($msg) > 1) { $msg .= "\n" if (!($msg =~ /\n$/)); prt($msg); } prt("$final_msg\n") if (length($final_msg)); close_log($outfile,$load_log); exit($val); } sub my_file_type($) { my ($file) = shift; return 1 if ($file eq 'CMakeLists.txt'); return 0; } sub process_dir($$$) { my ($dir,$ra,$lev) = @_; my @dirs = (); my ($file,$ff,$cnt); my ($itm); if (opendir( DIR, $dir )) { $total_dirs++; prt("Reading [$dir]...\n") if (VERB9()); my @files = readdir(DIR); closedir(DIR); $dir .= '/' if !($dir =~ /\/$/); foreach $file (@files) { next if ($file eq '.'); next if ($file eq '..'); $ff = $dir.$file; if ( -d $ff ) { push(@dirs,$ff); } elsif ( -f $ff ) { $total_files++; if (my_file_type($file)) { push(@{$ra},$ff); } } else { # a link or .... } } } else { prtw("WARNING: Unable to open folder [$dir]... $!...\n"); } if (@dirs) { $cnt = scalar @dirs; prt( "[$dbg02] $lev: Found $cnt subs in [$dir]...\n" ) if ($dbg02); foreach $itm (@dirs) { process_dir($itm,$ra,($lev + 1)); } } if ($lev == 0) { $cnt = scalar @{$ra}; prt("Found $cnt file items of type CMakeLists.txt, in scan of [$dir]\n"); if (VERB9()) { $cnt = 0; foreach $itm (@{$ra}) { $cnt++; prt("$cnt $itm\n"); } } } return $ra; } sub full_opts_line($) { my ($line) = shift; my $len = length($line); my $inquot = 0; my $inbrac = 0; my ($i,$ch,$qc); for ($i = 0; $i < $len; $i++) { $ch = substr($line,$i,1); if ($inbrac) { if ($inquot) { $inquot = 0 if ($ch eq $qc); } elsif ($ch eq '"') { $inquot = 1; $qc = $ch; } elsif ($ch eq ')') { $inbrac--; if ($inbrac == 0) { return 1; } } } else { if ($inquot) { $inquot = 0 if ($ch eq $qc); } elsif ($ch eq '"') { $inquot = 1; $qc = $ch; } elsif ($ch eq '(') { $inbrac++; } } } return 0; } sub full_fe_line($) { my $line = shift; return full_opts_line($line); } sub split_opts_line($) { my ($line) = shift; my $len = length($line); my $inquot = 0; my $inbrac = 0; my ($i,$ch,$qc,$command,$hadsp); my @arr = (); # got nothing so far $command = ''; # start the collection $hadsp = 0; # had no space yet for ($i = 0; $i < $len; $i++) { $ch = substr($line,$i,1); if ($ch eq '(') { last; # found first '(' } elsif ( !($ch =~ /\s/) ) { $command .= $ch; } else { # have a SPACE if (length($command)) { # already had some non-space - trailing space # should check if the next sig char is the '(' } # else have not yet started command, # so ignore this beginning space } } if (($ch ne '(')||(length($command)==0)) { prtw("WARNING: Option line did not conform! [$line]\n"); return \@arr; } push(@arr,$command); # push first item 'OPTION' or 'option' $command = ''; # collect space spearated items, skipping spaces in quoted strings for (; $i < $len; $i++) { $ch = substr($line,$i,1); if ($inbrac) { if ($inquot) { $command .= $ch; $inquot = 0 if ($ch eq $qc); } else { if ($ch =~ /\s/) { push(@arr,$command) if (length($command)); $command = ''; } else { # not a space if ($ch eq ')') { $inbrac--; if ($inbrac == 0) { last; } } else { # not end bracket $command .= $ch; if ($ch eq '"') { $inquot = 1; $qc = $ch; } } } } } else { if ($inquot) { $inquot = 0 if ($ch eq $qc); } elsif ($ch eq '"') { $inquot = 1; $qc = $ch; } elsif ($ch eq '(') { $inbrac++; } } } push(@arr,$command) if (length($command)); return \@arr; } sub show_cmake_options($) { my $rha = shift; my ($in,$roptions); my ($i,$ra,$len,$option,$message,$default,$cnt,$msg); my $mino = 0; my $minm = 0; my $mind = 0; my $dnhead = 0; $msg = ''; my $out = ''; foreach $in (keys %{$rha}) { $roptions = ${$rha}{$in}; $cnt = scalar @{$roptions}; $total_options += $cnt; $mino = 0; $minm = 0; if ($cnt) { prt("$cnt OPTIONS found in [$in]\n"); $out .= "$cnt OPTIONS found in [$in]\n"; # get LENGTH for ($i = 0; $i < $cnt; $i++) { # 0 1 2 # push(@options, [$option, $message, $default]); $option = ${$roptions}[$i][0]; $message = ${$roptions}[$i][1]; $default = ${$roptions}[$i][2]; $len = length($option); $mino = $len if ($len > $mino); $len = length($message); $minm = $len if ($len > $minm); $len = length($default); $mind = $len if ($len > $mind); } if (!$dnhead) { $option = "Option"; $default = "DEF"; $message = 'Description'; $option .= ' ' while (length($option) < $mino); $default .= ' ' while (length($default) < $mind); prt("$option $default $message\n"); $out .= "$option $default $message\n"; $dnhead = 1; } for ($i = 0; $i < $cnt; $i++) { # 0 1 2 # push(@options, [$option, $message, $default]); $option = ${$roptions}[$i][0]; $message = ${$roptions}[$i][1]; $default = ${$roptions}[$i][2]; $option .= ' ' while (length($option) < $mino); $default .= ' ' while (length($default) < $mind); ###$message .= ' ' while (length($message) < $minm); prt("$option $default $message\n"); $out .= "$option $default $message\n"; } } else { $msg .= "Found NO options in [$in]\n"; # if (VERB9()); } } prt($msg) if (length($msg) && VERB2()); if (length($out_file)) { rename_2_old_bak($out_file); # NEVER overwrite any existing file!!! write2file($out,$out_file); $final_msg = "Option list written to [$out_file]"; } } # escape ^ $ . | { } [ ] ( ) * + ? \ sub escape_regex($) { my $txt = shift; my $ntxt = ''; my $len = length($txt); my ($i,$ch); for ($i = 0; $i < $len; $i++) { $ch = substr($txt,$i,1); if (($ch eq '^')||($ch eq '$')||($ch eq '.')||($ch eq '|')||($ch eq '{')||($ch eq '}')) { $ntxt .= '\\'; } elsif (($ch eq '\\')||($ch eq '/')||($ch eq '(')||($ch eq ')')||($ch eq '[')||($ch eq ']')) { $ntxt .= '\\'; } elsif (($ch eq '*')||($ch eq '+')||($ch eq '?')) { $ntxt .= '\\'; } $ntxt .= $ch; } return $ntxt; } # processing a CMakeLists.txt sub process_cmake_lines($$$); sub process_cmake_lines($$$) { my ($in,$rlines,$rha) = @_; my @options = (); my ($cnt,$lnn,$i,$i2,$line,$tline,$tmp); my ($ra,$len,$option,$message,$default,$subd,$ff); my ($feline,$fevar,$febgn,@arr); my $mino = 0; my $minm = 0; my ($name,$dir) = fileparse($in); if ($dir =~ /^\.(\\|\/){1}$/) { $dir = ''; } else { $dir .= $PATH_SEP if ( !($dir =~ /(\\|\/)$/) ); } $cnt = scalar @{$rlines}; $in =~ s/^\.(\\|\/)//; $lnn = sprintf("%5d", $cnt); $in .= ' ' while (length($in) < 8+1+3); prt("Got $lnn lines, from [$in] to process...\n") if (VERB9()); my @sub_dirs = (); for ($i = 0; $i < $cnt; $i++) { $total_lines++; $i2 = $i + 1; $line = ${$rlines}[$i]; $total_bytes += length($line); chomp $line; $tline = trim_all($line); if ($tline =~ /^option\s*\(/i) { # seek OPTION(...) line while (($i2 < $cnt)&&(!full_opts_line($tline))) { $total_lines++; $i++; $i2 = $i + 1; $tmp = ${$rlines}[$i]; $total_bytes += length($tmp); $tline .= ' '; $tline .= trim_all($tmp); } prt("$tline\n") if (VERB9()); $ra = split_opts_line($tline); $len = scalar @{$ra}; if ($len >= 2) { $tmp = ${$ra}[0]; $option = ${$ra}[1]; $message = 'NO MESSAGE'; if ($len > 2) { $message = ${$ra}[2]; } $default = 'OFF'; if ($len > 3) { $default = ${$ra}[3]; } push(@options, [$option, $message, $default]); if (VERB9()) { prt("$option $message $default\n"); #foreach $tmp (@{$ra}) { # prt("$tmp "); #} #prt("\n"); } } else { prtw("$pgmname:WARNING: Line $i2 did not SPLIT correctly! [$tline] file $in\n"); } } elsif ($tline =~ /^foreach\s*\(/i) { $febgn = $tline; $tmp = $tline; $tmp =~ s/^foreach\s*\(\s*//i; $fevar = $tmp; prt("In [$in] got 'foreach' [$fevar]\n") if (VERB9()); while (($i2 < $cnt)&&(!full_fe_line($tline))) { $total_lines++; $i++; $i2 = $i + 1; $tmp = ${$rlines}[$i]; $total_bytes += length($tmp); $tmp = trim_all($tmp); $tline .= ' ' if (length($tline) && length($tmp)); $tline .= $tmp; } $tmp = escape_regex($febgn); $tline =~ s/$tmp//; $tline =~ s/\)\s*$//; $feline = trim_all($tline); prt("item set [$feline]\n") if (VERB9()); $tline = ''; while ($i2 < $cnt) { $total_lines++; $i++; $i2 = $i + 1; $tmp = ${$rlines}[$i]; $total_bytes += length($tmp); $tmp = trim_all($tmp); last if ($tmp =~ /^endforeach/i); $tline .= ' ' if (length($tline)); $tline .= $tmp; } prt("action: [$tline]\n") if (VERB9()); $tmp = escape_regex($fevar); if ($tline =~ /^\s*add_subdirectory\s*\(\s*\$\{\s*$tmp\s*\}\s*\)/) { @arr = split(/\s+/,$feline); $tmp = scalar @arr; prt("Need to process the set of $tmp items as subdirectories...!\n") if (VERB5()); foreach $tmp (@arr) { $ff = $dir.$tmp; if (-d $ff) { $ff .= $PATH_SEP."CMakeLists.txt"; if (-f $ff) { prt("Found added item [$ff]\n") if (VERB5()); push(@sub_dirs,$ff); } else { prtw("WARNING: NOT found [$ff]\n"); } } else { prtw("WARNING: NOT found sub-directory [$ff]\n"); } } } } elsif ($tline =~ /^add_subdirectory\s*\((.+)\)\s*$/i) { $subd = trim_all($1); prt("$i2: [$line] sub [$subd]\n") if (VERB9()); $ff = $dir.$subd; if (-d $ff) { $ff .= $PATH_SEP."CMakeLists.txt"; if (-f $ff) { prt("Found added item [$ff]\n") if (VERB5()); push(@sub_dirs,$ff); } else { prtw("WARNING: NOT found [$ff]\n"); } } else { prtw("WARNING: NOT found sub-directory [$ff]\n"); } } } ${$rha}{$in} = \@options; foreach $in (@sub_dirs) { if (open FIL, "<$in") { my @lines = ; close FIL; process_cmake_lines($in,\@lines,$rha); } } } sub process_file($) { my ($in) = @_; my ($name,$dir) = fileparse($in); if (!my_file_type($name)) { return; } if (open FIL, "<$in") { my @lines = ; close FIL; process_cmake_lines($in,\@lines,\%options_found); } else { prtw("WARNING: Unable to open file [$in]!\n"); } } sub mycmp_decend { return -1 if ( ${$a}[0] > ${$b}[0] ); return 1 if ( ${$a}[0] < ${$b}[0] ); return 0; } sub process_input() { my ($in); my @files = (); foreach $in (@in_files) { if (-f $in) { process_file($in); } elsif (-d $in) { process_dir($in,\@files,0); } else { pgm_exit(1,"ERROR: Input [$in] is NOT file or directory!\n"); } } foreach $in (@files) { process_file($in); } } ######################################### ###### MAIN ###### process_args(@ARGV); process_input(); show_cmake_options(\%options_found); prt("$total_dirs dirs, $total_files files, $total_lines lines, $total_bytes bytes, for $total_options options.\n"); pgm_exit(0,""); #################################### sub need_arg { my ($arg,@av) = @_; pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av); } sub process_args { my (@av) = @_; my ($arg,$sarg); while (@av) { $arg = $av[0]; if ($arg =~ /^-/) { $sarg = substr($arg,1); $sarg = substr($sarg,1) while ($sarg =~ /^-/); if (($sarg =~ /^h/) || ($sarg eq '?')) { give_help(); pgm_exit(0,"Help exit 0"); } elsif ($sarg =~ /^l/) { $load_log = 1; prt("Set to load log at end.\n"); } elsif ($sarg =~ /^o/) { need_arg(@av); shift @av; $sarg = $av[0]; $out_file = $sarg; prt("Output options to [$out_file].\n"); } elsif ($sarg =~ /^v/) { if ($sarg =~ /^v.*(\d+)$/) { $verbosity = $1; } else { while ($sarg =~ /^v/) { $verbosity++; $sarg = substr($sarg,1); } } prt("Set verbosity to $verbosity.\n") if (VERB1()); } else { pgm_exit(1,"$pgmname:ERROR: Unknown option [$arg]! Try -?\n"); } } else { push(@in_files,$arg); prt("Added input [$arg]\n"); } shift @av; } if (!@in_files && $debug_on) { push(@in_files,$def_file); prt("Added DEFAULT input [$def_file]\n"); } if (!@in_files && (-f "CMakeLists.txt")) { push(@in_files,"CMakeLists.txt"); prt("Added local input [CMakeLists.txt]\n"); } if ( ! @in_files ) { pgm_exit(1,"$pgmname:ERROR: No input found in command!\n"); } } sub give_help { prt("$pgmname version $pgm_vers\n"); prt("Usage: [options] input\n"); prt("Options:\n"); prt(" --help (-h,-?) = This help and exit.\n"); prt(" --load (-l) = Load log at end.\n"); prt(" --out file (-o) = Write options list to this file.\n"); prt(" --verb[Num] (-v) = Bump [or set] verbosity. (def=$verbosity)\n"); prt("Purpose: Given a directory, search for CMakeLists.txt files, recursively,\n"); prt("and show each option(NAME \"test\" ON) item found.\n"); } # eof - cmakeopts.pl