# 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 '', <DATA>;
Apache::Test::basic_config()->write_perlscript($file, $content);
}
unless (caller) {
$INC{'Apache/TestBuild.pm'} = __FILE__;
eval join '', <DATA>;
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));
}
}
}
}