#!/usr/bin/perl -w { $program_id = "build.pl v 1.2a 23 Oct 2009"; # 23 Oct 09. WJS v 1.2a # Disabled attempt to check for read errors, since it seemed to # fail for some fleetlink (linux) make files. # 7 Jun 09. WJS v 1.2 # Bug fix: build.pl clean did not work. Soln by recoding # arg parsing so as not to depend on perl -s switch # Note: there are references to a -force switch in the comments # but not in the code or the build.doc file. I suspect these were # from before the touch build-env.pl stuff but I'd want to dig # more before removing them. Relevant since this code revision will # diagnose -force as an unknown switch, if I remember what perl -s # does (not that I looked it up to see, mind you) # 27 Mar 08. WJS v 1.1 # No longer anything special about src/methods/makefile.aamethods # It's gone on OOservers and could be gone on data servers if we made # def a symbolic link to defgb # 27 Apr 07. WJS v 1.0b # Try to get rid of bogus "Could not read" error on linux boxes # 9 Jun 05. WJS v 1.0a # Bug fix of a problem if 2 "things" started w/same string # (eg, defgb & def) # Add program id string; put it in error msgs ############################### # # Master Makefile for Otheroption server [or data server] # - note that only makefile.extension files are 'make'd # - note that this is a perl script, not a "Master Makefile". We # do certain primitive make operations in here, such as being sure # that in a complete build, the library is made first. To be "real", # there would be one system-wide make file and no shell scripts. # NOTE: this file artificially updates build-env.pl. Further, it expects # all dependencies in all make files to depend on build-env.pl. Thus, # everything in a make file should be made on every invokation of build.pl # SWITCHES # Accepts 2 switches (before non-switch arguments, if any) # -cleanfirst/-cleanonly. Either switch calls the selected make files # with a target of "clean". -cleanfirst will then call the selected # make files without the "clean" target. -cleanonly is the equivalent # of an argument of "clean", which is accepted for backward compatibility # "Works" as well as the make files' clean targets interact w/the # dependencies in the make files. -force conflicts with -cleanonly. # ARGUMENTS # If called w/no arguments, will make "everything". Otherwise, # accepts list of arguments, which are processed in order. An argument can # satisfy several of the criteria below - the criteria are applied in order # (eg, can't have a method called 'clean'; will chose subdir defgb even if # makefile.defgb exists) # If argument is: # 1. clean (opt-build clean) # see -cleanonly switch # 2. subdirectory (opt-build lib) # make only makefiles in that subdirectory of optsrc. # Note that this does NOT necessarily make all target files # in the argument subdirectory of $jgofs_root (Example: # opt-build methods does not build methods/defgb because # makefile.defgb is typically in optsrc/defgb, not optsrc/methods) # 3. target file (opt-build list [opt-build makefile.list]) # will look for a make file named makefile.target. There is # no way to deal w/makefile.target found in > 1 directory. # Can specify leading "makefile." to allow discrimination # between subdirs and makefiles within that subdir # 4. target (opt-build methods/def) # calls all make files in methods with target as an argument. # Directory spec required; * (NOT escaped - let shell do # expansion) allowed. # "Works" as long as make files code targets as "../dir/target" # Neither clean switch is allowed w/target (clean presumably # cleans all targets in a make file, which is more than # target would make) # $TRUE = 1; $FALSE = 0; # "Magic" string ID'ing makefiles this routine will make. To be more # exact, such makefiles' file specs must end in /makefile.$STRING, where # $STRING is a non-empty string. $id_string = "makefile."; $working_dir = `pwd`; chomp $working_dir; $this_file = $0; # Use cd command to get directory w/o shell directory shortcuts and w/o # the . and .. directories. Don't know how to do it easily otherwise ($dir,$file) = ($this_file =~ /^(.*)\/(.+)$/); if ($file) { # Presumably $dir must be defined if $file is, so testing $dir is # asking whether or not it's empty $jgofs_root = ($dir) ? `(cd $dir; pwd)` : ""; } else { # Presumably $dir is not defined, or we'd have a non-empty dir spec w/an # empty file name. Don't think this is worth a diagnostic $jgofs_root = $working_dir; $file = $this_file; } chomp $jgofs_root; $opt = ($file =~ /^opt-/); $build_env_file_name = $jgofs_root . "/"; $opt && ($build_env_file_name .= "opt-"); $build_env_file_name .= "build-env.pl"; # Next env var should be set by build-env.pl to point to build-env.pl, and # should be in every make file in order to force builds $config_file_env_var = "CONFIG_FILE"; $source_root = $jgofs_root . "/"; $opt && ($source_root .= "opt"); $source_root .= "src"; # build-env.pl is a main program of its own, so make sure it # doesn't operate on our argument list # Note also that for build-env.pl to "know its name", it uses # global variable $build_env_file_name. $0 is build.pl's name when # build-env.pl is require'd @arglist = @ARGV; @ARGV = (); require $build_env_file_name; # Check coordination of CONFIG_FILE env var between here and build-env.pl ($ENV{$config_file_env_var} eq $build_env_file_name) || &bad_out ("\$ENV{$config_file_env_var} ne $build_env_file_name\n"); # Find and get rid of the "clean" parameters $switch_processing = $TRUE; foreach (@arglist) { if ( /^-/ || ($_ eq "clean") ) { $switch_processing || &bad_out("Switches/clean parameter must be at start of command args\n"); if (($_ eq "-cleanonly") || ($_ eq "clean")) { $cleanonly = 1; } elsif ($_ eq "-cleanfirst") { $cleanfirst = 1; } else { &bad_out ("Unknown switch: $_\n"); } shift @arglist; } else { $switch_processing = $FALSE; } } $cleanonly && $cleanfirst && &bad_out ("Conflicting switches -cleanfirst and -cleanonly (or argument clean)\n"); $clean = ($cleanonly || $cleanfirst); $build = ! $cleanonly; # make the stuff in lib, first - others depend on these # (tilde stuff apparently related to editor backup version stuff for # some editor in use when original build being developed WJS Jun 04) # (Apparently some ls's have a -B to ignore these backup files. Might # someday use that instead of grep. WJS Mar 08) @list = `ls -1 $source_root/lib/$id_string* | grep -v "~"`; # # Add other subdirectory stuff ONE LEVEL under $source_root # $list_command = " ls -1 $source_root/*/$id_string* " . '| grep -v "/lib/" ' . '| grep -v "~"'; push (@list, `$list_command` ); (@list == 0) && &bad_out ("Cannot find any files of the form " . "$source_root/*/$id_string*\n"); foreach (@list) { chomp; } system "touch $ENV{$config_file_env_var}"; if (@arglist == 0) { &do_make("part of full make","",@list); } else { foreach (@arglist) { ($_ eq "clean") && next; if (m"/") { # Args that are targets must include a directory spec $clean && (warn "Cannot use clean switch with target argument\n") && next; $type_of_make = "target"; } elsif (/^$id_string/) { # Args that start with "makefile." are makefiles. Strip off # magic string and treat them like "normal" args that aren't subdirs ($_) = /^$id_string(.+)$/; $_ || (warn "No target in argument $id_string\n" && next); $type_of_make = "requested makefile"; } else { # Arg is a subdir if we can find string "optsrc/$arg" in # list of all make files. Trailing slash deals w/things like # def vs defgb. $temp = "^$source_root/$_/"; # Can NOT use $_ in grep call @sub_list = grep(m"$temp",@list); $type_of_make = (@sub_list == 0) ? "requested makefile" : "part of subdirectory make"; } if ($type_of_make eq "target") { # See coding in make files. Handle case if arg has initial / $target = m"^/" ? "../..$_" : "../../$_"; @sub_list = @list; } elsif ($type_of_make eq "requested makefile") { $target = ""; $temp = "$id_string$_\$"; # Can NOT use $_ in grep call @sub_list = grep( m"$temp", @list); (@sub_list > 1) && (warn "More than one $id_string$_ file; namely \n" . join ('\t',@sub_list) . "\n") && next; } elsif ($type_of_make eq "part of subdirectory make") { $target = ""; } else { &bad_out ("Internal coding error - unknown value for \$type_of_make\n"); } if (@sub_list == 0) { warn "Cannot find appropriate make files\n" . "\tThere are no $id_string* in $source_root/$_ nor \n" . "\tare there any $id_string$_ files in $source_root/*\n"; } else { &do_make($type_of_make,$target,@sub_list); } } } exit; } sub do_make { my ($type_of_make,$target,@files) = @_; my ($dir,$file,$message); my ($n_makefiles_for_target,$any_force_worked,$target_exists_as_file); # Set up to check that for target makes, target appears in exactly 1 make file $n_makefiles_for_target = 0; # Next 2 variables used to try to figure out if a target is up-to-date or # is not a real target. 1) Read comments below 2) Code using these variables # is NOT guaranteed to work, but failure amounts to deceptive message $target_exists_as_file = 0; foreach (@files) { ($dir,$file) = m"^(.+)/($id_string.+)$"; ($dir && $file) || &bad_out ("Bad make file spec $_\n"); chdir $dir; if ($clean) { print "\n ... cleaning $_\n"; $status = system("make -f $file clean"); } if ($build) { if ($target) { # make -q -f file target on globec returns 0 if target exists in file but is # up-to-date, 1 if target exists in file but needs updating, and 2 (w/output # to stderr) if target does not exist in file. Therefore, we should be able # to go through all make files until we get a 0 or 1 back, and then act # accordingly. However, 1) target might appear in >1 make file 2) make # exit statuses are not explicitly defined in man page (or O'Reilly book), so # presumably they could differ. Therefore, go through all make files and # report results accordingly. Sorry about hex/decimal and shift stuff in # constants. # More: # 1) verified that /usr/ccs/bin/make on globec does not return 2 # 2) found out that all makes return 0 if target happens to be an # existing file and make file does NOT contain target. For # example, after a cd optsrc/methods, # make -q -f makefile.stats ../../methods/defgb # returns 0. Best way around this is to change target names # (after all, the don't HAVE to match the target file names). However, # at the moment, they all do) # 3) May be able to parse output (globec make: "nothing to do for defgb" # (if target not in file) vs "defgb up-to-date" (if target IS in file) # However, output varies between makes. Would have to prepare 2 # test cases of each type and diff them (to find out where the target # names appear in each message). Then somehow make that diff into # a template into which we could insert real target names. # Therefore: gave up on distinguishing between up-to-date # targets and targets that appear in no make files. This is # another reason to use -force $status = system ("make -q -f $file $target 2> /dev/null"); $target_exists_as_file || ($target_exists_as_file = (-e $target)); (($status == 512) || ($status == 0)) && next; ($status == 256) || &bad_out ("Uncoded-for return status ($status) from make\n"); $n_makefiles_for_target++; $thing_being_made = $target; $description = "using $_"; } else { $thing_being_made = $_; $description = $type_of_make; } print "\n ... making $thing_being_made ($description)\n"; $status = &check_macro($_,"#",$config_file_env_var); if ($!) { $problem = ($status) ? $status : "access"; warn "Could not $problem $_. \$! = $!\n"; } elsif ($status) { warn " *** make file $_ does not contain appropriate\n" . " *** macro name ($config_file_env_var) to force makes\n"; } system ("make -f $file $target"); } chdir $working_dir; } if ($target) { if ($n_makefiles_for_target == 0) { $message = " *** $target not found in any make file"; if ($target_exists_as_file) { $message .= " or an up-to-date file named $target exists "; } warn "$message\n"; } elsif ($n_makefiles_for_target > 1) { warn " *** $target found in > 1 make file\n"; } } return; } sub check_macro { my ($file,$comment_char,$macro) = @_; # Check that file $file contains string of form ${macro} in some # non-comment line. Normal return is empty string. Return $macro if macro not found. # If I/O error, return "open", "read", or "close" to indicate where problem # occurred. $! is an implicit return and is always set by this routine (0 = OK). # NB: if comment character is special to perl regex's, it must be backslashed my ($rec,$save_dollar_bang); $! = 0; open(FILE,"< $file") || (return "open"); $! = 0; while ( $macro && (defined ($rec = )) ) { ($rec) = split (/$comment_char/,$rec); $rec && ($rec =~ /\$\{$macro\}/) && ($macro = ""); $! = 0; } # Loop seemed to end normally but set $! to "bad file descriptor" on fleelink # (linux) ... but not for all files?! Seems to be no way to test for # read error?! # $! && ($save_dollar_bang = $!); $! = 0; close FILE; $save_dollar_bang && ($! = $save_dollar_bang) && return "read"; $! && return "close"; return $macro; } sub bad_out { die (@_,"$program_id\n"); }