294347549cd3204670532f017069124f70199c0e
[buildfarm-client.git] / run_build.pl
1 #!/usr/bin/perl
2
3 =comment
4
5 Copyright (c) 2003-2010, Andrew Dunstan
6
7 See accompanying License file for license details
8
9 =cut 
10
11 ####################################################
12
13 =comment
14
15  NAME: run_build.pl - script to run exim buildfarm
16
17  SYNOPSIS:
18
19   run_build.pl [option ...] [branchname]
20
21  AUTHOR: Andrew Dunstan
22
23  DOCUMENTATION:
24
25   See http://wiki.exim.org/wiki/PostgreSQL_Buildfarm_Howto
26
27  REPOSITORY:
28
29   https://github.com/EximBuildFarm/client-code
30
31 =cut
32
33 ###################################################
34
35 use vars qw($VERSION); $VERSION = 'REL_0.1';
36
37 use strict;
38 use warnings;
39 use Config;
40 use Fcntl qw(:flock :seek);
41 use File::Path;
42 use File::Copy;
43 use File::Basename;
44 use File::Temp;
45 use File::Spec;
46 use IO::Handle;
47 use POSIX qw(:signal_h strftime);
48 use Data::Dumper;
49 use Cwd qw(abs_path getcwd);
50 use File::Find ();
51
52 # save a copy of the original enviroment for reporting
53 # save it early to reduce the risk of prior mangling
54 use vars qw($orig_env);
55
56 BEGIN
57 {
58     $orig_env = {};
59     while (my ($k,$v) = each %ENV)
60     {
61
62         # report all the keys but only values for whitelisted settings
63         # this is to stop leaking of things like passwords
64         $orig_env->{$k} =(
65             (
66                     $k =~ /^PG(?!PASSWORD)|MAKE|CC|CPP|FLAG|LIBRAR|INCLUDE/
67                   ||$k =~/^(HOME|LOGNAME|USER|PATH|SHELL)$/
68             )
69             ? $v
70             : 'xxxxxx'
71         );
72     }
73 }
74
75 use EximBuild::SCM;
76 use EximBuild::Options;
77 use EximBuild::WebTxn;
78
79 my %module_hooks;
80 my $orig_dir = getcwd();
81 push @INC, $orig_dir;
82
83 # make sure we exit nicely on any normal interrupt
84 # so the cleanup handler gets called.
85 # that lets us stop the db if it's running and
86 # remove the inst and exim directories
87 # so the next run can start clean.
88
89 foreach my $sig (qw(INT TERM HUP QUIT))
90 {
91     $SIG{$sig}=\&interrupt_exit;
92 }
93
94 # copy command line before processing - so we can later report it
95 # unmunged
96
97 my @invocation_args = (@ARGV);
98
99 # process the command line
100 EximBuild::Options::fetch_options();
101
102 die "only one of --from-source and --from-source-clean allowed"
103   if ($from_source && $from_source_clean);
104
105 die "only one of --skip-steps and --only-steps allowed"
106   if ($skip_steps && $only_steps);
107
108 $verbose=1 if (defined($verbose) && $verbose==0);
109 $verbose ||= 0; # stop complaints about undefined var in numeric comparison
110
111 if ($testmode)
112 {
113     $verbose=1 unless $verbose;
114     $forcerun = 1;
115     $nostatus = 1;
116     $nosend = 1;
117
118 }
119
120 use vars qw(%skip_steps %only_steps);
121 $skip_steps ||= "";
122 if ($skip_steps =~ /\S/)
123 {
124     %skip_steps = map {$_ => 1} split(/\s+/,$skip_steps);
125 }
126 $only_steps ||= "";
127 if ($only_steps =~ /\S/)
128 {
129     %only_steps = map {$_ => 1} split(/(\s+|[:,])/,$only_steps);
130 }
131
132 # Currently only specifying a branch is actually used.
133 # Specifying a different repo is just a wishlist item .
134 use vars qw($branch $repo);
135 my ($arg1,$arg2) = (shift,shift);
136 $branch = $arg2 ? $arg2 :
137           $arg1 ? $arg1 :
138           'HEAD';
139 $repo = $arg2 ? $arg1 : 'exim';
140 my $explicit_branch = $branch;
141
142 print_help() if ($help);
143
144 #
145 # process config file
146 #
147 require $buildconf;
148
149 # get the config data into some local variables
150 my (
151     $buildroot,$target,$animal, $print_success,
152     $aux_path,$trigger_exclude,$trigger_include,$secret,
153     $keep_errs,$force_every, $make, $optional_steps,
154     $use_vpath,$tar_log_cmd, $using_msvc, $extra_config,
155     $make_jobs, $core_file_glob
156   )
157   =@EximBuild::conf{
158     qw(build_root target animal print_success aux_path trigger_exclude
159       trigger_include secret keep_error_builds force_every make optional_steps
160       use_vpath tar_log_cmd using_msvc extra_config make_jobs core_file_glob)
161   };
162
163 #default is no parallel build
164 $make_jobs ||= 1;
165
166 # default core file pattern is Linux, which used to be hardcoded
167 $core_file_glob ||= 'core*';
168
169 # legacy name
170 if (defined($EximBuild::conf{trigger_filter}))
171 {
172     $trigger_exclude = $EximBuild::conf{trigger_filter};
173 }
174
175 my  $scm_timeout_secs = $EximBuild::conf{scm_timeout_secs}
176   || $EximBuild::conf{cvs_timeout_secs};
177
178 print scalar(localtime()),": buildfarm run for $animal:$branch starting\n"
179   if $verbose;
180
181 # Allow commandline overrides of conf variables
182 foreach my $arg ( @{$EximBuild::Options::overrides} )
183 {
184   if (my ($key,$val) = split '=', $arg)
185   {
186     $EximBuild::conf{$key} = $val;
187     printf "Commandline override: '$key' = '%s'\n", $EximBuild::conf{$key}
188       if $verbose;
189   }
190 }
191
192 if (ref($force_every) eq 'HASH')
193 {
194     $force_every = $force_every->{$branch} || $force_every->{default};
195 }
196
197 my $config_opts = $EximBuild::conf{config_opts};
198 my $scm = new EximBuild::SCM \%EximBuild::conf;
199
200 my $buildport;
201
202 if (exists $EximBuild::conf{base_port})
203 {
204     $buildport = $EximBuild::conf{base_port};
205     if ($branch =~ /REL(\d+)_(\d+)/)
206     {
207         $buildport += (10 * ($1 - 7)) + $2;
208     }
209 }
210 else
211 {
212
213     # support for legacy config style
214     $buildport = $EximBuild::conf{branch_ports}->{$branch} || 5999;
215 }
216
217 $ENV{EXTRA_REGRESS_OPTS} = "--port=$buildport";
218
219 $tar_log_cmd ||= "tar -z -cf runlogs.tgz *.log";
220
221 my $logdirname = "lastrun-logs";
222
223 if ($from_source || $from_source_clean)
224 {
225     $from_source ||= $from_source_clean;
226     die "sourceroot $from_source not absolute"
227       unless $from_source =~ m!^/!;
228
229     # we need to know where the lock should go, so unless the path
230     # contains HEAD we require it to be specified.
231     die "must specify branch explicitly with from_source"
232       unless ($explicit_branch || $from_source =~ m!/HEAD/!);
233     $verbose ||= 1;
234     $nosend=1;
235     $nostatus=1;
236     $use_vpath = undef;
237     $logdirname = "fromsource-logs";
238 }
239
240 my @locales;
241 if ($branch eq 'HEAD' || $branch ge 'REL8_4')
242 {
243
244     # non-C locales are not regression-safe before 8.4
245     @locales = @{$EximBuild::conf{locales}} if exists $EximBuild::conf{locales};
246 }
247 unshift(@locales,'C') unless grep {$_ eq "C"} @locales;
248
249 # sanity checks
250 # several people have run into these
251
252 if ( `uname -s 2>&1 ` =~ /CYGWIN/i )
253 {
254     my @procs = `ps -ef`;
255     die "cygserver not running" unless(grep {/cygserver/} @procs);
256 }
257 my $ccachedir;
258 if ( $ccachedir = $EximBuild::conf{build_env}->{CCACHE_DIR} )
259 {
260
261     # ccache is smart enough to create what you tell it is the cache dir, but
262     # not smart enough to build the whole path. mkpath croaks on error, so
263     # we just let it.
264
265     mkpath $ccachedir;
266     $ccachedir = abs_path($ccachedir);
267 }
268
269 if ($^V lt v5.8.0)
270 {
271     die "no aux_path in config file" unless $aux_path;
272 }
273
274 die "cannot run as root/Administrator" unless ($using_msvc or $> > 0);
275
276 my $devnull = $using_msvc ? "nul" : "/dev/null";
277
278 if (!$from_source)
279 {
280     $scm->check_access($using_msvc);
281 }
282
283 my $st_prefix = "$animal.";
284
285 my $exim = $from_source  || $scm->get_build_path($use_vpath);
286
287 # set environment from config
288 while (my ($envkey,$envval) = each %{$EximBuild::conf{build_env}})
289 {
290     $ENV{$envkey}=$envval;
291 }
292
293 # change to buildroot for this branch or die
294 die "no buildroot" unless $buildroot;
295
296 unless ($buildroot =~ m!^/!
297     or($using_msvc and $buildroot =~ m![a-z]:[/\\]!i ))
298 {
299     die "buildroot $buildroot not absolute";
300 }
301
302 die "$buildroot does not exist or is not a directory" unless -d $buildroot;
303
304 chdir $buildroot || die "chdir to $buildroot: $!";
305
306 mkdir $branch unless -d $branch;
307 chdir $branch || die "chdir to $buildroot/$branch";
308
309 # rename legacy status files/directories
310 foreach my $oldfile (glob("last*"))
311 {
312     move $oldfile, "$st_prefix$oldfile";
313 }
314
315 my $branch_root = getcwd();
316
317 # make sure we are using GNU make
318 die "$make is not GNU Make - please fix config file"
319   unless check_make();
320
321 # set up modules
322 foreach my $module (@{$EximBuild::conf{modules}})
323 {
324
325     # fill in the name of the module here, so use double quotes
326     # so everything BUT the module name needs to be escaped
327     my $str = qq!
328          require EximBuild::Modules::$module; 
329          EximBuild::Modules::${module}::setup(
330               \$buildroot,
331               \$branch,
332               \\\%EximBuild::conf,
333               \$exim);
334     !;
335     eval $str;
336
337     # make errors fatal
338     die $@ if $@;
339 }
340
341 # acquire the lock
342
343 my $lockfile;
344 my $have_lock;
345
346 open($lockfile, ">builder.LCK") || die "opening lockfile: $!";
347
348 # only one builder at a time allowed per branch
349 # having another build running is not a failure, and so we do not output
350 # a failure message under this condition.
351 if ($from_source)
352 {
353     die "acquiring lock in $buildroot/$branch/builder.LCK"
354       unless flock($lockfile,LOCK_EX|LOCK_NB);
355 }
356 elsif ( !flock($lockfile,LOCK_EX|LOCK_NB) )
357 {
358     print "Another process holds the lock on "
359       ."$buildroot/$branch/builder.LCK. Exiting."
360       if ($verbose);
361     exit(0);
362 }
363
364 die "$buildroot/$branch has $exim or inst directories!"
365   if ((!$from_source && -d $exim) || -d "inst");
366
367 # we are OK to run if we get here
368 $have_lock = 1;
369
370 # check if file present for forced run
371 my $forcefile = $st_prefix . "force-one-run";
372 if (-e $forcefile)
373 {
374     $forcerun = 1;
375     unlink $forcefile;
376 }
377
378 # try to allow core files to be produced.
379 # another way would be for the calling environment
380 # to call ulimit. We do this in an eval so failure is
381 # not fatal.
382 eval{
383     require BSD::Resource;
384     BSD::Resource->import();
385
386     # explicit sub calls here. using & keeps compiler happy
387     my $coreok = setrlimit(&RLIMIT_CORE,&RLIM_INFINITY,&RLIM_INFINITY);
388     die "setrlimit" unless $coreok;
389 };
390 warn "failed to unlimit core size: $@" if $@ && $verbose > 1;
391
392 # the time we take the snapshot
393 my $now=time;
394 my $installdir = "$buildroot/$branch/inst";
395 my $dbstarted;
396
397 my $extraconf;
398
399 # cleanup handler for all exits
400 END
401 {
402
403     # clean up temp file
404     unlink $ENV{TEMP_CONFIG} if $extraconf;
405
406     # if we have the lock we must already be in the build root, so
407     # removing things there should be safe.
408     # there should only be anything to cleanup if we didn't have
409     # success.
410     if ( $have_lock && -d "$exim")
411     {
412         if ($dbstarted)
413         {
414             chdir $installdir;
415             system(qq{"bin/pg_ctl" -D data stop >$devnull 2>&1});
416             foreach my $loc (@locales)
417             {
418                 next unless -d "data-$loc";
419                 system(qq{"bin/pg_ctl" -D "data-$loc" stop >$devnull 2>&1});
420             }
421             chdir $branch_root;
422         }
423         if ( !$from_source && $keep_errs)
424         {
425             print "moving kept error trees\n" if $verbose;
426             my $timestr = strftime "%Y-%m-%d_%H-%M-%S", localtime($now);
427             unless (move("$exim", "eximkeep.$timestr"))
428             {
429                 print "error renaming '$exim' to 'eximkeep.$timestr': $!";
430             }
431             if (-d "inst")
432             {
433                 unless(move("inst", "instkeep.$timestr"))
434                 {
435                     print "error renaming 'inst' to 'instkeep.$timestr': $!";
436                 }
437             }
438         }
439         else
440         {
441             rmtree("inst") unless $keepall;
442             rmtree("$exim") unless ($from_source || $keepall);
443         }
444
445         # only keep the cache in cases of success
446         rmtree("$ccachedir") if $ccachedir;
447     }
448
449     # get the modules to clean up after themselves
450     process_module_hooks('cleanup');
451
452     if ($have_lock)
453     {
454         if ($use_vpath)
455         {
456
457             # vpath builds leave some stuff lying around in the
458             # source dir, unfortunately. This should clean it up.
459             $scm->cleanup();
460         }
461         close($lockfile);
462         unlink("builder.LCK");
463     }
464 }
465
466 # Prepend the DEFAULT settings (if any) to any settings for the
467 # branch. Since we're mangling this, deep clone $extra_config
468 # so the config object is kept as given. This is done using
469 # Dumper() because the MSys DTK perl doesn't have Storable. This
470 # is less efficient but it hardly matters here for this shallow
471 # structure.
472
473 $extra_config = eval Dumper($extra_config);
474
475 if ($extra_config &&  $extra_config->{DEFAULT})
476 {
477     if (!exists  $extra_config->{$branch})
478     {
479         $extra_config->{$branch} =      $extra_config->{DEFAULT};
480     }
481     else
482     {
483         unshift(@{$extra_config->{$branch}}, @{$extra_config->{DEFAULT}});
484     }
485 }
486
487 if ($extra_config && $extra_config->{$branch})
488 {
489     my $tmpname;
490     ($extraconf,$tmpname) =File::Temp::tempfile(
491         'buildfarm-XXXXXX',
492         DIR => File::Spec->tmpdir(),
493         UNLINK => 1
494     );
495     die 'no $tmpname!' unless $tmpname;
496     $ENV{TEMP_CONFIG} = $tmpname;
497     foreach my $line (@{$extra_config->{$branch}})
498     {
499         print $extraconf "$line\n";
500     }
501     autoflush $extraconf 1;
502 }
503
504 use vars qw($steps_completed);
505 $steps_completed = "";
506
507 my @changed_files;
508 my @changed_since_success;
509 my $last_status;
510 my $last_run_snap;
511 my $last_success_snap;
512 my $current_snap;
513 my @filtered_files;
514 my $savescmlog = "";
515
516 if ($from_source_clean)
517 {
518     print time_str(),"cleaning source in $exim ...\n";
519     clean_from_source();
520 }
521 elsif (!$from_source)
522 {
523
524     # see if we need to run the tests (i.e. if either something has changed or
525     # we have gone over the force_every heartbeat time)
526
527     print time_str(),"checking out source ...\n" if $verbose;
528
529     my $timeout_pid;
530
531     $timeout_pid = spawn(\&scm_timeout,$scm_timeout_secs)
532       if $scm_timeout_secs;
533
534     $savescmlog = $scm->checkout($branch);
535     $steps_completed = "SCM-checkout";
536
537     process_module_hooks('checkout',$savescmlog);
538
539     if ($timeout_pid)
540     {
541
542         # don't kill me, I finished in time
543         if (kill(SIGTERM, $timeout_pid))
544         {
545
546             # reap the zombie
547             waitpid($timeout_pid,0);
548         }
549     }
550
551     print time_str(),"checking if build run needed ...\n" if $verbose;
552
553     # transition to new time processing
554     unlink "last.success";
555
556     # get the timestamp data
557     $last_status = find_last('status') || 0;
558     $last_run_snap = find_last('run.snap');
559     $last_success_snap = find_last('success.snap');
560     $forcerun = 1 unless (defined($last_run_snap));
561
562     # updated by find_changed to last mtime of any file in the repo
563     $current_snap=0;
564
565     # see if we need to force a build
566     $last_status = 0
567       if ( $last_status
568         && $force_every
569         &&$last_status+($force_every*3600) < $now);
570     $last_status = 0 if $forcerun;
571
572     # see what's changed since the last time we did work
573     $scm->find_changed(\$current_snap,$last_run_snap, $last_success_snap,
574         \@changed_files,\@changed_since_success);
575
576     #ignore changes to files specified by the trigger exclude filter, if any
577     if (defined($trigger_exclude))
578     {
579         @filtered_files = grep { !m[$trigger_exclude] } @changed_files;
580     }
581     else
582     {
583         @filtered_files = @changed_files;
584     }
585
586     #ignore changes to files NOT specified by the trigger include filter, if any
587     if (defined($trigger_include))
588     {
589         @filtered_files = grep { m[$trigger_include] } @filtered_files;
590     }
591
592     my $modules_need_run;
593
594     process_module_hooks('need-run',\$modules_need_run);
595
596     # if no build required do nothing
597     if ($last_status && !@filtered_files && !$modules_need_run)
598     {
599         print time_str(),
600           "No build required: last status = ",scalar(gmtime($last_status)),
601           " GMT, current snapshot = ",scalar(gmtime($current_snap))," GMT,",
602           " changed files = ",scalar(@filtered_files),"\n"
603           if $verbose;
604         rmtree("$exim");
605         exit 0;
606     }
607
608     # get version info on both changed files sets
609     # XXX modules support?
610
611     $scm->get_versions(\@changed_files);
612     $scm->get_versions(\@changed_since_success);
613
614 } # end of unless ($from_source)
615
616 cleanlogs();
617
618 writelog('SCM-checkout',$savescmlog) unless $from_source;
619 $scm->log_id() unless $from_source;
620
621 # copy/create according to vpath/scm settings
622
623 if ($use_vpath)
624 {
625     print time_str(),"creating vpath build dir $exim ...\n" if $verbose;
626     mkdir $exim || die "making $exim: $!";
627 }
628 elsif (!$from_source && $scm->copy_source_required())
629 {
630     print time_str(),"copying source to $exim ...\n" if $verbose;
631
632     $scm->copy_source($using_msvc);
633 }
634
635 process_module_hooks('setup-target');
636
637 # start working
638
639 set_last('status',$now) unless $nostatus;
640 set_last('run.snap',$current_snap) unless $nostatus;
641
642 my $started_times = 0;
643 print time_str(),"running configure ...\n" if $verbose;
644
645 # each of these routines will call send_result, which calls exit,
646 # on any error, so each step depends on success in the previous
647 # steps.
648 configure();
649
650 make();
651
652 display_features();
653
654 make_test() if (check_optional_step('make_test'));
655
656 make_doc() if (check_optional_step('build_docs'));
657
658 ##check_port_is_ok($buildport,'Post');
659
660 # if we get here everything went fine ...
661
662 my $saved_config = get_config_summary();
663
664 rmtree("inst"); # only keep failures
665 rmtree("$exim") unless ($from_source || $keepall);
666
667 print(time_str(),"OK\n") if $verbose;
668
669 send_result("OK");
670
671 exit;
672
673 ############## end of main program ###########################
674
675 sub print_help
676 {
677     print qq!
678 usage: $0 [options] [branch]
679
680  where options are one or more of:
681
682   --nosend                  = don't send results
683   --nostatus                = don't set status files
684   --force                   = force a build run (ignore status files)
685   --from-source=/path       = use source in path, not from SCM
686   or
687   --from-source-clean=/path = same as --from-source, run make distclean first
688   --config=/path/to/file    = alternative location for config file
689   --keepall                 = keep directories if an error occurs
690   --verbose[=n]             = verbosity (default 1) 2 or more = huge output.
691   --quiet                   = suppress normal error message 
692   --test                    = short for --nosend --nostatus --verbose --force
693   --skip-steps=list         = skip certain steps
694   --only-steps=list         = only do certain steps, not allowed with skip-steps
695                               lists can be comma, colon, or space separated
696
697 Default branch is HEAD. Usually only the --config option should be necessary.
698
699 !;
700     exit(0);
701 }
702
703 sub time_str
704 {
705     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
706     return sprintf("[%.2d:%.2d:%.2d] ",$hour, $min, $sec);
707 }
708
709 sub step_wanted
710 {
711     my $step = shift;
712     return $only_steps{$step} if $only_steps;
713     return !$skip_steps{$step} if $skip_steps;
714     return 1; # default is everything is wanted
715 }
716
717 sub register_module_hooks
718 {
719     my $who = shift;
720     my $what = shift;
721     while (my ($hook,$func) = each %$what)
722     {
723         $module_hooks{$hook} ||= [];
724         push(@{$module_hooks{$hook}},[$func,$who]);
725     }
726 }
727
728 sub process_module_hooks
729 {
730     my $hook = shift;
731
732     # pass remaining args (if any) to module func
733     foreach my $module (@{$module_hooks{$hook}})
734     {
735         my ($func,$module_instance) = @$module;
736         &$func($module_instance, @_);
737     }
738 }
739
740 sub check_optional_step
741 {
742     my $step = shift;
743     my $oconf;
744     my $shandle;
745
746     return undef unless ref($oconf = $optional_steps->{$step});
747     if ($oconf->{branches})
748     {
749         return undef unless grep {$_ eq $branch} @{$oconf->{branches}};
750     }
751
752     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =localtime(time);
753     return undef if (exists $oconf->{min_hour} &&  $hour < $oconf->{min_hour});
754     return undef if (exists $oconf->{max_hour} &&  $hour > $oconf->{max_hour});
755     return undef if (exists $oconf->{dow}
756         &&grep {$_ eq $wday} @{$oconf->{dow}});
757
758     my $last_step = $last_status = find_last("$step") || 0;
759
760     return undef unless ($forcerun ||
761                          time >$last_step + (3600 * $oconf->{min_hours_since}));
762     set_last("$step") unless $nostatus;
763
764     return 1;
765 }
766
767 sub clean_from_source
768 {
769     if (-e "$exim/GNUmakefile")
770     {
771
772         # fixme for MSVC
773         my @makeout = `cd $exim && $make distclean 2>&1`;
774         my $status = $? >>8;
775         writelog('distclean',\@makeout);
776         print "======== distclean log ===========\n",@makeout if ($verbose > 1);
777         send_result('distclean',$status,\@makeout) if $status;
778     }
779 }
780
781 sub interrupt_exit
782 {
783     my $signame = shift;
784     print "Exiting on signal $signame\n";
785     exit(1);
786 }
787
788 sub cleanlogs
789 {
790     my $lrname = $st_prefix . $logdirname;
791     rmtree("$lrname");
792     mkdir "$lrname" || die "can't make $lrname dir: $!";
793 }
794
795 sub writelog
796 {
797     my $stage = shift;
798     my $loglines = shift;
799     my $handle;
800     my $lrname = $st_prefix . $logdirname;
801     open($handle,">$lrname/$stage.log") || die $!;
802     print $handle @$loglines;
803     close($handle);
804 }
805
806 sub display_features
807 {
808     return unless step_wanted('features');
809     my @out = `cd $exim
810                src/build-*/exim -C test/confs/0000 -bV `;
811     my $status = $? >>8;
812     writelog('features',\@out);
813     print "======== features log ===========\n",@out if ($verbose > 1);
814     send_result('Features',$status,\@out) if $status;
815     $steps_completed .= " Features";
816 }
817     
818 sub check_make
819 {
820     my @out = `$make -v 2>&1`;
821     return undef unless ($? == 0 && grep {/GNU Make/} @out);
822     return 'OK';
823 }
824
825 sub make
826 {
827     return unless step_wanted('make');
828     print time_str(),"running make ...\n" if $verbose;
829     my $make_args = join(' ',$EximBuild::conf{make_args});
830     my (@makeout);
831     my $make_cmd = $make;
832     $make_cmd = "$make -j $make_jobs"
833       if ($make_jobs > 1 && ($branch eq 'HEAD' || $branch ge 'REL9_1'));
834     @makeout = `cd $exim/src && $make_cmd $make_args 2>&1`;
835     my $status = $? >>8;
836     writelog('make',\@makeout);
837     print "======== make log ===========\n",@makeout if ($verbose > 1);
838     send_result('Make',$status,\@makeout) if $status;
839     $steps_completed .= " Make";
840 }
841
842 sub make_doc
843 {
844     return unless step_wanted('make-doc');
845     print time_str(),"running make doc ...\n" if $verbose;
846
847     my (@makeout);
848     @makeout = `cd $exim/doc/doc-docbook/ && \
849                 EXIM_VER="4.82" $make everything 2>&1`;
850     my $status = $? >>8;
851     writelog('make-doc',\@makeout);
852     print "======== make doc log ===========\n",@makeout if ($verbose > 1);
853     send_result('Doc',$status,\@makeout) if $status;
854     $steps_completed .= " Doc";
855 }
856
857 sub get_stack_trace
858 {
859     my $bindir = shift;
860     my $pgdata = shift;
861
862     # no core = no result
863     my @cores = glob("$pgdata/$core_file_glob");
864     return () unless @cores;
865
866     # no gdb = no result
867     system "gdb --version > $devnull 2>&1";
868     my $status = $? >>8;
869     return () if $status;
870
871     my $cmdfile = "./gdbcmd";
872     my $handle;
873     open($handle, ">$cmdfile");
874     print $handle "bt\n";
875     close($handle);
876
877     my @trace;
878
879     foreach my $core (@cores)
880     {
881         my @onetrace = `gdb -x $cmdfile --batch $bindir/exim $core 2>&1`;
882         push(@trace,
883             "\n\n================== stack trace: $core ==================\n",
884             @onetrace);
885     }
886
887     unlink $cmdfile;
888
889     return @trace;
890 }
891
892 sub make_install_check
893 {
894     my $locale = shift;
895     return unless step_wanted('install-check');
896     print time_str(),"running make installcheck ($locale)...\n" if $verbose;
897
898     my @checklog;
899     unless ($using_msvc)
900     {
901         @checklog = `cd $exim/src/test/regress && $make installcheck 2>&1`;
902     }
903     else
904     {
905         chdir "$exim/src/tools/msvc";
906         @checklog = `perl vcregress.pl installcheck 2>&1`;
907         chdir $branch_root;
908     }
909     my $status = $? >>8;
910     my @logfiles =
911       ("$exim/src/test/regress/regression.diffs","$installdir/logfile");
912     foreach my $logfile(@logfiles)
913     {
914         next unless (-e $logfile );
915         push(@checklog,"\n\n================== $logfile ==================\n");
916         my $handle;
917         open($handle,$logfile);
918         while(<$handle>)
919         {
920             push(@checklog,$_);
921         }
922         close($handle);
923     }
924     if ($status)
925     {
926         my @trace =
927           get_stack_trace("$installdir/bin","$installdir/data-$locale");
928         push(@checklog,@trace);
929     }
930     writelog("install-check-$locale",\@checklog);
931     print "======== make installcheck log ===========\n",@checklog
932       if ($verbose > 1);
933     send_result("InstallCheck-$locale",$status,\@checklog) if $status;
934     $steps_completed .= " InstallCheck-$locale";
935 }
936
937 sub make_isolation_check
938 {
939     my $locale = shift;
940     return unless step_wanted('isolation-check');
941     my @makeout;
942     unless ($using_msvc)
943     {
944         my $cmd =
945           "cd $exim/src/test/isolation && $make NO_LOCALE=1 installcheck";
946         @makeout = `$cmd 2>&1`;
947     }
948     else
949     {
950         chdir "$exim/src/tools/msvc";
951         @makeout = `perl vcregress.pl isolationcheck 2>&1`;
952         chdir $branch_root;
953     }
954
955     my $status = $? >>8;
956
957     # get the log files and the regression diffs
958     my @logs = glob("$exim/src/test/isolation/log/*.log");
959     push(@logs,"$installdir/logfile");
960     unshift(@logs,"$exim/src/test/isolation/regression.diffs")
961       if (-e "$exim/src/test/isolation/regression.diffs");
962     foreach my $logfile (@logs)
963     {
964         push(@makeout,"\n\n================== $logfile ===================\n");
965         my $handle;
966         open($handle,$logfile);
967         while(<$handle>)
968         {
969             push(@makeout,$_);
970         }
971         close($handle);
972     }
973     if ($status)
974     {
975         my @trace =
976           get_stack_trace("$installdir/bin","$installdir/data-$locale");
977         push(@makeout,@trace);
978     }
979     writelog('isolation-check',\@makeout);
980     print "======== make isolation check logs ===========\n",@makeout
981       if ($verbose > 1);
982
983     send_result('IsolationCheck',$status,\@makeout) if $status;
984     $steps_completed .= " IsolationCheck";
985 }
986
987 sub make_test
988 {
989     return unless step_wanted('test');
990     print time_str(),"running make test ...\n" if $verbose;
991     my $tests_range = $EximBuild::conf{range_num_tests} || "1 4";
992     my @makeout;
993     @makeout =`(cd $exim/test
994                 autoconf && ./configure && $make )2>&1 `;
995     my $status = $? >>8;
996     unless($status)
997     {
998       my @tmp = `(WORKDIR=\$PWD
999                   cd $exim/test
1000                   ./runtest \$WORKDIR/$exim/src/build-*/exim -CONTINUE $tests_range )2>&1`;
1001       $status = $? >>8;
1002       push @makeout, @tmp;
1003     }
1004     writelog('test',\@makeout);
1005     print "======== make test logs ===========\n",@makeout
1006       if ($verbose > 1);
1007
1008     send_result('Test',$status,\@makeout) if $status;
1009     $steps_completed .= " Test";
1010 }
1011
1012 sub make_ecpg_check
1013 {
1014     return unless step_wanted('ecpg-check');
1015     my @makeout;
1016     my $ecpg_dir = "$exim/src/interfaces/ecpg";
1017     if ($using_msvc)
1018     {
1019         chdir "$exim/src/tools/msvc";
1020         @makeout = `perl vcregress.pl ecpgcheck 2>&1`;
1021         chdir $branch_root;
1022     }
1023     else
1024     {
1025         @makeout = `cd  $ecpg_dir && $make NO_LOCALE=1 check 2>&1`;
1026     }
1027     my $status = $? >>8;
1028
1029     # get the log files and the regression diffs
1030     my @logs = glob("$ecpg_dir/test/log/*.log");
1031     unshift(@logs,"$ecpg_dir/test/regression.diffs")
1032       if (-e "$ecpg_dir/test/regression.diffs");
1033     foreach my $logfile (@logs)
1034     {
1035         push(@makeout,"\n\n================== $logfile ===================\n");
1036         my $handle;
1037         open($handle,$logfile);
1038         while(<$handle>)
1039         {
1040             push(@makeout,$_);
1041         }
1042         close($handle);
1043     }
1044     if ($status)
1045     {
1046         my $base = "$ecpg_dir/test/regress/tmp_check";
1047         my @trace =
1048           get_stack_trace("$base/install$installdir/bin",       "$base/data");
1049         push(@makeout,@trace);
1050     }
1051     writelog('ecpg-check',\@makeout);
1052     print "======== make ecpg check logs ===========\n",@makeout
1053       if ($verbose > 1);
1054
1055     send_result('ECPG-Check',$status,\@makeout) if $status;
1056     $steps_completed .= " ECPG-Check";
1057 }
1058
1059 sub configure
1060 {
1061     return unless step_wanted('configure');
1062     print time_str(),"creating configuration ...\n" if $verbose;
1063
1064     my @quoted_opts;
1065     foreach my $c_opt (@$config_opts)
1066     {
1067         if ($c_opt =~ /['"]/)
1068         {
1069             push(@quoted_opts,$c_opt);
1070         }
1071         else
1072         {
1073             push(@quoted_opts,"'$c_opt'");
1074         }
1075     }
1076
1077     my $env = $EximBuild::conf{makefile_set};
1078     my $add = $EximBuild::conf{makefile_add};
1079     my $features = $EximBuild::conf{makefile_regex};
1080
1081     my $envstr = "";
1082     while (my ($key,$val) = each %$env)
1083     {
1084         $envstr .= "$key='$val'\n";
1085     }
1086     while (my ($key,$val) = each %$add)
1087     {
1088         $envstr .= "$key+='$val'\n";
1089     }
1090
1091     my $conf_path = "src/src/EDITME";
1092     my $local_conf = "src/Local/Makefile";
1093     my @confout = `cd $exim; mkdir -p src/Local 2>&1`;
1094     my @tmp = `cd $exim && cp $conf_path $local_conf 2>&1`;
1095     my $status = $? >> 8;
1096     push @confout, @tmp;
1097     if ($status == 0)
1098     {
1099         # First, let's display some directory permissions in case
1100         # permissions are causing problems.
1101         my @dir = split('/',`pwd`);
1102         chomp(@dir);
1103         my $count = scalar @dir;
1104         my $loop = 1;
1105         my $dirs = '';
1106         while ($loop < $count)
1107         {
1108           my $dir = "";
1109           foreach my $i (0 .. $loop)
1110           {
1111             $dir .= $dir[$i].'/';
1112           }
1113           $dirs .= " $dir";
1114           $loop++;
1115         }
1116         @tmp = `echo "Verify Directory Permissions"
1117                 ls -ld $dirs`;
1118         push @confout, @tmp;
1119         # Build the config file from the EDITME template
1120         @tmp = `cd $exim && echo '$envstr' >> $local_conf`;
1121         push @confout, @tmp;
1122         my $exim_user = $EximBuild::conf{master_exim_user} || 'exim';
1123         @tmp = `echo "Hardcoded Exim user info:"; id $exim_user
1124           cd $exim && perl -pi -e 's/^EXIM_USER=.*/EXIM_USER=$exim_user/' $local_conf`;
1125         push @confout, @tmp;
1126         my $me = `whoami`; chomp $me;
1127         @tmp = `echo "Build Farm user info:"; id $me
1128           cd $exim && perl -pi -e 's/^# CONFIGURE_OWNER=\$/CONFIGURE_OWNER=$me/' $local_conf`;
1129         push @confout, @tmp;
1130         @tmp = `cd $exim && perl -pi -e 's/^# TRUSTED_CONFIG_LIST=/TRUSTED_CONFIG_LIST=/' $local_conf`;
1131         push @confout, @tmp;
1132         @tmp = `cd $exim && perl -pi -e 's/^# WHITELIST_D_MACROS=.*/WHITELIST_D_MACROS=DIR:EXIM_PATH:AA:ACL:ACLRCPT:ACL_MAIL:ACL_PREDATA:ACL_RCPT:AFFIX:ALLOW:ARG1:ARG2:AUTHF:AUTHS:AUTH_ID_DOMAIN:BAD:BANNER:BB:BR:BRB:CERT:COM:COMMAND_USER:CONNECTCOND:CONTROL:CREQCIP:CREQMAC:CRL:CSS:D6:DATA:DCF:DDF:DEFAULTDWC:DELAY:DETAILS:DRATELIMIT:DYNAMIC_OPTION:ELI:ERROR_DETAILS:ERT:FAKE:FALLBACK:FILTER:FILTER_PREPEND_HOME:FORBID:FORBID_SMTP_CODE:FUSER:HAI:HAP:HARDLIMIT:HEADER_LINE_MAXSIZE:HEADER_MAXSIZE:HELO_MSG:HL:HOSTS:HOSTS_AVOID_TLS:HOSTS_MAX_TRY:HVH:IFACE:IGNORE_QUOTA:INC:INSERT:IP1:IP2:LAST:LDAPSERVERS:LENCHECK:LIMIT:LIST:LOG_SELECTOR:LS:MAXNM:MESSAGE_LOGS:MSIZE:NOTDAEMON:ONCE:ONLY:OPT:OPTION:ORDER:PAH:PEX:PORT:PTBC:QDG:QOLL:QUOTA:QUOTA_FILECOUNT:QWM:RCPT_MSG:REMEMBER:REQUIRE:RETRY:RETRY1:RETRY2:RETURN:RETURN_ERROR_DETAILS:REWRITE:ROUTE_DATA:RRATELIMIT:RT:S:SELECTOR:SELF:SERVER:SERVERS:SREQCIP:SREQMAC:SRV:STD:STRICT:SUB:SUBMISSION_OPTIONS:TIMEOUTDEFER:TIMES:TRUSTED:TRYCLEAR:UL:USE_SENDER:UTF8:VALUE:WMF:X:Y/' $local_conf`;
1133         push @confout, @tmp;
1134         @tmp = `cd $exim && perl -pi -e 's/^EXIM_MONITOR=(.*)/# EXIM_MONITOR=\$1/' $local_conf`;
1135         push @confout, @tmp;
1136         for my $regex ( @$features )
1137         {
1138             @tmp = `cd $exim
1139                     perl -pi -e '$regex' $local_conf 2>&1
1140                     echo "Used regex: $regex" `;
1141             push @confout, @tmp;
1142         }
1143         # Add the final build file to the display output
1144         @tmp = `cd $exim
1145                 echo
1146                 echo "Contents of Local/Makefile:"
1147                 egrep '^[^#]' $local_conf `;
1148         push @confout, @tmp;
1149         # Does not matter what the Exim version is, as long as it is valid.
1150         my $exim_ver = $EximBuild::conf{exim_test_version} || '4.82';
1151         `cd $exim
1152          echo 'EXIM_RELEASE_VERSION="$exim_ver"' > src/src/version.sh
1153          echo 'EXIM_VARIANT_VERSION=""' >> src/src/version.sh
1154          echo 'EXIM_COMPILE_NUMBER="0"' >> src/src/version.sh`;
1155     }
1156    
1157     print "======== configure output ===========\n",@confout
1158       if ($verbose > 1);
1159
1160     writelog('configure',\@confout);
1161
1162     if ($status)
1163     {
1164         send_result('Configure',$status,\@confout);
1165     }
1166
1167     $steps_completed .= " Configure";
1168 }
1169
1170 sub find_last
1171 {
1172     my $which = shift;
1173     my $stname = $st_prefix . "last.$which";
1174     my $handle;
1175     open($handle,$stname) or return undef;
1176     my $time = <$handle>;
1177     close($handle);
1178     chomp $time;
1179     return $time + 0;
1180 }
1181
1182 sub set_last
1183 {
1184     my $which = shift;
1185     my $stname = $st_prefix . "last.$which";
1186     my $st_now = shift || time;
1187     my $handle;
1188     open($handle,">$stname") or die "opening $stname: $!";
1189     print $handle "$st_now\n";
1190     close($handle);
1191 }
1192
1193 sub send_result
1194 {
1195
1196     # clean up temp file
1197     $extraconf = undef;
1198
1199     my $stage = shift;
1200
1201     my $ts = $now || time;
1202     my $status=shift || 0;
1203     my $log = shift || [];
1204     print "======== log passed to send_result ===========\n",@$log
1205       if ($verbose > 1);
1206
1207     unshift(@$log,
1208         "Last file mtime in snapshot: ",
1209         scalar(gmtime($current_snap)),
1210         " GMT\n","===================================================\n")
1211       unless ($from_source || !$current_snap);
1212
1213     my $log_data = join("",@$log);
1214     my $confsum = "";
1215     my $changed_this_run = "";
1216     my $changed_since_success = "";
1217     $changed_this_run = join("!",@changed_files)
1218       if @changed_files;
1219     $changed_since_success = join("!",@changed_since_success)
1220       if ($stage ne 'OK' && @changed_since_success);
1221
1222     if ($stage eq 'OK')
1223     {
1224         $confsum= $saved_config;
1225     }
1226     elsif ($stage !~ /CVS|Git|SCM/ )
1227     {
1228         $confsum = get_config_summary();
1229     }
1230     else
1231     {
1232         $confsum = get_script_config_dump();
1233     }
1234
1235     my $savedata = Data::Dumper->Dump(
1236         [
1237             $changed_this_run, $changed_since_success, $branch, $status,$stage,
1238             $animal, $ts,$log_data, $confsum, $target, $verbose, $secret
1239         ],
1240         [
1241             qw(changed_this_run changed_since_success branch status stage
1242               animal ts log_data confsum target verbose secret)
1243         ]
1244     );
1245
1246     my $lrname = $st_prefix . $logdirname;
1247
1248     # might happen if there is a CVS failure and have never got further
1249     mkdir $lrname unless -d $lrname;
1250
1251     my $txfname = "$lrname/web-txn.data";
1252     my $txdhandle;
1253     open($txdhandle,">$txfname");
1254     print $txdhandle $savedata;
1255     close($txdhandle);
1256
1257     if ($nosend || $stage eq 'CVS' || $stage eq 'CVS-status' )
1258     {
1259         print "Branch: $branch\n";
1260         if ($stage eq 'OK')
1261         {
1262             print "All stages succeeded\n";
1263             set_last('success.snap',$current_snap) unless $nostatus;
1264             exit(0);
1265         }
1266         else
1267         {
1268             print "Stage $stage failed with status $status\n";
1269             exit(1);
1270         }
1271     }
1272
1273     if ($stage !~ /CVS|Git|SCM|Pre-run-port-check/ )
1274     {
1275
1276         my @logfiles = glob("$lrname/*.log");
1277         my %mtimes = map { $_ => (stat $_)[9] } @logfiles;
1278         @logfiles =
1279           map { basename $_ }( sort { $mtimes{$a} <=> $mtimes{$b} } @logfiles );
1280         my $logfiles = join(' ',@logfiles);
1281         $tar_log_cmd =~ s/\*\.log/$logfiles/;
1282         chdir($lrname);
1283         system("$tar_log_cmd 2>&1 ");
1284         chdir($branch_root);
1285
1286     }
1287     else
1288     {
1289
1290         # these would be from an earlier run, since we
1291         # do cleanlogs() after the cvs stage
1292         # so don't send them.
1293         unlink "$lrname/runlogs.tgz";
1294     }
1295
1296     my $txstatus;
1297
1298     # this should now only apply to older Msys installs. All others should
1299     # be running with perl >= 5.8 since that's required to build exim
1300     # anyway
1301     if (!$^V or $^V lt v5.8.0)
1302     {
1303
1304         unless (-x "$aux_path/run_web_txn.pl")
1305         {
1306             print "Could not locate $aux_path/run_web_txn.pl\n";
1307             exit(1);
1308         }
1309
1310         system("$aux_path/run_web_txn.pl $lrname");
1311         $txstatus = $? >> 8;
1312     }
1313     else
1314     {
1315         $txstatus = EximBuild::WebTxn::run_web_txn($lrname) ? 0 : 1;
1316
1317     }
1318
1319     if ($txstatus)
1320     {
1321         print "Web txn failed with status: $txstatus\n";
1322
1323         # if the web txn fails, restore the timestamps
1324         # so we try again the next time.
1325         set_last('status',$last_status) unless $nostatus;
1326         set_last('run.snap',$last_run_snap) unless $nostatus;
1327         exit($txstatus);
1328     }
1329
1330     unless ($stage eq 'OK' || $quiet)
1331     {
1332         print "BuildFarm member $animal failed on $branch stage $stage\n";
1333     }
1334
1335     #   print "Success!\n",$response->content
1336     #           if $print_success;
1337
1338     set_last('success.snap',$current_snap) if ($stage eq 'OK' && !$nostatus);
1339
1340     exit 0;
1341 }
1342
1343 sub get_config_summary
1344 {
1345     my $handle;
1346     my $config = "";
1347     # unless ($using_msvc)
1348     # {
1349     #     open($handle,"$exim/config.log") || return undef;
1350     #     my $start = undef;
1351     #     while (<$handle>)
1352     #     {
1353     #         if (!$start && /created by PostgreSQL configure/)
1354     #         {
1355     #             $start=1;
1356     #             s/It was/This file was/;
1357     #         }
1358     #         next unless $start;
1359     #         last if /Core tests/;
1360     #         next if /^\#/;
1361     #         next if /= <?unknown>?/;
1362
1363     #         # split up long configure line
1364     #         if (m!\$.*configure.*--with! && length > 70)
1365     #         {
1366     #             my $pos = index($_," ",70);
1367     #             substr($_,$pos+1,0,"\\\n        ") if ($pos > 0);
1368     #             $pos = index($_," ",140);
1369     #             substr($_,$pos+1,0,"\\\n        ") if ($pos > 0);
1370     #             $pos = index($_," ",210);
1371     #             substr($_,$pos+1,0,"\\\n        ") if ($pos > 0);
1372     #         }
1373     #         $config .= $_;
1374     #     }
1375     #     close($handle);
1376     #     $config .=
1377     #       "\n========================================================\n";
1378     # }
1379     $config .= get_script_config_dump();
1380     return $config;
1381 }
1382
1383 sub get_script_config_dump
1384 {
1385     my $conf = {
1386         %EximBuild::conf,  # shallow copy
1387         script_version => $VERSION,
1388         invocation_args => \@invocation_args,
1389         steps_completed => $steps_completed,
1390         orig_env => $orig_env,
1391     };
1392     delete $conf->{secret};
1393     $Data::Dumper::Sortkeys = 1;
1394     return  Data::Dumper->Dump([$conf],['Script_Config']);
1395 }
1396
1397 sub scm_timeout
1398 {
1399     my $wait_time = shift;
1400     my $who_to_kill = getpgrp(0);
1401     my $sig = SIGTERM;
1402     $sig = -$sig;
1403     print "waiting $wait_time secs to time out process $who_to_kill\n"
1404       if $verbose;
1405     foreach my $sig (qw(INT TERM HUP QUIT))
1406     {
1407         $SIG{$sig}='DEFAULT';
1408     }
1409     sleep($wait_time);
1410     $SIG{TERM} = 'IGNORE'; # so we don't kill ourself, we're exiting anyway
1411     # kill the whole process group
1412     unless (kill $sig,$who_to_kill)
1413     {
1414         print "scm timeout kill failed\n";
1415     }
1416 }
1417
1418 sub spawn
1419 {
1420     my $coderef = shift;
1421     my $pid = fork;
1422     if (defined($pid) && $pid == 0)
1423     {
1424         exit &$coderef(@_);
1425     }
1426     return $pid;
1427 }
1428