X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FEngine%2FHTTP%2FRestarter%2FWatcher.pm;h=6aada3e06d61fd03acd674a4adcc6d8d29b78fab;hp=7a6bd10550611c36cc9817752d6e9439362af944;hb=ae29b412955743885e80350085167b54b69672da;hpb=65586a18685daa023a1a623cf228943e4a4f830d diff --git a/lib/Catalyst/Engine/HTTP/Restarter/Watcher.pm b/lib/Catalyst/Engine/HTTP/Restarter/Watcher.pm index 7a6bd10..6aada3e 100644 --- a/lib/Catalyst/Engine/HTTP/Restarter/Watcher.pm +++ b/lib/Catalyst/Engine/HTTP/Restarter/Watcher.pm @@ -1,37 +1,42 @@ package Catalyst::Engine::HTTP::Restarter::Watcher; -use strict; -use warnings; -use base 'Class::Accessor::Fast'; +use Moose; +with 'MooseX::Emulate::Class::Accessor::Fast'; + use File::Find; use File::Modified; use File::Spec; use Time::HiRes qw/sleep/; +use Moose::Util qw/find_meta/; +use namespace::clean -except => 'meta'; + +BEGIN { + # If we can detect stash changes, then we do magic + # to make their metaclass mutable (if they have one) + # so that restarting works as expected. + eval { require B::Hooks::OP::Check::StashChange; }; + *DETECT_PACKAGE_COMPILATION = $@ + ? sub () { 0 } + : sub () { 1 } +} -__PACKAGE__->mk_accessors( qw/delay - directory - modified - regex - watch_list/ ); +has delay => (is => 'rw'); +has regex => (is => 'rw'); +has modified => (is => 'rw'); +has directory => (is => 'rw'); +has watch_list => (is => 'rw'); +has follow_symlinks => (is => 'rw'); -sub new { - my ( $class, %args ) = @_; - - my $self = { %args }; - - bless $self, $class; - - $self->_init; - - return $self; +sub BUILD { + shift->_init; } sub _init { my $self = shift; - + my $watch_list = $self->_index_directory; - $self->watch_list( $watch_list ); - + $self->watch_list($watch_list); + $self->modified( File::Modified->new( method => 'mtime', @@ -42,46 +47,50 @@ sub _init { sub watch { my $self = shift; - + my @changes; my @changed_files; - sleep $self->delay || 1; - + my $delay = ( defined $self->delay ) ? $self->delay : 1; + + sleep $delay if $delay > 0; + eval { @changes = $self->modified->changed }; - if ( $@ ) { + if ($@) { + # File::Modified will die if a file is deleted. my ($deleted_file) = $@ =~ /stat '(.+)'/; push @changed_files, $deleted_file || 'unknown file'; } - - if ( @changes ) { + + if (@changes) { + # update all mtime information $self->modified->update; - + # check if any files were changed @changed_files = grep { -f $_ } @changes; - + # Check if only directories were changed. This means # a new file was created. - unless ( @changed_files ) { + unless (@changed_files) { + # re-index to find new files my $new_watch = $self->_index_directory; - + # look through the new list for new files my $old_watch = $self->watch_list; - @changed_files = grep { ! defined $old_watch->{$_} } - keys %{ $new_watch }; - + @changed_files = grep { !defined $old_watch->{$_} } + keys %{$new_watch}; + return unless @changed_files; } # Test modified pm's - for my $file ( @changed_files ) { + for my $file (@changed_files) { next unless $file =~ /\.pm$/; if ( my $error = $self->_test($file) ) { - print STDERR - qq/File "$file" modified, not restarting\n\n/; + print STDERR qq/File "$file" modified, not restarting\n\n/; print STDERR '*' x 80, "\n"; print STDERR $error; print STDERR '*' x 80, "\n"; @@ -89,17 +98,19 @@ sub watch { } } } - + return @changed_files; } sub _index_directory { my $self = shift; - - my $dir = $self->directory || die "No directory specified"; - my $regex = $self->regex || '\.pm$'; + + my $dir = $self->directory; + die "No directory specified" if !$dir or ref($dir) && !@{$dir}; + + my $regex = $self->regex || '\.pm$'; my %list; - + finddepth( { wanted => sub { @@ -108,32 +119,48 @@ sub _index_directory { return unless -f $file; $file =~ s{/script/..}{}; $list{$file} = 1; - + # also watch the directory for changes my $cur_dir = File::Spec->rel2abs($File::Find::dir); - $cur_dir =~ s{/script/..}{}; + $cur_dir =~ s{/script/..}{}; $list{$cur_dir} = 1; }, + follow_fast => $self->follow_symlinks ? 1 : 0, no_chdir => 1 }, - $dir + ref $dir eq 'ARRAY' ? @{$dir} : $dir ); return \%list; } sub _test { my ( $self, $file ) = @_; - - delete $INC{$file}; + + my $id; + if (DETECT_PACKAGE_COMPILATION) { + $id = B::Hooks::OP::Check::StashChange::register(sub { + my ($new, $old) = @_; + my $meta = find_meta($new); + if ($meta) { # A little paranoia here - Moose::Meta::Role has neither of these methods. + my $is_immutable = $meta->can('is_immutable'); + my $make_mutable = $meta->can('make_mutable'); + $meta->$make_mutable() if $is_immutable && $make_mutable && $meta->$is_immutable(); + } + }); + } + + delete $INC{$file}; # Remove from %INC so it will reload local $SIG{__WARN__} = sub { }; - + open my $olderr, '>&STDERR'; open STDERR, '>', File::Spec->devnull; eval "require '$file'"; open STDERR, '>&', $olderr; - + + B::Hooks::OP::Check::StashChange::unregister($id) if $id; + return ($@) ? $@ : 0; -} +} 1; __END__ @@ -147,7 +174,7 @@ files my $watcher = Catalyst::Engine::HTTP::Restarter::Watcher->new( directory => '/path/to/MyApp', - regex => '\.yml$|\.yaml$|\.pm$', + regex => '\.yml$|\.yaml$|\.conf|\.pm$', delay => 1, ); @@ -172,15 +199,24 @@ Creates a new Watcher object. Returns a list of files that have been added, deleted, or changed since the last time watch was called. +=head2 DETECT_PACKAGE_COMPILATION + +Returns true if L is installed and +can be used to detect when files are compiled. This is used internally +to make the L metaclass of any class being reloaded immutable. + +If L is not installed, then the +restarter makes all application components immutable. This covers the +simple case, but is less useful if you're using Moose in components +outside Catalyst's namespaces, but inside your application directory. + =head1 SEE ALSO L, L, L =head1 AUTHORS -Sebastian Riedel, - -Andy Grundman, +Catalyst Contributors, see Catalyst.pm =head1 THANKS