#!perl use strict; use warnings; use Cwd 'abs_path'; use File::Spec; =head1 NAME buildsvk.pl - packaging svk =head1 SYNOPSIS buildsvk.pl # build svk from src/SVK-version.tar.gz dist file buildsvk.pl .. # build svk from the toplevel tree of current checkout =head1 DESCRIPTION Put the CPAN dist files under src and C will create a build tarball which bundles the svn libraries. To use, just untar and symlink C under it to a bin path. There will also be a maketest script for you to run the included tests. If you are on win32, This will build a bundled installer for you including svn libraries and all necessary perl core modules. you need the strawberry-perl.zip and svn-win*{,_pl}.zip under current directory before you run buildsvk.pl. strawberry-perl.zip can be obtained by zipping the freshly installed strawberry-perl for the moment. You will also need NSIS installed under $PATH or C:\program files\nsis. You also need the unzip.exe binary in your path (FIXME: just use our extract method) =head1 TODO =over 4 =item * README file in the build. =item * cleanup win32 build code to be more like unix build. =back =cut my $build = SVK::Build->new; my $t = time(); $build->prepare_perl(); $build->prepare_svn_core(); $build->build_module('libwin32') if $^O eq 'MSWin32'; $build->build_module($_) for qw(Scalar-List-Utils Class-Autouse version Sub-Uplevel Test-Simple Test-Exception Data-Hierarchy PerlIO-via-dynamic PerlIO-via-symlink SVN-Simple PerlIO-eol Algorithm-Diff Algorithm-Annotate Pod-Escapes Pod-Simple IO-Digest TimeDate Getopt-Long Encode PathTools YAML-Syck Locale-Maketext-Simple App-CLI List-MoreUtils Path-Class Class-Data-Inheritable Class-Accessor UNIVERSAL-require File-Temp Log-Log4perl Time-Progress LWP-UserAgent); $build->build_module($_) for qw(Locale-Maketext-Lexicon TermReadKey IO-Pager); $build->build_module($_) for qw(File-chdir SVN-Mirror); $build->build_module($_) for qw(FreezeThaw); $build->build_module($_) for qw(PerlIO-via-Bzip2 Compress-Bzip2 PerlIO-gzip SVN-Dump); my $svkroot = shift; if ($svkroot) { $build->perlmake_install($svkroot); } else { $build->build_module('SVK'); ($svkroot) = glob($build->build_dir.'/SVK-*'); } $build->prepare_dist($svkroot); warn 'build finished - '.(time() - $t); exit 0; package SVK::Build; use Archive::Extract; use Archive::Tar; use Env::Path; use File::Path (qw(mkpath rmtree)); use File::chdir; use File::Copy 'copy'; use File::Temp 'tempdir'; our $BUILD_BASE; sub prepare_perl { 1 }; sub prepare_svn_core { my $self = shift; my $output = `ldd \`which svn\``; for ($output =~ m/^.*$/mg) { my ($lib, $file) = m/(\S.*?) => (\S.*?)\s/ or next; if ($lib =~ m/libsvn_*/) { warn "$lib $file"; copy($file, $self->build_dir); } } $self->prepare_svn_perl_binding(); } sub prepare_svn_perl_binding { my $self = shift; my $output = `ldd \`locate -l 1 _Core.so\``; for ($output =~ m/^.*$/mg) { my ($lib, $file) = m/(\S.*?) => (\S.*?)\s/ or next; if ($lib =~ m/libsvn_swig*/ || $lib =~ m/libapr*/) { warn "$lib $file"; copy($file, $self->build_dir); } } my @SVNCoreModules = ( 'Base', 'Client', 'Core', 'Delta', 'Fs', 'Ra', 'Repos', 'Wc'); for my $prefix (@INC) { for my $SVNdir ("/SVN/","/auto/SVN/") { my $fullpath = $prefix.$SVNdir; if (-d $fullpath) { mkpath [$self->perldest.$SVNdir]; for my $module (@SVNCoreModules) { my $file = $module.".pm"; if (-f $fullpath.$file) { warn $file; copy($fullpath.$file, $self->perldest.$SVNdir); } if (-d $fullpath.'_'.$module) { mkpath [$self->perldest.$SVNdir.'_'.$module]; for my $ext (".bs", ".so") { my $file = $fullpath.'_'.$module.'/_'.$module.$ext; if (-f $file) { warn $file; copy($file, $self->perldest.$SVNdir.'_'.$module); } } } } } } } } sub build_dir { shift->build_base ."/dest"; } sub build_base { $BUILD_BASE ||= tempdir(); } sub prepare_build_dir { my $self = shift; mkpath [$self->build_dir]; } sub new { my $class = shift; if ($^O eq 'MSWin32') { $class .= '::Win32'; } elsif ($^O eq 'darwin') { $class .= "::Darwin"; } my $self = bless {}, $class; $self->prepare_build_dir; return $self; } sub extract { my $self = shift; my ($arc, $to) = @_; my $ae = Archive::Extract->new( archive => $arc ); $ae->extract( to => $to || $self->build_base ) or die $ae->error; } sub perl { [ $^X, '-I'.$_[0]->perldest ] } sub make { 'make' } sub build_module { my $self = shift; my $module = shift; my $subdir = shift; # XXX: try to match version number only for the glob here my ($dir) = glob($self->build_dir."/$module-*"); rmtree [$dir] if $dir; my ($file) = glob("src/$module-*") or die $module; $self->extract($file); ($dir) = glob($self->build_base."/$module-*"); $self->perlmake_install( $subdir ? "$dir/$subdir" : $dir ); } sub perlmake_install { my ($self, $dir) = @_; my $PERLDEST = $self->perldest; my $PERLDESTARCH = $PERLDEST; local $CWD = $dir; warn "$CWD\n"; system @{$self->perl}, qw(Makefile.PL INSTALLDIRS=perl), "INSTALLARCHLIB=$PERLDESTARCH", "INSTALLPRIVLIB=$PERLDEST", "INSTALLBIN=$PERLDEST/../bin", "INSTALLSCRIPT=$PERLDEST/../bin", "INSTALLMAN1DIR=$PERLDEST/../man/man1", "INSTALLMAN3DIR=$PERLDEST/../man/man3"; $ENV{PERL_EXTUTILS_AUTOINSTALL} = '--skipdeps'; system $self->make, qw( all install ) ; } sub perldest { my $self = shift; $self->build_dir.'/perl'; } sub test_files { my $self = shift; my $toplevel = shift; my @tests; open my $fh, "$toplevel/MANIFEST" or die "Could not create $toplevel/MANIFEST: ".$!; while (<$fh>) { chomp; next unless m{^t/}; push @tests, $_; } return @tests; } sub prepare_dist { my $self = shift; my $toplevel = shift; copy('svk-wrapper' => $self->build_dir."/svk"); chmod 0755, $self->build_dir."/svk"; for my $file ($self->test_files($toplevel)) { my (undef, $dir, undef) = File::Spec->splitpath($file); mkpath [ $self->build_dir."/$dir" ]; copy($toplevel.'/'.$file => $self->build_dir."/$file"); } copy('maketest' => $self->build_dir."/maketest"); chmod 0755, $self->build_dir."/maketest"; my $version = $self->get_svk_version($toplevel); rename($self->build_dir => $self->build_base.'/svk-'.$version); $self->build_archive( 'svk-'.$version); } sub get_svk_version { my ($self, $toplevel) = @_; my $version = eval { local @INC = @INC; unshift @INC, "$toplevel/lib"; require SVK::Version; SVK->VERSION; }; } sub build_archive { my $self = shift; my $path = shift; my $olddir = $CWD; { local $CWD = $self->build_base; warn "In ".$self->build_base . " looking for ". $path; my @cmd = ( 'tar', 'czvf' , "$olddir/$path.tgz", $path); system( @cmd); if ($!) { die "Failed to create tarball: ". $! . join (' ',@cmd);} } if (-f "$path.tgz" ) { print "Congratulations! You have a new build of $path in ".$olddir."/".$path.".tgz\n"; } else { warn "Couldn't build ".$self->build_base."/$path into a tarball\n"; } } package SVK::Build::Win32; use base 'SVK::Build'; use Cwd 'abs_path'; use File::Path (qw(rmtree)); use File::Spec; use File::Copy 'move'; use constant svn_version => '1.5.0'; sub build_dir { 'c:/tmp/svk-build'; } sub build_base { 'c:/tmp/svk-build'; } sub perl { my $self = shift; [abs_path(File::Spec->catfile($self->build_dir, qw(strawberry-perl perl bin perl.exe))) ]; } sub make { 'dmake' } sub perlmake_install { my $self = shift; local %ENV = %ENV; Env::Path->PATH->Assign( map { abs_path(File::Spec->catfile($self->build_dir, 'strawberry-perl', $_, 'bin')) } qw(perl c)); return $self->SUPER::perlmake_install(@_); } sub perldest { File::Spec->catdir(abs_path($_[0]->build_dir), qw(strawberry-perl perl lib)); } sub prepare_perl { my $self = shift; if (-d $self->perldest) { warn "found strawberry perl, remove ".$self->perldest." for clean build.\n"; return 1; } mkdir('strawberry-perl'); $self->extract('strawberry-perl.zip' => $self->build_dir.'/strawberry-perl'); } sub prepare_svn_core { my $self = shift; return 1 if -e File::Spec->catfile($self->build_dir, 'strawberry-perl', 'perl', 'lib', 'SVN' ); $self->extract("svn-win32-@{[svn_version]}.zip"); $self->extract("svn-win32-@{[svn_version]}_pl.zip"); my $svnperl = File::Spec->catfile($self->build_dir, "svn-win32-@{[svn_version]}", 'perl', 'site', 'lib' ); my $strperl = File::Spec->catfile($self->build_dir, 'strawberry-perl', 'perl', 'lib' ); rename(File::Spec->catfile($svnperl, "SVN") => File::Spec->catfile($strperl, "SVN")) or die $!; rename(File::Spec->catfile($svnperl, "auto", "SVN") => File::Spec->catfile($strperl, "auto", "SVN")) or die $!; move($_ => File::Spec->catfile($self->build_dir, 'strawberry-perl', 'perl', 'bin')) for glob($self->build_dir."/svn-win32-@{[svn_version]}/bin/*.dll"); move($self->build_dir."/svn-win32-@{[svn_version]}/iconv" => File::Spec->catfile($self->build_dir, 'strawberry-perl', 'iconv')) } sub prepare_dist { my $self = shift; my $toplevel = shift; my @paroptions; open my $tmpfh, '>test_files.txt' or die $!; print $tmpfh map { "$toplevel/$_;site/$_\n" } $self->test_files($toplevel); close $tmpfh; open my $fh, 'win32/paroptions.txt' or die $!; while (<$fh>) { next if m/^#/; chomp; push @paroptions, split(/ /,$_) }; my @bundled_dll = map { s{.*\\}{}; $_ } glob(File::Spec->catfile($self->build_dir, 'strawberry-perl', 'perl', 'bin').'\*.dll'); push @paroptions, -o => $self->build_dir."/SVK.par", -a => "$toplevel/lib/SVK/Help;lib/SVK/Help", -a => "$toplevel/lib/SVK/I18N;lib/SVK/I18N", -a => $self->perldest."/auto/POSIX;lib/auto/POSIX", -I => "$toplevel/lib", -I => $self->perldest, (map { (-a => File::Spec->catfile($self->build_dir, 'strawberry-perl', 'perl', 'bin', $_).";bin/$_") } qw(perl.exe prove.bat), @bundled_dll ), -a => "$toplevel/blib/script/svk;bin/svk", -a => "$toplevel/pkg/win32/maketest.bat;win32/maketest.bat", -a => "$toplevel/pkg/win32/svk.ico;win32/svk.ico", -a => "$toplevel/pkg/win32/svk-uninstall.ico;win32/svk-uninstall.ico", -a => "$toplevel/pkg/win32/svk.nsi;win32/svk.nsi", -a => "$toplevel/pkg/win32/Path.nsh;win32/Path.nsh", -a => File::Spec->catfile($self->build_dir, 'strawberry-perl', 'iconv').";iconv", -a => "$toplevel/contrib;site/contrib", -a => "$toplevel/utils;site/utils", -a => "$toplevel/README;README", -a => "$toplevel/CHANGES;CHANGES", -a => "$toplevel/ARTISTIC;ARTISTIC", -a => "$toplevel/COPYING;COPYING", -A => "test_files.txt"; # map { -a => "$toplevel/$_;site/$_" } $self->test_files($toplevel); rmtree ['build'] if -d 'build'; mkdir('build'); $ENV{PAR_VERBATIM} = 1; # dynloader gets upset and gives warnings if it has #line system('pp', @paroptions, "$toplevel/blib/script/svk"); eval { $self->extract( $self->build_dir."/SVK.par" => $self->build_dir."/build" ); } or warn $@; $self->build_archive( $self->build_dir."/build", $self->get_svk_version($toplevel)); } sub build_archive { my ($self, $dir, $version) = @_; Env::Path->PATH->Prepend("C:/Program Files/NSIS"); system('makensis', "/X !define MUI_VERSION $version", "$dir/win32/svk.nsi"); my ($file) = glob($self->build_dir."/build/*.exe"); if ($file) { my (undef, $dir, $name) = File::Spec->splitpath($file); rename($file => $name); print "Congratulations! You have a new build.\n"; } else { warn "Couldn't build installer.\n"; } } package SVK::Build::Darwin; use base 'SVK::Build'; use File::Copy 'copy'; sub prepare_svn_core { my $self = shift; my $output = `otool -L \`which svn\``; for ($output =~ m/^.*$/mg) { my ($lib) = m/^\s*(.*?)\s/ or next; next if $lib =~ /^\/(?:System|usr\/lib)/; warn $lib; copy($lib, $self->build_dir); } }