# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package Apache::TestBuild; use strict; use warnings FATAL => 'all'; use subs qw(system chdir info warning); use Config; use File::Spec::Functions; use File::Path (); use Cwd (); use constant DRYRUN => 0; my @min_modules = qw(access auth log-config env mime setenvif mime autoindex dir alias); my %shared_modules = ( min => join(' ', @min_modules), ); my %configs = ( all => { 'apache-1.3' => [], 'httpd-2.0' => enable20(qw(modules=all proxy)), }, most => { 'apache-1.3' => [], 'httpd-2.0' => enable20(qw(modules=most)), }, min => { 'apache-1.3' => [], 'httpd-2.0' => enable20(@min_modules), }, exp => { 'apache-1.3' => [], 'httpd-2.0' => enable20(qw(example case_filter case_filter_in cache echo deflate bucketeer)), }, ); my %builds = ( default => { cflags => '-Wall', config => { 'apache-1.3' => [], 'httpd-2.0' => [], }, }, debug => { cflags => '-g', config => { 'apache-1.3' => [], 'httpd-2.0' => [qw(--enable-maintainer-mode)], }, }, prof => { cflags => '-pg -DGPROF', }, shared => { config => { 'apache-1.3' => [], 'httpd-2.0' => enable20_shared('all'), }, }, mostshared => { config => { 'apache-1.3' => [], 'httpd-2.0' => enable20_shared('most'), }, }, minshared => { config => { 'apache-1.3' => [], 'httpd-2.0' => enable20_shared('min'), }, }, static => { }, ); my %mpms = ( default => [qw(prefork worker)], MSWin32 => [qw(winnt)], ); my @cvs = qw(httpd-2.0 apache-1.3); my @dirs = qw(build tar src install); sub enable20 { [ map { "--enable-$_" } @_ ]; } sub enable20_shared { my $name = shift; my $modules = $shared_modules{$name} || $name; enable20(qq(mods-shared="$modules")); } sub default_mpms { $mpms{ $^O } || $mpms{'default'}; } sub default_dir { my($self, $dir) = @_; $self->{$dir} ||= catdir $self->{prefix}, $dir, } sub new { my $class = shift; #XXX: not generating a BUILD script yet #this way we can run: #perl Apache-Test/lib/Apache/TestBuild.pm --cvsroot=anon --foo=... require Apache::TestConfig; require Apache::TestTrace; Apache::TestTrace->import; my $self = bless { prefix => '/usr/local/apache', cwd => Cwd::cwd(), cvsroot => 'cvs.apache.org:/home/cvs', cvs => \@cvs, cvstag => "", ssldir => "", mpms => default_mpms(), make => $Config{make}, builds => {}, name => "", extra_config => { 'httpd-2.0' => [], }, @_, }, $class; #XXX if (my $c = $self->{extra_config}->{'2.0'}) { $self->{extra_config}->{'httpd-2.0'} = $c; } for my $dir (@dirs) { $self->default_dir($dir); } if ($self->{ssldir}) { push @{ $self->{extra_config}->{'httpd-2.0'} }, '--enable-ssl', "--with-ssl=$self->{ssldir}"; } $self; } sub init { my $self = shift; for my $dir (@dirs) { mkpath($self->{$dir}); } } use subs qw(symlink unlink); use File::Basename; use File::Find; sub symlink_tree { my $self = shift; my $httpd = 'httpd'; my $install = "$self->{install}/bin/$httpd"; my $source = "$self->{build}/.libs/$httpd"; unlink $install; symlink $source, $install; my %dir = (apr => 'apr', aprutil => 'apr-util'); for my $libname (qw(apr aprutil)) { my $lib = "lib$libname.so.0.0.0"; my $install = "$self->{install}/lib/$lib"; my $source = "$self->{build}/srclib/$dir{$libname}/.libs/$lib"; unlink $install; symlink $source, $install; } $install = "$self->{install}/modules"; $source = "$self->{build}/modules"; for (<$install/*.so>) { unlink $_; } finddepth(sub { return unless /\.so$/; my $file = "$File::Find::dir/$_"; symlink $file, "$install/$_"; }, $source); } sub unlink { my $file = shift; if (-e $file) { print "unlink $file\n"; } else { print "$file does not exist\n"; } CORE::unlink($file); } sub symlink { my($from, $to) = @_; print "symlink $from => $to\n"; unless (-e $from) { print "source $from does not exist\n"; } my $base = dirname $to; unless (-e $base) { print "target dir $base does not exist\n"; } CORE::symlink($from, $to) or die $!; } sub cvs { my $self = shift; my $cmd = "cvs -d $self->{cvsroot} @_"; if (DRYRUN) { info "$cmd"; } else { system $cmd; } } my %cvs_names = ( '2.0' => 'httpd-2.0', '1.3' => 'apache-1.3', ); my %cvs_snames = ( '2.0' => 'httpd', '1.3' => 'apache', ); sub cvs_up { my($self, $version) = @_; my $name = $cvs_names{$version}; my $dir = $self->srcdir($version); if ($self->{cvsroot} eq 'anon') { $self->{cvsroot} = ':pserver:anoncvs@cvs.apache.org:/home/cvspublic'; unless (-d $dir) { #XXX do something better than doesn't require prompt if #we already have an entry in ~/.cvspass #$self->cvs('login'); warning "may need to run the following command ", "(password is 'anoncvs')"; warning "cvs -d $self->{cvsroot} login"; } } if (-d $dir) { chdir $dir; $self->cvs(up => "-dP $self->{cvstag}"); return; } my $co = checkout($name); $self->$co($name, $dir); my $post = post_checkout($name); $self->$post($name, $dir); } sub checkout_httpd_2_0 { my($self, $name, $dir) = @_; my $tag = $self->{cvstag}; $self->cvs(co => "-d $dir $tag $name"); chdir "$dir/srclib"; $self->cvs(co => "$tag apr apr-util"); } sub checkout_apache_1_3 { my($self, $name, $dir) = @_; $self->cvs(co => "-d $dir $self->{cvstag} $name"); } sub post_checkout_httpd_2_0 { my($self, $name, $dir) = @_; } sub post_checkout_apache_1_3 { } sub canon { my $name = shift; return $name unless $name; $name =~ s/[.-]/_/g; $name; } sub checkout { my $name = canon(shift); \&{"checkout_$name"}; } sub post_checkout { my $name = canon(shift); \&{"post_checkout_$name"}; } sub cvs_update { my $self = shift; my $cvs = shift || $self->{cvs}; chdir $self->{src}; for my $name (@$cvs) { $self->cvs_up($name); } } sub merge_build { my($self, $version, $builds, $configs) = @_; my $b = { cflags => $builds{default}->{cflags}, config => [ @{ $builds{default}->{config}->{$version} } ], }; for my $name (@$builds) { next if $name eq 'default'; #already have this if (my $flags = $builds{$name}->{cflags}) { $b->{cflags} .= " $flags"; } if (my $cfg = $builds{$name}->{config}) { if (my $vcfg = $cfg->{$version}) { push @{ $b->{config} }, @$vcfg; } } } for my $name (@$configs) { my $cfg = $configs{$name}->{$version}; next unless $cfg; push @{ $b->{config} }, @$cfg; } if (my $ex = $self->{extra_config}->{$version}) { push @{ $b->{config} }, @$ex; } if (my $ex = $self->{extra_cflags}->{$version}) { $b->{config} .= " $ex"; } $b; } my @srclib_dirs = qw( apr apr-util apr-util/xml/expat pcre ); sub install_name { my($self, $builds, $configs, $mpm) = @_; return $self->{name} if $self->{name}; my $name = join '-', $mpm, @$builds, @$configs; if (my $tag = $self->cvs_name) { $name .= "-$tag"; } $name; } #currently the httpd-2.0 build does not properly support static linking #of ssl libs, force the issue sub add_ssl_libs { my $self = shift; my $ssldir = $self->{ssldir}; return unless $ssldir and -d $ssldir; my $name = $self->{current_install_name}; my $ssl_mod = "$name/modules/ssl"; info "editing $ssl_mod/modules.mk"; if (DRYRUN) { return; } my $ssl_mk = "$self->{build}/$ssl_mod/modules.mk"; open my $fh, $ssl_mk or die "open $ssl_mk: $!"; my @lines = <$fh>; close $fh; for (@lines) { next unless /SH_LINK/; chomp; $_ .= " -L$ssldir -lssl -lcrypto\n"; info 'added ssl libs'; last; } open $fh, '>', $ssl_mk or die $!; print $fh join "\n", @lines; close $fh; } sub cvs_name { my $self = shift; if (my $tag = $self->{cvstag}) { $tag =~ s/^-[DAr]//; $tag =~ s/\"//g; $tag =~ s,[/ :],_,g; #-D"03/29/02 07:00pm" return $tag; } return ""; } sub srcdir { my($self, $src) = @_; my $prefix = ""; if ($src =~ s/^(apache|httpd)-//) { $prefix = $1; } else { $prefix = $cvs_snames{$src}; } if ($src =~ /^\d\.\d$/) { #release version will be \d\.\d\.\d+ if (my $tag = $self->cvs_name) { $src .= "-$tag"; } $src .= '-cvs'; } join '-', $prefix, $src; } sub configure_httpd_2_0 { my($self, $src, $builds, $configs, $mpm) = @_; $src = $self->srcdir($src); chdir $self->{build}; my $name = $self->install_name($builds, $configs, $mpm); $self->{current_install_name} = $name; $self->{builds}->{$name} = 1; if ($self->{fresh}) { rmtree($name); } else { if (-e "$name/.DONE") { warning "$name already configured"; warning "rm $name/.DONE to force"; return; } } my $build = $self->merge_build('httpd-2.0', $builds, $configs); $ENV{CFLAGS} = $build->{cflags}; info "CFLAGS=$ENV{CFLAGS}"; my $prefix = "$self->{install}/$name"; rmtree($prefix) if $self->{fresh}; my $source = "$self->{src}/$src"; my @args = ("--prefix=$prefix", "--with-mpm=$mpm", "--srcdir=$source", @{ $build->{config} }); chdir $source; system "./buildconf"; my $cmd = "$source/configure @args"; chdir $self->{build}; mkpath($name); chdir $name; for my $dir (@srclib_dirs) { mkpath("srclib/$dir"); } for my $dir (qw(build docs/conf)) { mkpath($dir); } system $cmd; open FH, ">.DONE" or die "open .DONE: $!"; print FH scalar localtime; close FH; chdir $self->{prefix}; $self->add_ssl_libs; } sub make { my($self, @cmds) = @_; push @cmds, 'all' unless @cmds; for my $name (keys %{ $self->{builds} }) { chdir "$self->{build}/$name"; for my $cmd (@cmds) { system "$self->{make} $cmd"; } } } sub system { my $cmd = "@_"; info $cmd; return if DRYRUN; unless (CORE::system($cmd) == 0) { my $status = $? >> 8; die "system $cmd failed (exit status=$status)"; } } sub chdir { my $dir = shift; info "chdir $dir"; CORE::chdir $dir; } sub mkpath { my $dir = shift; return if -d $dir; info "mkpath $dir"; return if DRYRUN; File::Path::mkpath([$dir], 1, 0755); } sub rmtree { my $dir = shift; return unless -d $dir; info "rmtree $dir"; return if DRYRUN; File::Path::rmtree([$dir], 1, 1); } sub generate_script { my($class, $file) = @_; $file ||= catfile 't', 'BUILD'; my $content = join '', ; Apache::Test::basic_config()->write_perlscript($file, $content); } unless (caller) { $INC{'Apache/TestBuild.pm'} = __FILE__; eval join '', ; die $@ if $@; } 1; __DATA__ use strict; use warnings FATAL => 'all'; use lib qw(Apache-Test/lib); use Apache::TestBuild (); use Getopt::Long qw(GetOptions); use Cwd (); my %options = ( prefix => "checkout/build/install prefix", ssldir => "enable ssl with given directory", cvstag => "checkout with given cvs tag", cvsroot => "use 'anon' for anonymous cvs", version => "apache version (e.g. '2.0')", mpms => "MPMs to build (e.g. 'prefork')", flavor => "build flavor (e.g. 'debug shared')", modules => "enable modules (e.g. 'all exp')", name => "change name of the build/install directory", ); my %opts; Getopt::Long::Configure(qw(pass_through)); #XXX: could be smarter here, being lazy for the moment GetOptions(\%opts, map "$_=s", sort keys %options); if (@ARGV) { print "passing extra args to configure: @ARGV\n"; } my $home = $ENV{HOME}; $opts{prefix} ||= join '/', Cwd::cwd(), 'farm'; #$opts{ssldir} ||= ''; #$opts{cvstag} ||= ''; #$opts{cvsroot} ||= ''; $opts{version} ||= '2.0'; $opts{mpms} ||= 'prefork'; $opts{flavor} ||= 'debug-shared'; $opts{modules} ||= 'all-exp'; #my @versions = qw(2.0); #my @mpms = qw(prefork worker perchild); #my @flavors = ([qw(debug shared)], [qw(prof shared)], # [qw(debug static)], [qw(prof static)]); #my @modules = ([qw(all exp)]); my $split = sub { split '-', delete $opts{ $_[0] } }; my @versions = $opts{version}; my @mpms = $split->('mpms'); my @flavors = ([ $split->('flavor') ]); my @modules = ([ $split->('modules') ]); my $tb = Apache::TestBuild->new(fresh => 1, %opts, extra_config => { $opts{version} => \@ARGV, }); $tb->init; for my $version (@versions) { $tb->cvs_update([ $version ]); for my $mpm (@mpms) { for my $flavor (@flavors) { for my $mods (@modules) { $tb->configure_httpd_2_0($version, $flavor, $mods, $mpm); $tb->make(qw(all install)); } } } }