#!/usr/bin/perl -w # NAME: exefixcmake.pl # AIM: Given a single CMakeLists.txt, or a path to search for CMakeLists.txt, read in # and make sure for every 'add_executable( name ... )' there also exists a # set_target_properties( name PROPERTIES DEBUG_POSTFIX d ), put in if(WIN32) # 2017-10-01 - Review and update... # 18/04/2014 - add a -d --dry-run to show which files will be changed, and change to if(MSVC) # 21/09/2013 - attempt to exactly retain the file format # 02/06/2013 - Process ALL CMakeLists.txt in the set # 23/03/2013 - Enhance UI # if added... and if -w, backup file and write new... use strict; use warnings; use File::Basename; # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] ) use Cwd; my $os = $^O; my $perl_dir = '/home/geoff/bin'; my $PATH_SEP = '/'; my $temp_dir = '/tmp'; if ($os =~ /win/i) { $perl_dir = 'C:\GTools\perl'; $temp_dir = $perl_dir; $PATH_SEP = "\\"; } unshift(@INC, $perl_dir); require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n"; # 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); # user variables my $VERS = "0.0.3 2017-10-01"; ###my $VERS = "0.0.2 2014-04-18"; ###my $VERS = "0.0.1 2012-07-18"; my $load_log = 0; my $in_file = ''; my $verbosity = 0; my $out_file = ''; my $write_files = 0; my $add_msvc_if = 1; my $dry_run = 0; my @input_files = (); my @input_dirs = (); # ### DEBUG ### my $debug_on = 0; my $def_file = 'def_file'; ##my $def_dir = 'C:\FG\16\simgear'; my $def_dir = 'C:\FG\18\flightgear'; ### program variables my @warnings = (); my $cwd = cwd(); my $total_fixes = 0; my $total_files = 0; my $total_lines = 0; # to distinguish between debug and release lib # set( CMAKE_DEBUG_POSTFIX "d" ) my $seen_set_postfix = 0; my @set_files = (); my @fix_files = (); sub VERB1() { return $verbosity >= 1; } sub VERB2() { return $verbosity >= 2; } sub VERB5() { return $verbosity >= 5; } sub VERB9() { return $verbosity >= 9; } sub show_warnings($) { my ($val) = @_; if (@warnings) { prt( "\nGot ".scalar @warnings." WARNINGS...\n" ); foreach my $itm (@warnings) { prt("$itm\n"); } prt("\n"); } else { prt( "\nNo warnings issued.\n\n" ) if (VERB9()); } } sub pgm_exit($$) { my ($val,$msg) = @_; if (length($msg)) { $msg .= "\n" if (!($msg =~ /\n$/)); prt($msg); } show_warnings($val); close_log($outfile,$load_log); exit($val); } sub prtw($) { my ($tx) = shift; $tx =~ s/\n$//; prt("$tx\n"); push(@warnings,$tx); } sub not_complete_cmake_line($) { my $line = shift; my $len = length($line); my @brackets = (); my ($i,$ch,$inquot,$pc); $inquot = 0; $ch = ''; for ($i = 0; $i < $len; $i++) { $pc = $ch; $ch = substr($line,$i,1); if ($inquot) { $inquot = 0 if (($ch eq '"') && ($pc ne "\\")); } else { if ($ch eq '"') { $inquot = 1; } elsif ($ch eq '(') { push(@brackets,$i); } elsif ($ch eq ')') { if (@brackets) { pop @brackets; } } elsif ($ch eq '#') { if (!@brackets) { last; } } } } $len = scalar @brackets; return $len; } # split line into 'tags' separated by (, ), or space(s) # return ref array of tags sub split_cmake_line($) { my $oline = shift; my $line = trim_all($oline); my $len = length($line); my @brackets = (); my @tags = (); my ($i,$ch,$inquot,$pc,$tag); $inquot = 0; $ch = ''; $tag = ''; for ($i = 0; $i < $len; $i++) { $pc = $ch; $ch = substr($line,$i,1); if ($inquot) { $tag .= $ch; if (($ch eq '"') && ($pc ne "\\")) { $inquot = 0; push(@tags,$tag); $tag = ''; } next; } else { if ($ch eq '"') { $inquot = 1; push(@tags,$tag) if (length($tag)); $tag = $ch; next; } elsif ($ch eq '(') { if (!@brackets) { push(@tags,$tag) if (length($tag)); $tag = ''; } push(@brackets,$i); next; } elsif ($ch eq ')') { if (@brackets) { pop @brackets; } if (!@brackets) { push(@tags,$tag) if (length($tag)); $tag = ''; } next; } elsif ($ch eq '#') { if (!@brackets) { last; } } } if ($ch =~ /\s/) { push(@tags,$tag) if (length($tag)); $tag = ''; } else { $tag .= $ch; # build up a tag } } $len = scalar @brackets; return \@tags; } my %set_hash = (); my $act_line = ''; my $bgn_line = 0; my $end_line = 0; sub do_subs($); sub init_set_hash() { $set_hash{CMAKE_MODULE_PATH} = $cwd; } sub do_subs($) { my $var = shift; return $var if (!($var =~ /\$/)); return $var if (!($var =~ /\{/)); return $var if (!($var =~ /\}/)); my $len = length($var); return $var if ($len < 4); my $nvar = ''; my ($i,$ch,$i2,$nc,$cnt,$j,$tag); for ($i = 0; $i < $len; $i++) { $i2 = $i + 1; $ch = substr($var,$i,1); $nc = ($i2 < $len) ? substr($var,$i2,1) : ''; if (($ch eq '$')&&($nc eq '{')) { $cnt = 1; $tag = ''; for ($j = $i2+1; $j < $len; $j++) { #$i2 = $j + 1; $ch = substr($var,$j,1); if ($ch eq $nc) { $cnt++; } elsif ($ch eq '}') { $cnt--; if ($cnt == 0) { last; } } else { $tag .= $ch; } } if (length($tag) && ($cnt == 0)) { if (($tag =~ /\$/)&&($tag =~ /\{/)&&($tag =~ /\}/)) { $tag = do_subs($tag); } if (defined $set_hash{$tag}) { $nvar .= $set_hash{$tag}; $i = $j; } } } else { $nvar .= $ch; } } return $nvar; } sub add_to_set_hash($) { my $rta = shift; my $len = scalar @{$rta}; if ($len < 3) { prtw("WARNING: SET does NOT have 3+ components! line [".trim_all($act_line). "]\nArray = ".join(" ",@{$rta})."\n") if (VERB5()); ### return; } my ($i,$tmp,$i2); my $val = ''; if (VERB9()) { prt("$bgn_line:$end_line: [$act_line]\n"); $tmp = ''; for ($i = 0; $i < $len; $i++) { $i2 = $i + 1; if ($i == 1) { $tmp .= '('; } else { $tmp .= ' ' if (length($tmp)); } $tmp .= ${$rta}[$i]; if ($i2 == $len) { $tmp .= ')'; } } prt("$bgn_line:$end_line: [$tmp] recon\n"); } my $var = ${$rta}[1]; $var = do_subs($var); ### $set_hash{$var} = ''; for ($i = 2; $i < $len; $i++) { $tmp = do_subs(${$rta}[$i]); $val .= ';' if (length($val)); $val .= $tmp; } if (length($val) == 0) { if (defined $set_hash{$var}) { delete $set_hash{$var}; prt("$bgn_line:$end_line: Removed $var = no value\n") if (VERB9()); } } else { $set_hash{$var} = $val; prt("$bgn_line:$end_line: SET($var $val)\n") if (VERB9()); } } sub process_in_file($) { my ($inf) = @_; if (! open INF, "<$inf") { pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); } my @lines = ; close INF; my $lncnt = scalar @lines; my ($i,$line,$inc,$lnn,$tline,$len,$tmp,$ttmp,$rta,$test,$chg,$cline); my ($cnt,$head,$name,$msg); $total_files++; $total_lines += $lncnt; $head = "\n# $total_files: Processing $lncnt lines, from [$inf]...\n"; if (VERB9()) { prt($head); $head = ''; } $lnn = 0; my @nlines = (); my @olines = (); # 20130921 - try to keep ORIGINAL format of lines my $total_chgs = 0; my $exe_cnt = 0; my $lib_cnt = 0; my $tp_cnt = 0; my $is_exe = 0; my $is_lib = 0; my $is_tp = 0; my @exe_lines = (); my @lib_lines = (); my @tp_lines = (); for ($i = 0; $i < $lncnt; $i++) { $lnn = $i + 1; $bgn_line = $lnn; $line = $lines[$i]; chomp $line; $tline = trim_all($line); $len = length($tline); if ($len == 0) { push(@nlines,""); next; } if ($line =~ /^\s*\#/) { # skip comments push(@nlines,$line); next; } push(@olines,$line); while (not_complete_cmake_line($line) && ($lnn < $lncnt)) { $i++; $lnn = $i + 1; $tmp = $lines[$i]; chomp $tmp; push(@olines,$tmp); $ttmp = trim_all($tmp); $len = length($ttmp); next if ($len == 0); $line .= ' '.$tmp; $tline .= ' '.$ttmp; } $act_line = $line; $end_line = $lnn; $rta = split_cmake_line($line); $len = scalar @{$rta}; $cnt = 0; $ttmp = ''; $is_exe = 0; $is_lib = 0; $is_tp = 0; foreach $tmp (@{$rta}) { $cnt++; if ($cnt == 1) { $ttmp .= "$tmp("; if ($tmp =~ /^\s*add_executable/i) { $exe_cnt++; $is_exe = 1; push(@exe_lines,$line); } elsif ($tmp =~ /^\s*add_library/i) { $lib_cnt++; $is_lib = 1; push(@lib_lines,$line); } elsif ($tmp =~ /^\s*set_target_properties/i) { $tp_cnt++; $is_tp = 1; push(@tp_lines,$line); } elsif ($tmp =~ /^set$/i) { add_to_set_hash($rta); if (($len > 2)&&(${$rta}[1] eq 'CMAKE_DEBUG_POSTFIX')) { # set( CMAKE_DEBUG_POSTFIX "d" ) $seen_set_postfix++; push(@set_files,$inf); } } } else { $ttmp .= "$tmp "; if (($cnt == 2) && ($is_exe || $is_lib || $is_tp)) { $name = $tmp; } } } $ttmp =~ s/\s+$//; $ttmp .= ")"; ### if ($is_exe || $is_lib || $is_tp) { if ($is_exe || $is_tp) { prt($head) if (length($head) && VERB1()); $head = ''; prt("$ttmp # $name\n") if (VERB1()); } #push(@nlines,$line); push(@nlines,@olines); @olines = (); } ### if ($exe_cnt || $lib_cnt) { if ($exe_cnt) { my ($rta2,$line2,$name2,$fnd,$len2,$prop,$fix_cnt); $fix_cnt = 0; my @fix_lines = (); foreach $line (@exe_lines) { $rta = split_cmake_line($line); $name = ${$rta}[1]; $fnd = 0; $prop = ''; foreach $line2 (@tp_lines) { $rta2 = split_cmake_line($line2); $len2 = scalar @{$rta2}; if (($len2 > 4)&&(${$rta2}[2] eq 'PROPERTIES')&&(${$rta2}[3] eq 'DEBUG_POSTFIX')) { $name2 = ${$rta2}[1]; $prop = ${$rta2}[4]; if ($name eq $name2) { $fnd = 1; last; } } } if ($fnd) { prt("# Exe $name already has DEBUG_POSTFIX $prop property\n") if (VERB1()); } else { push(@fix_lines,$line); $fix_cnt++; } } if ($fix_cnt) { # ------- $total_fixes++; push(@fix_files,$inf); $msg = ''; if ($add_msvc_if) { $tmp = "if (MSVC)"; push(@nlines,$tmp); $msg = "$tmp\n"; } foreach $line (@fix_lines) { $rta = split_cmake_line($line); $name = ${$rta}[1]; # set_target_properties( name PROPERTIES DEBUG_POSTFIX d ) $tmp = " set_target_properties( $name PROPERTIES DEBUG_POSTFIX d )"; push(@nlines,$tmp); $msg .= "$tmp\n"; } if ($add_msvc_if) { $tmp = "endif ()"; push(@nlines,$tmp); $msg .= "$tmp\n"; } if ($dry_run) { prt("Would adjust [$inf] with -\n"); prt($msg); if (!$write_files) { prt("Also write is OFF. Use -w to write changes to file [$inf].\n"); } } else { prt($msg) if (VERB1()); if ($write_files) { rename_2_old_bak($inf); $line = join("\n",@nlines); $line .= "\n"; write2file($line,$inf); prt("Written amended to [$inf]\n"); } else { prt("Write is OFF. Use -w to write changes to file [$inf].\n"); } } # ------- } prt("# Found $exe_cnt exe, $lib_cnt libs, and $tp_cnt props, $fix_cnt fixes in [$inf]\n"); } ### } #if (length($out_file)) { # write2file($line,$out_file); # prt("Results written to [$out_file], with $total_chgs changes.\n"); #} } sub add_cmakelists($); sub add_cmakelists($) { my $dir = shift; my @dirs = (); if (!opendir(DIR,$dir)) { prtw("WARNING: Unable to open directory [$dir]!\n"); return; } my @files = readdir(DIR); closedir(DIR); my ($file,$ff); ut_fix_directory(\$dir); foreach $file (@files) { next if ($file eq '.'); next if ($file eq '..'); $ff = $dir.$file; if (-d $ff) { push(@dirs,$ff); } elsif (-f $ff) { if ($file =~ /^CMakeLists\.txt$/i) { push(@input_files,$ff); } } else { prtw("WARNING: What is THIS? [$ff]\n"); } } foreach $dir (@dirs) { add_cmakelists($dir); } } sub process_inputs() { my ($item); foreach $item (@input_dirs) { $set_hash{PROJECT_SOURCE_DIR} = $item; add_cmakelists($item); } foreach $item (@input_files) { process_in_file($item); } prt("\nFound $total_fixes fixes in scan of $total_files files, $total_lines lines\n"); foreach $item (@fix_files) { prt(" $item\n"); } if ($seen_set_postfix) { $item = scalar @set_files; prt("For LIBRARIES, seen set CMAKE_DEBUG_POSTFIX ($seen_set_postfix) in $item file(s)\n"); foreach $item (@set_files) { prt(" $item\n"); } } else { prt("\nWARNING: set CMAKE_DEBUG_POSTFIX NOT SEEN\n"); } prt("\n"); } ######################################### ### MAIN ### parse_args(@ARGV); init_set_hash(); process_inputs(); pgm_exit(0,""); ######################################## sub need_arg { my ($arg,@av) = @_; pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av); } sub parse_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/i)||($sarg eq '?')) { give_help(); pgm_exit(0,"Help exit(0)"); } elsif ($sarg =~ /^v/) { if ($sarg =~ /^v.*(\d+)$/) { $verbosity = $1; } else { while ($sarg =~ /^v/) { $verbosity++; $sarg = substr($sarg,1); } } prt("Verbosity = $verbosity\n") if (VERB1()); } elsif ($sarg =~ /^l/) { if ($sarg =~ /^ll/) { $load_log = 2; } else { $load_log = 1; } prt("Set to load log at end. ($load_log)\n") if (VERB1()); # } elsif ($sarg =~ /^o/) { # need_arg(@av); # shift @av; # $sarg = $av[0]; # $out_file = $sarg; # prt("Set out file to [$out_file].\n") if (VERB1()); } elsif ($sarg =~ /^w/) { $write_files = 1; prt("Set to write changed files after backup. ($write_files)\n") if (VERB1()); } elsif ($sarg =~ /^d/) { $dry_run = 1; prt("Set to show changed files. ($dry_run)\n") if (VERB1()); } else { pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n"); } } else { if (-f $arg) { $in_file = $arg; prt("Added input file [$in_file]\n") if (VERB1()); push(@input_files,$in_file); } elsif (-d $arg) { prt("Added input directory [$arg]\n") if (VERB1()); push(@input_dirs,$arg); } else { pgm_exit(1,"ERROR: [$arg] is NOT file or directory!\n"); } } shift @av; } if ($debug_on) { prtw("WARNING: DEBUG is ON\n"); if ((length($in_file) == 0)) { if (-f $def_file) { $in_file = $def_file; prt("Set DEFAULT input to [$in_file]\n"); push(@input_files,$in_file); } } if (!@input_dirs) { if (-d $def_dir) { prt("Set DEFAULT input to [$def_dir]\n"); push(@input_dirs,$def_dir); $load_log = 3; } } } if (!@input_files && !@input_dirs) { pgm_exit(1,"ERROR: No input files or directories found in command!\n"); } } sub give_help { prt("$pgmname: version $VERS\n"); prt("Usage: $pgmname [options] (in-dir | in-file)\n"); prt("Options:\n"); prt(" --help (-h or -?) = This help, and exit 0.\n"); prt(" --verb[n] (-v) = Bump [or set] verbosity. def=$verbosity\n"); prt(" --load (-l) = Load LOG at end. ($outfile)\n"); ###prt(" --out (-o) = Write output to this file.\n"); prt(" --write (-w) = Write changes to files after backup.\n"); prt(" --dry-run (-d) = Only show which files would be changed.\n"); prt("\n"); prt("AIM: If given a directory, will search for ALL CMakeLists.txt in folder,\n"); prt("else if given a CMakeLists.txt file, or set of files, only process it/them\n"); prt("for change.\n"); prt("The change is to search for all 'add_executable(name srcs)' and ensure there is\n"); prt("a set_target_properties( name PROPERTIES DEBUG_POSTFIX d ), put in if(MSVC)\n"); } # eof - exefixcmake.pl