Create branch register_actions.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine / HTTP / Restarter / Watcher.pm
index 7a6bd10..6aada3e 100644 (file)
@@ -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<B::Hooks::OP::Check::StashChange> is installed and
+can be used to detect when files are compiled. This is used internally
+to make the L<Moose> metaclass of any class being reloaded immutable.
+
+If L<B::Hooks::OP::Check::StashChange> 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<Catalyst>, L<Catalyst::Engine::HTTP::Restarter>, L<File::Modified>
 
 =head1 AUTHORS
 
-Sebastian Riedel, <sri@cpan.org>
-
-Andy Grundman, <andy@hybridized.org>
+Catalyst Contributors, see Catalyst.pm
 
 =head1 THANKS