package App::BundleDeps; use strict; use warnings; use local::lib (); use Cwd (); use ExtUtils::MakeMaker; use File::Spec; our $VERSION = '0.00006'; sub new { my ($class, @args) = @_; return bless { extlib => File::Spec->catdir(File::Spec->curdir, 'extlib'), notest => 1, @args }, $class; } # XXX - accessors inlined for minimal setup sub extlib { my $self = shift; $self->{extlib} = shift if @_; return $self->{extlib}; } sub notest { my $self = shift; $self->{notest} = shift if @_; return $self->{notest}; } sub deps { my $self = shift; $self->{deps} = shift if @_; return $self->{deps}; } # given a META.yml file (if no arguments, looks for a META.yml in the current # directory), does the proper dep bundling sub bundle_from_meta { my ($self, $file) = @_; $file ||= 'META.yml'; require YAML; require YAML::Dumper; # yeah, really, wtf my $meta = eval { YAML::LoadFile($file) }; if (! $meta || $@) { $@ ||= 'Unknown reason'; die "Failed to load file $file: $@." } my $requires = $meta->{requires} || {}; my $build_requires = $meta->{build_requires} || {}; my %deps = (%{ $requires }, %{ $build_requires }); $self->setup_deps(keys %deps); $self->bundle(); } sub bundle_modules { my($self, @modules) = @_; $self->setup_deps(@modules); $self->bundle(); } sub setup_deps { my ($self, @deps) = @_; @deps = grep { $_ ne 'perl' } sort @deps; $self->deps(\@deps); } sub bundle { my $self = shift; $self->bundle_deps(); } sub bundle_deps { my $self = shift; $ENV{PERL5LIB} = ''; # detach existent local::lib local::lib->setup_local_lib_for( Cwd::abs_path( $self->extlib ) ); # wtf: ExtUtils::MakeMaker shipped with Leopard is old if ($ExtUtils::MakeMaker::VERSION < 6.31) { $ENV{PERL_MM_OPT} =~ s/INSTALL_BASE=(.*)/$& INSTALLBASE=$1/; } # no man pages TODO: do the same with Module::Build $ENV{PERL_MM_OPT} .= " INSTALLMAN1DIR=none INSTALLMAN3DIR=none"; $ENV{PERL_MM_USE_DEFAULT} = 1; # Remove /opt from PATH: end users won't have ports $ENV{PATH} = join ":", grep !/^\/opt/, split /:/, $ENV{PATH}; my @cmd = ('cpanm', '--skip-installed'); if ($self->notest) { push @cmd, '--notest'; } push @cmd, @{ $self->deps }; system(@cmd); } 1; __END__ =head1 NAME App::BundleDeps - Bundle All Your Module Deps In A local::lib Dir =head1 SYNOPSIS use App::BundleDeps; App::BundleDeps->new()->bundle_from_meta( 'META.yml' ); # or my @modules = ( $module1, $module2, $module3, ... ); App::BundleDeps->new()->bundle_modules( @modules ); =head1 DESCRIPTION App::BundleDeps is a tool that allows you to "bundle" all your prerequisites in one local::lib installation. This is very useful when you want to deploy your application. =head1 SCENARIO Here, I'm going to show how to deploy a Catalyst application using App::BundleDeps and daemontools. So, suppose you checked out / downloaded your production ready application here: /home/apps/MyApp-0.89 Move to that directory, and create your bundle: cd /home/apps/MyApp-0.89 perl Makefile.PL NO_META=0 perl -MApp::BundleDeps -e 'App::BundleDeps->new->bundle_from_meta("META.yml");' You obviously have to have Makefile.PL setup so that it includes all the necessary prerequisites. At this point you should have a directory named C. Now prep your daemontools environemnt like so: First create the necessary directory structure (do it where svscan isn't watching) mkdir /var/lib/svscan/MyApp-0.89 Now move to that directory, and create a C script file. We'll assume we're deploying a fastcgi server: #!/bin/sh MYAPP_DIR=/home/apps/MyApp-0.89 PERL=/usr/local/bin/perl # I like to explicitly declare this, YMMV # XXX - You may have to specify MYAPP_HOME/MYAPP_CONFIG exec setuidgid app \ $PERL \ -Mlocal::lib=$MYAPP_DIR/extlib \ $MAYPP_DIR/script/myapp_fastcgi.pl \ -e \ -l /path/to/socket \ 2>&1 You should probably set up run script for logging, but see the daemontools manual for that. Now, create a symlink to where svscan is watching, typically /service or /etc/service: ln -s /var/lib/svscan/MyApp-0.89 /service/MyApp-0.89 And your service should start. This is especially useful, because when you come up with MyApp-0.90, you can simply follow the same steps and deploy MyApp-0.90 with its own set of prerequisites (you don't have to care what MyApp-0.89 was using, or what the system installed module versions are!). When you checked that MyApp-0.90 is receiving the requests, just follow the following steps to unstage your previous app: rm /service/MyApp-0.89 svc -dx /var/lib/svscan/MyApp-0.89 svc -dx /var/lib/svscan/MyApp-0.89/log Woohoo, done! =head1 CAVEATS =over 4 =item Prerequisites marked as "recommends" are not installed =item Tests will not be run =back =head1 AUTHOR Daisuke Maki - C<< >> Miyagawa Tatsuhiko did the actual useful bits. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See http://www.perl.com/perl/misc/Artistic.html =cut