#!/usr/bin/perl # This file is part of MKF. # # MKF is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # MKF is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with MKF; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Copyright (c) 2004 Alexandre Becoulet #use strict; my $mkf_file_gc = "mkf.info"; ###################################################### # temporary files # my @temp_files; my $tmp_dir = "/tmp"; sub get_temp_file { my ($ext_p) = @_; my $fname; do { my $random = int(rand(10000)); $fname = "$tmp_dir/configure_".$random."_".$ext_p; } while (-f $fname); push (@temp_files, $fname); return $fname; } sub clear_temp_files { unlink($_) foreach (@temp_files); } ###################################################### # Display functions # sub error { $_ = @_[0]; # s/'/'\033[01;31m/g; # s/`/\033[01;36m`/g; print "[ Error ] $_\n"; # print "[ Error ] \033[01;31m$_\033[m\n"; clear_temp_files; exit -1; } sub warning { $_ = @_[0]; # s/'/'\033[01;93m/g; # s/`/\033[01;36m`/g; print "[Warning] $_\n"; # print "[Warning] \033[01;93m$_\033[m\n"; } ###################################################### # strings replace functions # sub replace { my ($str_p, $value_p, $keyword_p) = @_; $str_p =~ s/{$keyword_p}/$value_p/g; return $str_p; } sub replace_vars { my ($str_p, $vars_p, $keyword_p) = @_; $str_p =~ s/{$keyword_p,([^\}]+)}/$$vars_p{$1}/g; return $str_p; } sub replace_vars_list { my ($str_p, $vars_p, $keyword_p) = @_; while ($str_p =~ /{$keyword_p,([^\,]*),([^\}]+)}/) { my $var = $2; my $pre = $1; my $list; $list = "$list $pre$_" foreach (split(/ +/, $$vars_p{$var})); $str_p =~ s/{$keyword_p,$pre,$var}/$list/; } return $str_p; } sub replace_list { my ($str_p, $list, $keyword_p) = @_; return $str_p if (! $list); while ($str_p =~ /{$keyword_p(!?),([^\},]+),?([^\}]*)}/) { my $val; my $strip = $1; my $match_p = $2; my $prefix = $3; foreach (@$list) { if (/^$match_p$/) { my $item = $_; $item =~ s/\..*$// if ($strip); $val .= " ".$prefix.$item; } } $str_p =~ s/{$keyword_p$strip,[^\}]+}/$val/; } return $str_p; } sub replace_temp_file { my ($str_p, $keyword_p) = @_; while ($str_p =~ /{$keyword_p,([^\,]+),([^\}]+)}/) { my $id = $1; my $ext = $2; my $fname = get_temp_file ($ext); $str_p =~ s/{$keyword_p,$id,$ext}/$fname/g; } return $str_p; } ###################################################### # Mkf file commands functions # sub cmd_end_if { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: no parameter expected for `@$params_p[0]'" if (@$params_p != 1); my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; error "line $line_num: no previous _if command to end" if (! $if_depth); $$state_p{if_depth} = $if_depth - 1; } sub cmd_else { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: no parameter expected for `@$params_p[0]'" if (@$params_p != 1); my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; error "line $line_num: no previous _if command" if (! $if_depth); @{$if_bool}[$if_depth] = @{$if_bool}[$if_depth - 1] && ! @{$if_bool}[$if_depth]; } sub cmd_if_exec_ok { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: more parameters expected for `@$params_p[0]'" if (@$params_p < 2); my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; my $cmd = replace_temp_file(@$params_p[1], "TEMP"); @{$if_bool}[$if_depth + 1] = @{$if_bool}[$if_depth] && ! system($cmd . " > /dev/null 2>&1"); $$state_p{if_depth} = $if_depth + 1; } sub cmd_if_file_exists { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: one parameters expected for `@$params_p[0]'" if (@$params_p != 2); my $file = @$params_p[1]; my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; @{$if_bool}[$if_depth + 1] = @{$if_bool}[$if_depth] && -e $file; $$state_p{if_depth} = $if_depth + 1; } sub cmd_if_in_path { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: one parameters expected for `@$params_p[0]'" if (@$params_p != 2); my $exec_file = @$params_p[1]; my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; @{$if_bool}[$if_depth + 1] = 0; if (@{$if_bool}[$if_depth]) { if ($exec_file =~ /\//) { @{$if_bool}[$if_depth + 1] = -x $exec_file; } else { foreach (split(/:/, $ENV{"PATH"})) { if (-x "$_/$exec_file") { @{$if_bool}[$if_depth + 1] = 1; last; } } } } $$state_p{if_depth} = $if_depth + 1; } sub cmd_if_stdout_match { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p != 3); my $str = @$params_p[1]; my $exec_cmd = @$params_p[2]; my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; @{$if_bool}[$if_depth + 1] = @{$if_bool}[$if_depth] && (`$exec_cmd 2>&1` =~ /$str/); $$state_p{if_depth} = $if_depth + 1; } sub cmd_if_var_eq { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p < 2); my $vars = $$state_p{vars}; my $var_name = @$params_p[1]; my $var_value = @$params_p[2]; my $if_depth = $$state_p{if_depth}; my $if_bool = $$state_p{if_bool}; @{$if_bool}[$if_depth + 1] = @{$if_bool}[$if_depth] && (${$vars}{$var_name} eq $var_value); $$state_p{if_depth} = $if_depth + 1; } sub cmd_command_add { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: three parameters expected for `@$params_p[0]'" if (@$params_p < 4); my $oper = "@$params_p[1]_@$params_p[2]"; my $jobs = $$state_p{jobs}; $$jobs{$oper} = [] if (! exists $$jobs{$oper}); my $jobs_list = $$jobs{$oper}; push (@$jobs_list, join (" ", @$params_p[3..@$params_p - 1])); } sub cmd_clean_add { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: three parameters expected for `@$params_p[0]'" if (@$params_p < 4); my $oper = "@$params_p[1]_@$params_p[2]"; my $torm = $$state_p{torm}; $$torm{$oper} = [] if (! exists $$torm{$oper}); my $torm_list = $$torm{$oper}; foreach my $item (split(/ +/, @$params_p[3])) { push (@$torm_list, $item); } } # parse C like source files to get included files sub auto_dep_c { my ($dir_p, $deps_p, $c_src_p, $inc_dirs) = @_; if (! open (C_SRC, "$dir_p/$c_src_p")) { warning "can't open `$c_src_p' c source file"; return; } foreach my $c_line () { # find #include "" lines if ($c_line =~ /^[ \t]*\#[ \t]*include[ \t]*\"(.*)\"/) { my $inc_file = $1; # find included file in include directories foreach my $inc_dir ((split(/ +/, $inc_dirs), ".")) { my $abs_path = $inc_dir =~ /^\// ? $inc_dir : "$dir_p/$inc_dir"; if (! -d $abs_path) { warning "can't find `$abs_path' include directory"; next; } next if (! -f "$abs_path/$inc_file"); my $dep_hdr = "$inc_dir/$inc_file"; $dep_hdr =~ s/^.\///; push(@$deps_p, $dep_hdr); } } } } # dummy function, nothing to parse in file sub auto_dep_none { } my %auto_src_cmd = ( "c_src" => \&auto_dep_c, "none" => \&auto_dep_none, ); sub cmd_auto_source_dep { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: three parameters expected for `@$params_p[0]'" if (@$params_p != 4); my $deps = $$state_p{deps}; # hash table for all target/deps of current Makefile my $src_type = @$params_p[1]; my $ext = @$params_p[2]; my $ext_out = @$params_p[3]; foreach my $c_src (<$dir_p/*.$ext>) { $c_src = $1 if ($c_src =~ /.*\/(.*)/); my $target = $c_src; $target =~ s/\.$ext$/.$ext_out/g; next if (${$deps}{$target}); ${$deps}{$target} = []; push(@{${$deps}{$target}}, $c_src); # get pointer on function for source file parse if (my $func_ptr = $auto_src_cmd{$src_type}) { # call command function $func_ptr -> ($dir_p, ${$deps}{$target}, $c_src, ${$$state_p{vars}}{"inc_dirs"}); } else { # error if unknow function error "line $line_num: unknown source file type `$src_type'"; } } } sub cmd_auto_dep { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p != 3); my $deps = $$state_p{deps}; # hash table for all target/deps of current Makefile my $ext = @$params_p[1]; my $ext_out = @$params_p[2]; foreach my $dep (keys(%$deps)) { # match file name extention in existing target next if (not ($dep =~ /[^.]*\.(.*)$/)); next if ($1 ne $ext); # change extention to $ext_out my $target = $dep; $target =~ s/\.$ext$/.$ext_out/g; # skip if target already exist ${$deps}{$target} = []; push(@{${$deps}{$dep}}, $target); } } sub cmd_target_dep { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p != 3); my $deps = $$state_p{deps}; # hash table for all target/deps of current Makefile my $target = @$params_p[1]; # target name my @target_deps = split(/ +/, @$params_p[2]); # target dependencies name list # error "line $line_num: redefinition of `$target' target dependencies" # if (${$deps}{$target}); foreach (@target_deps) { ${$deps}{$_} = [] if (! exists ${$deps}{$_}); } push(@{${$deps}{$target}}, @target_deps); } sub cmd_target_dep_add { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p != 3); my $deps = $$state_p{deps}; # hash table for all target/deps of current Makefile my $target = @$params_p[1]; # target name my @target_deps = split(/ +/, @$params_p[2]); # target dependencies name list error "line $line_num: no previous dependencies for `$target' target" if (! ${$deps}{$target}); foreach (@target_deps) { ${$deps}{$_} = "0" if (! exists ${$deps}{$_}); } push (@{${$deps}{$target}}, @target_deps); } sub cmd_install_cmd { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: one parameters expected for `@$params_p[0]'" if (@$params_p != 2); my $jobs_list = $$state_p{install}; push (@$jobs_list, @$params_p[1]); } sub cmd_msg_info { my ($line_num, $dir_p, $params_p, $state_p) = @_; print "[ info ] ", @$params_p[1], "\n"; } sub cmd_msg_error { my ($line_num, $dir_p, $params_p, $state_p) = @_; error join (" ", @$params_p[1..@$params_p - 1]); } sub cmd_msg_warning { my ($line_num, $dir_p, $params_p, $state_p) = @_; warning join (" ", @$params_p[1..@$params_p - 1]); } sub cmd_mkf_include { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: one parameters expected for `@$params_p[0]'" if (@$params_p != 2); my $filename = @$params_p[1]; local *MKF_FILE; error "line $line_num: can't open `$filename' included file" if (! open (MKF_FILE, "$dir_p/$filename")); process_dir_mkffile($dir_p, $state_p, *MKF_FILE); } sub cmd_path_parent { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: one parameters expected for `@$params_p[0]'" if (@$params_p != 2); my $vars = $$state_p{vars}; foreach my $var_name (split(/ +/, @$params_p[1])) { ${$vars}{$var_name} =~ s/(^| )([^\/][^ ]+)/$1..\/$2/g; } } sub cmd_var_append { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p != 3); my $vars = $$state_p{vars}; my $var_name = @$params_p[1]; my $var_value = @$params_p[2]; # warning "line $line_num: variable `$var_name' not defined before" # if (! defined $$vars{$var_name}); ${$vars}{$var_name} .= " $var_value"; } sub cmd_var_define { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: two parameters expected for `@$params_p[0]'" if (@$params_p != 3); my $vars = $$state_p{vars}; my $var_name = @$params_p[1]; my $var_value = @$params_p[2]; ${$vars}{$var_name} = $var_value; } sub cmd_var_undefine { my ($line_num, $dir_p, $params_p, $state_p) = @_; error "line $line_num: one parameters expected for `@$params_p[0]'" if (@$params_p != 2); my $vars = $$state_p{vars}; my $var_name = @$params_p[1]; ${$vars}{$var_name} = undef; } my %mkf_cmd = ( "_end_if" => \&cmd_end_if, "_else" => \&cmd_else, "_if_exec_ok" => \&cmd_if_exec_ok, "_if_file_exists" => \&cmd_if_file_exists, "_if_in_path" => \&cmd_if_in_path, "_if_stdout_match" => \&cmd_if_stdout_match, "_if_var_eq" => \&cmd_if_var_eq, "command_add" => \&cmd_command_add, "clean_add" => \&cmd_clean_add, "auto_source_dep" => \&cmd_auto_source_dep, "auto_dep" => \&cmd_auto_dep, "target_dep" => \&cmd_target_dep, "target_dep_add" => \&cmd_target_dep_add, "install_cmd" => \&cmd_install_cmd, "msg_error" => \&cmd_msg_error, "msg_info" => \&cmd_msg_info, "msg_warning" => \&cmd_msg_warning, "mkf_include" => \&cmd_mkf_include, "path_parent" => \&cmd_path_parent, "var_define" => \&cmd_var_define, "var_append" => \&cmd_var_append, "var_undefine" => \&cmd_var_undefine ); ###################################################### # Main processing functions # sub process_makefile_rule { (my $dir_p, my $target, my $state_p, local *MAKEFILE) = @_; return if (${$$state_p{done}}{$target}); ${$$state_p{done}}{$target} = "1"; # get dependencies my $deps = ${$$state_p{deps}}{$target}; my $target_name = $target; # get jobs list my $hash; if (-d "$dir_p/$target") { $hash = "enter_dir"; $target_name = "_".$target; } else { if (($deps && (@$deps[0] =~ /[^.]*\.(.*)/)) || ($target =~ /(all)/)) { $hash = $1; } else { $hash = "noext"; } if (($target =~ /[^.]*\.(.*)/) || ($target =~ /(all)/)) { $hash .= "_$1"; } else { $hash .= "_noext"; } } my $job_list = ${$$state_p{jobs}}{$hash}; my $rm_list = ${$$state_p{torm}}{$hash}; if ((!$deps) && (!$job_list)) { print MAKEFILE "all:\n\n" if ($target =~ /^all$/); return; } # write target print MAKEFILE "$target_name:"; if ($deps) { foreach (@$deps) { if (-d "$dir_p/$_") { print MAKEFILE " _$_"; } else { print MAKEFILE " $_"; } } } print MAKEFILE "\n"; foreach my $job (@$job_list) { my $job_line = $job; $job_line = replace_vars($job_line, $$state_p{vars}, "JVAR"); $job_line = replace_vars_list($job_line, $$state_p{vars}, "JLIST"); $job_line = replace_list($job_line, $deps, "SRC"); $job_line = replace($job_line, $target, "OUT"); $job_line =~ s/[\t\n ]+/ /g; print MAKEFILE "\t$job_line\n"; } print MAKEFILE "\n"; push (@{$$state_p{outs}}, $target) if (@$job_list); # add to out files target list foreach my $rm (@$rm_list) { my $rm_line = $rm; $rm_line = replace_vars($rm_line, $$state_p{vars}, "JVAR"); $rm_line = replace_list($rm_line, $deps, "SRC"); $target =~ /^([^.]*)/; $rm_line = replace($rm_line, $1, "OUT!"); push (@{$$state_p{outs}}, $rm_line); } if ($deps) { process_makefile_rule ($dir_p, "$_", $state_p) foreach (@$deps); } } sub process_makefile_clean { (my $dir_p, my $target, my $state_p, local *MAKEFILE) = @_; print MAKEFILE "clean:\n"; my @files, my @dirs; foreach (@{$$state_p{outs}}) { if (-d "$dir_p/$_") { push (@dirs, $_) } else { push (@files, $_) } } foreach my $job (@{${$$state_p{jobs}}{"clean_file"}}) { foreach (@files) { my $job_line = $job; $job_line = replace($job_line, $_, "FILE"); print MAKEFILE "\t$job_line\n"; } } foreach my $job (@{${$$state_p{jobs}}{"clean_dir"}}) { foreach (@dirs) { my $job_line = $job; $job_line = replace($job_line, $_, "DIR"); print MAKEFILE "\t$job_line\n"; } } print MAKEFILE "\n"; print MAKEFILE "re: clean all\n\n"; } sub process_makefile_install { (my $dir_p, my $target, my $state_p, local *MAKEFILE) = @_; print MAKEFILE "install:\n"; foreach my $job (@{${$$state_p{jobs}}{"install_dir"}}) { foreach (@{$$state_p{outs}}) { next if (! -d "$dir_p/$_"); my $job_line = $job; $job_line = replace($job_line, $_, "DIR"); print MAKEFILE "\t$job_line\n"; } } foreach (@{$$state_p{install}}) { print MAKEFILE "\t$_\n"; } print MAKEFILE "\n"; } sub process_makefile { my ($dir_p, $state_p) = @_; open (MAKEFILE, ">$dir_p/Makefile.mkf"); print MAKEFILE "#\n". "# Makefile generated by MKF configure script\n". "#\n\n"; process_makefile_rule ($dir_p, "all", $state_p, *MAKEFILE); process_makefile_clean ($dir_p, "clean", $state_p, *MAKEFILE); process_makefile_install ($dir_p, "install", $state_p, *MAKEFILE); close (MAKEFILE); } sub process_dir_mkffile { (my $dir_p, my $state_p, local *MKF_FILE) = @_; # line counter for error repporting my $line_num = 0; my $enter_if_depth = $$state_p{if_depth}; # used to append lines when ended with \ my $last = ""; # parse mkf.info file line at once foreach my $line () { $line_num++; # keep lines and concat lines ending with \ if ($line =~ /^(.*)\\$/) { $last .= $1; next; } # add last line ending with \ if any if ($last) { $line = $last.$line; $last = ""; } # remove leading spaces $line =~ s/^[ \t]*//g; # skip empty lines and comment lines next if ($line =~ /^[ \t]*(\#.*)?$/); # skip if conditional state is false next if ((not $line =~ /^_/) && (! @{$$state_p{if_bool}}[$$state_p{if_depth}])); # replace variables in command line $line = replace_vars($line, $$state_p{vars}, "PVAR"); $line = replace($line, $dir_p, "PWD"); # split fields my @line_l = split(/[\n\t]+/, $line); # get pointer on function for command if (my $func_ptr = $mkf_cmd{@line_l[0]}) { # call command function $func_ptr -> ($line_num, $dir_p, \@line_l, $state_p); } else { # error if unknow function error "line $line_num: unknown command `@line_l[0]'"; } } error "line $line_num: if not closed at end of file" if ($$state_p{if_depth} != $enter_if_depth); } sub process_dir { my ($dir_p, %state_p) = @_; # add local field to state structure $state_p{deps} = {}; # target/dependencies for current Makefile $state_p{done} = {}; # already done targets $state_p{if_bool} = ["1"]; # conditional state stack $state_p{if_depth} = 0; # conditional state stack index $state_p{outs} = []; # output files $state_p{install} = []; # install commands # undefine previous local variable foreach (keys(%{$state_p{vars}})) { ${$state_p{vars}}{$_} = undef if (/^_/); } ${$state_p{vars}}{_dir} = $dir_p; print "entering $dir_p\n"; # open mkf.info file in directory local *MKF_FILE; open (MKF_FILE, "$dir_p/$mkf_file_gc") || error "no $mkf_file_gc file in directory"; process_dir_mkffile($dir_p, \%state_p, *MKF_FILE); foreach my $subdir (keys(%{$state_p{deps}})) { next if (! -d "$dir_p/$subdir"); my %state = ( "vars" => {%{$state_p{vars}}}, "jobs" => {%{$state_p{jobs}}}, "torm" => {%{$state_p{torm}}} ); process_dir ("$dir_p/$subdir", %state); } # close file close (MKF_FILE); process_makefile ("$dir_p", \%state_p); } sub main { # param passed to configure script on command line my %param_h; # extract variable name and value from parameter foreach my $param (@ARGV) { if ($param =~ /--help/) { -f "mkf.help" && system("cat mkf.help"); return 0; } if ($param =~ /--enable-(.*)/) { $param_h{$1} = "yes"; next; } if ($param =~ /--disable-(.*)/) { $param_h{$1} = "no"; next; } error "bad command line parameter `$param'" if (! ($param =~ /--(.*)=(.*)/)); error "command line parameter `--$1' used twice" if (defined ($param_h{$1})); $param_h{$1} = $2; } # state structure used during configure process my %state = ( "vars" => {%param_h}, "jobs" => {}, "torm" => {} ); # process current directory return process_dir (".", %state); } main; clear_temp_files; print "Done.\n"; exit 0;