diff options
author | Lucas Kanashiro <kanashiro.duarte@gmail.com> | 2016-02-17 21:49:13 -0200 |
---|---|---|
committer | Lucas Kanashiro <kanashiro.duarte@gmail.com> | 2016-02-17 21:49:13 -0200 |
commit | 8f76b7628d422cc884b680909a3de8d8773ce8f2 (patch) | |
tree | bb3d16a76dfecc5893303a3933c55e6f7d96a5ab | |
parent | 51ff20c794b8b27ee50f0199739c8d1d3f31107f (diff) | |
parent | ad1dced46cee68ddce1b701e8c774ba1e5c6146b (diff) |
Merge tag 'upstream/0.09'
Upstream version 0.09
# gpg: Signature made Wed 17 Feb 2016 09:49:09 PM BRST using RSA key ID 9883C97C
# gpg: Good signature from "Lucas Kanashiro <kanashiro.duarte@gmail.com>"
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg: There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 8ED6 C3F8 BAC9 DB7F C130 A870 F823 A272 9883 C97C
-rw-r--r-- | Changes | 14 | ||||
-rw-r--r-- | LICENSE | 8 | ||||
-rw-r--r-- | MANIFEST | 1 | ||||
-rw-r--r-- | META.json | 19 | ||||
-rw-r--r-- | META.yml | 10 | ||||
-rw-r--r-- | Makefile.PL | 29 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | README.pod | 124 | ||||
-rwxr-xr-x | bin/inotify-hookable | 2 | ||||
-rwxr-xr-x | lib/App/Inotify/Hookable.pm | 28 | ||||
-rw-r--r-- | t/00-compile.t | 121 |
11 files changed, 272 insertions, 88 deletions
@@ -1,5 +1,19 @@ Revision history for App::Inotify::Hookable +0.09 2016-02-15 20:33:43 + + - I forgot to write a changelog for 0.08, this release is + identical to it except I'm now updating the Changes file: + + - Fixed a common race condition bug where inotify-hookable would + just plain die if a directory it tried to watch had been deleted + (happens e.g. in the middle of "git checkout"). + + Now we just ignore that and move on, before the whola watcher + would just stop. + +0.08 2016-02-15 20:29:38 + 0.07 2012-12-21 16:18:36 - Use find(1) again instead of File::Find::Rule, it's noticably @@ -1,4 +1,4 @@ -This software is copyright (c) 2012 by Ævar Arnfjörð Bjarmason. +This software is copyright (c) 2016 by Ævar Arnfjörð Bjarmason. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. @@ -12,7 +12,7 @@ b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- -This software is Copyright (c) 2012 by Ævar Arnfjörð Bjarmason. +This software is Copyright (c) 2016 by Ævar Arnfjörð Bjarmason. This is free software, licensed under: @@ -22,7 +22,7 @@ This is free software, licensed under: Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. - 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -272,7 +272,7 @@ That's all there is to it! --- The Artistic License 1.0 --- -This software is Copyright (c) 2012 by Ævar Arnfjörð Bjarmason. +This software is Copyright (c) 2016 by Ævar Arnfjörð Bjarmason. This is free software, licensed under: @@ -5,6 +5,7 @@ META.json META.yml Makefile.PL README +README.pod bin/inotify-hookable dist.ini lib/App/Inotify/Hookable.pm @@ -1,10 +1,10 @@ { "abstract" : "blocking command-line interface to inotify", "author" : [ - "\u00c3\u0086var Arnfj\u00c3\u00b6r\u00c3\u00b0 Bjarmason <avar@cpan.org>" + "\u00c6var Arnfj\u00f6r\u00f0 Bjarmason <avar@cpan.org>" ], "dynamic_config" : 0, - "generated_by" : "Dist::Zilla version 4.300021, CPAN::Meta::Converter version 2.120921", + "generated_by" : "Dist::Zilla version 4.300040, CPAN::Meta::Converter version 2.143240", "license" : [ "perl_5" ], @@ -20,6 +20,11 @@ "ExtUtils::MakeMaker" : "6.30" } }, + "develop" : { + "requires" : { + "Test::Pod" : "1.41" + } + }, "runtime" : { "requires" : { "Class::Inspector" : "0", @@ -39,9 +44,11 @@ }, "test" : { "requires" : { - "File::Find" : "0", - "File::Temp" : "0", - "Test::More" : "0" + "File::Spec" : "0", + "IO::Handle" : "0", + "IPC::Open3" : "0", + "Test::More" : "0", + "perl" : "5.006" } } }, @@ -61,7 +68,7 @@ "web" : "http://github.com/avar/app-inotify-hookable" } }, - "version" : "0.07", + "version" : "0.09", "x_authority" : "cpan:AVAR" } @@ -3,13 +3,15 @@ abstract: 'blocking command-line interface to inotify' author: - 'Ævar Arnfjörð Bjarmason <avar@cpan.org>' build_requires: - File::Find: 0 - File::Temp: 0 + File::Spec: 0 + IO::Handle: 0 + IPC::Open3: 0 Test::More: 0 + perl: 5.006 configure_requires: ExtUtils::MakeMaker: 6.30 dynamic_config: 0 -generated_by: 'Dist::Zilla version 4.300021, CPAN::Meta::Converter version 2.120921' +generated_by: 'Dist::Zilla version 4.300040, CPAN::Meta::Converter version 2.143240' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -35,5 +37,5 @@ resources: homepage: http://metacpan.org/release/App-Inotify-Hookable license: http://dev.perl.org/licenses/ repository: git://github.com/avar/app-inotify-hookable.git -version: 0.07 +version: 0.09 x_authority: cpan:AVAR diff --git a/Makefile.PL b/Makefile.PL index b4614db..4d549f7 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -10,12 +10,8 @@ use ExtUtils::MakeMaker 6.30; my %WriteMakefileArgs = ( "ABSTRACT" => "blocking command-line interface to inotify", - "AUTHOR" => "\303\206var Arnfj\303\266r\303\260 Bjarmason <avar\@cpan.org>", - "BUILD_REQUIRES" => { - "File::Find" => 0, - "File::Temp" => 0, - "Test::More" => 0 - }, + "AUTHOR" => "\x{c6}var Arnfj\x{f6}r\x{f0} Bjarmason <avar\@cpan.org>", + "BUILD_REQUIRES" => {}, "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => "6.30" }, @@ -40,13 +36,32 @@ my %WriteMakefileArgs = ( "strict" => 0, "warnings" => 0 }, - "VERSION" => "0.07", + "TEST_REQUIRES" => { + "File::Spec" => 0, + "IO::Handle" => 0, + "IPC::Open3" => 0, + "Test::More" => 0 + }, + "VERSION" => "0.09", "test" => { "TESTS" => "t/*.t" } ); +unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { + my $tr = delete $WriteMakefileArgs{TEST_REQUIRES}; + my $br = $WriteMakefileArgs{BUILD_REQUIRES}; + for my $mod ( keys %$tr ) { + if ( exists $br->{$mod} ) { + $br->{$mod} = $tr->{$mod} if $tr->{$mod} > $br->{$mod}; + } + else { + $br->{$mod} = $tr->{$mod}; + } + } +} + unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) { my $br = delete $WriteMakefileArgs{BUILD_REQUIRES}; my $pp = $WriteMakefileArgs{PREREQ_PM}; @@ -46,6 +46,10 @@ DESCRIPTION probably run in another process via POE or something). Patches welcome. OPTIONS + Note that boolean options can be negated with "--no-OPTION", e.g. + "--no-r" or "--no-recursive" to turn off the "--recursive" option which + is on by default. + "-w" or "--watch-directories" Specify this to watch a directory, you can give this however many times you like to watch lots of directories. diff --git a/README.pod b/README.pod new file mode 100644 index 0000000..4a7f2dd --- /dev/null +++ b/README.pod @@ -0,0 +1,124 @@ +=encoding utf8 + +=head1 NAME + +App::Inotify::Hookable - blocking command-line interface to inotify + +=head1 SYNOPSIS + +Watch a directory, tell us when things change in it: + + inotify-hookable --watch-directories /tmp/watch-this + +Watch a git tree, some configs, and a repository of static assets, +restart the webserver or compress those assets if anything changes: + + inotify-hookable \ + --watch-directories /etc/uwsgi \ + --watch-directories /git_tree/central \ + --watch-directories /etc/app-config \ + --watch-directories /git_tree/static_assets \ + --on-modify-path-command "^(/etc/uwsgi|/git_tree/central|/etc/app-config)=sudo /etc/init.d/uwsgi restart" \ + --on-modify-path-command "^/git_tree/static_assets=(cd /git_tree/static_assets && compress_static_assets)" + +Or watch specific files: + + inotify-hookable \ + --watch-files /var/www/cgi-bin/mod_perl_handler \ + --on-modify-command "apachectl restart" + +=head1 DESCRIPTION + +This simple command-line program is my replacement for the +functionality offered by L<Plack>'s L<Filesys::Notify::Simple>. I +found that on very large git trees Plack would spend an inordinate +amount watching the filesystem for changes. + +This program uses L<Linux::Inotify2>, so the kernel will notify it +B<instantly> when something changes (actually it's so fast that we +have to work around how fast it sends us events). + +The result is that you can run this e.g. in a screen session and have +it watch your development environment, and your webserver will have +begun restarting before your finger leaves the I<save> button. + +vim and emacs temporary files are ignored by default (see C<--ignore-paths>.) +so you can edit your files without your server restarting unnecessarily. + +Currently the command-line interface for this is the only one that +really makes sense, this module is entirely blocking (although it +could probably run in another process via L<POE> or +something). Patches welcome. + +=head1 OPTIONS + +Note that boolean options can be negated with C<--no-OPTION>, +e.g. C<--no-r> or C<--no-recursive> to turn off the C<--recursive> +option which is on by default. + +=head2 C<-w> or C<--watch-directories> + +Specify this to watch a directory, you can give this however many +times you like to watch lots of directories. + +=head2 C<-f> or C<--watch-files> + +Watch a file, specify multiple times for multiple files. +You can watch files and directories in the same command. + +=head2 C<-r> or C<--recursive> + +If you supply this any directory you give will be recursively +watched. This is on by default. + +=head2 C<-c> or C<--on-modify-command> + +A command that will be run when something is modified. + +=head2 C<-C> or C<--on-modify-path-command> + +A key-value pair where the key is a regex that'll be matched against a +modified path, and the value is a command that'll be run. See the +L</SYNOPSIS> for an example. + +Useful for e.g. restarting a webserver if you modify directory F<A> +but compressing some static assets if you modify directory F<B>. + +=head2 C<-t> or C<--buffer-time> + +Linux will send you inotify events B<really> fast, so fast that if you +run something like: + + touch foo bar + +You might get an event for F<foo> in one batch, followed by an event +for F<bar> later on. + +To deal with this we enter a loop when we start getting events and sleep for a +default of 100 microseconds, as long as we keep getting events we keep sleeping +for 100 microseconds, but as soon as we haven't received anything new we fire +off our event handlers. + +=head2 C<-i> or C<--ignore-paths> + +Regexes for files/directories to ignore events for. By default this is set to +regexes for vim and emacs temporary files, C<qr{\..*sw.\z}> and +C<qr{\.\#[^/]+\z}> respectively. + +The regexes match after any C</> in the path or the beginning of the string. + +=head2 C<-d> or C<--debug> + +Spew out some verbose debug output while running. + +=head1 ACKNOWLEDGMENT + +This module was originally developed at and for Booking.com. With +approval from Booking.com, this module was generalized and put on +CPAN, for which the authors would like to express their gratitude. + +=head1 AUTHOR + +Ævar Arnfjörð Bjarmason <avar@cpan.org> + +=cut diff --git a/bin/inotify-hookable b/bin/inotify-hookable index 9274b04..14e585e 100755 --- a/bin/inotify-hookable +++ b/bin/inotify-hookable @@ -4,7 +4,7 @@ BEGIN { $main::AUTHORITY = 'cpan:AVAR'; } { - $main::VERSION = '0.07'; + $main::VERSION = '0.09'; } use strict; use warnings; diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index cbdf561..e5f189a 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -4,7 +4,7 @@ BEGIN { $App::Inotify::Hookable::AUTHORITY = 'cpan:AVAR'; } { - $App::Inotify::Hookable::VERSION = '0.07'; + $App::Inotify::Hookable::VERSION = '0.09'; } use Moose; use MooseX::Types::Moose ':all'; @@ -91,7 +91,7 @@ has buffer_time => ( isa => Int, default => 100, cmd_aliases => 't', - documentation => "How many us should we buffer inotify for? (default 100)", + documentation => "How many milliseconds should we buffer inotify for? (default 100)", ); has ignore_paths => ( @@ -138,7 +138,7 @@ sub log { my $dumper_squashed = sub { my $val = shift; - + my $dd = Data::Dumper->new([]); $dd->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Indent(0); return $dd->Values([ $val ])->Dump; @@ -147,7 +147,7 @@ my $dumper_squashed = sub { sub run { my ($self) = @_; - # Catch sigint so DEMOLISH can run + # Catch sigint so DEMOLISH can run local $SIG{INT} = sub { exit 1 }; my @watch_dirs = $self->watch_directories; @@ -156,7 +156,8 @@ sub run { $self->log( "Starting up, " . (@watch_dirs ? - ($self->recursive ? "recursively " : "") . + ($self->recursive ? "recursively" : "non-recursively") . + " " . "watching directories <@watch_dirs>" . (@watch_files ? " and " : "") : "") . @@ -218,11 +219,11 @@ sub run { @events = $notifier->read; ualarm(0); } catch { - $self->log("We have no more events with a timeout of $sleep_ms us") if $self->debug; + $self->log("We have no more events with a timeout of $sleep_ms ms") if $self->debug; }; if (@events) { - $self->log("We have events, waiting another $sleep_ms us and checking again") if $self->debug; + $self->log("We have events, waiting another $sleep_ms ms and checking again") if $self->debug; $log_modified_paths->(\@events); } else { @@ -368,7 +369,7 @@ sub setup_watch { $path, ( # Is this is a directory? - ($type eq 'directory' ? + ($type eq 'directory' ? # Modifications I care about IN_MODIFY | @@ -415,6 +416,13 @@ The original error was: $error DIE + } elsif ($error == ENOENT) { + # Don't hard die on the common race condition where a + # file/directory we found with our "find" call has + # since gone away (e.g. due to a different "git + # checkout" removing it). + $self->log("Couldn't watch $type '$path': $error"); + next WATCH; } else { die $error; } @@ -513,6 +521,10 @@ something). Patches welcome. =head1 OPTIONS +Note that boolean options can be negated with C<--no-OPTION>, +e.g. C<--no-r> or C<--no-recursive> to turn off the C<--recursive> +option which is on by default. + =head2 C<-w> or C<--watch-directories> Specify this to watch a directory, you can give this however many diff --git a/t/00-compile.t b/t/00-compile.t index 6771e12..843cdcc 100644 --- a/t/00-compile.t +++ b/t/00-compile.t @@ -1,73 +1,78 @@ -#!perl - +use 5.006; use strict; use warnings; -use Test::More; +# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.051 +use Test::More; +plan tests => 2 + ($ENV{AUTHOR_TESTING} ? 1 : 0); -use File::Find; -use File::Temp qw{ tempdir }; +my @module_files = ( + 'App/Inotify/Hookable.pm' +); -my @modules; -find( - sub { - return if $File::Find::name !~ /\.pm\z/; - my $found = $File::Find::name; - $found =~ s{^lib/}{}; - $found =~ s{[/\\]}{::}g; - $found =~ s/\.pm$//; - # nothing to skip - push @modules, $found; - }, - 'lib', +my @scripts = ( + 'bin/inotify-hookable' ); -sub _find_scripts { - my $dir = shift @_; - - my @found_scripts = (); - find( - sub { - return unless -f; - my $found = $File::Find::name; - # nothing to skip - open my $FH, '<', $_ or do { - note( "Unable to open $found in ( $! ), skipping" ); - return; - }; - my $shebang = <$FH>; - return unless $shebang =~ /^#!.*?\bperl\b\s*$/; - push @found_scripts, $found; - }, - $dir, - ); - - return @found_scripts; -} +# no fake home requested -my @scripts; -do { push @scripts, _find_scripts($_) if -d $_ } - for qw{ bin script scripts }; +my $inc_switch = -d 'blib' ? '-Mblib' : '-Ilib'; -my $plan = scalar(@modules) + scalar(@scripts); -$plan ? (plan tests => $plan) : (plan skip_all => "no tests to run"); +use File::Spec; +use IPC::Open3; +use IO::Handle; +open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!"; + +my @warnings; +for my $lib (@module_files) { - # fake home for cpan-testers - # no fake requested ## local $ENV{HOME} = tempdir( CLEANUP => 1 ); - - like( qx{ $^X -Ilib -e "require $_; print '$_ ok'" }, qr/^\s*$_ ok/s, "$_ loaded ok" ) - for sort @modules; - - SKIP: { - eval "use Test::Script 1.05; 1;"; - skip "Test::Script needed to test script compilation", scalar(@scripts) if $@; - foreach my $file ( @scripts ) { - my $script = $file; - $script =~ s!.*/!!; - script_compiles( $file, "$script script compiles" ); - } + # see L<perlfaq8/How can I capture STDERR from an external command?> + my $stderr = IO::Handle->new; + + my $pid = open3($stdin, '>&STDERR', $stderr, $^X, $inc_switch, '-e', "require q[$lib]"); + binmode $stderr, ':crlf' if $^O eq 'MSWin32'; + my @_warnings = <$stderr>; + waitpid($pid, 0); + is($?, 0, "$lib loaded ok"); + + if (@_warnings) + { + warn @_warnings; + push @warnings, @_warnings; } } + +foreach my $file (@scripts) +{ SKIP: { + open my $fh, '<', $file or warn("Unable to open $file: $!"), next; + my $line = <$fh>; + + close $fh and skip("$file isn't perl", 1) unless $line =~ /^#!\s*(?:\S*perl\S*)((?:\s+-\w*)*)(?:\s*#.*)?$/; + my @flags = $1 ? split(' ', $1) : (); + + my $stderr = IO::Handle->new; + + my $pid = open3($stdin, '>&STDERR', $stderr, $^X, $inc_switch, @flags, '-c', $file); + binmode $stderr, ':crlf' if $^O eq 'MSWin32'; + my @_warnings = <$stderr>; + waitpid($pid, 0); + is($?, 0, "$file compiled ok"); + + # in older perls, -c output is simply the file portion of the path being tested + if (@_warnings = grep { !/\bsyntax OK$/ } + grep { chomp; $_ ne (File::Spec->splitpath($file))[2] } @_warnings) + { + warn @_warnings; + push @warnings, @_warnings; + } +} } + + + +is(scalar(@warnings), 0, 'no warnings found') + or diag 'got warnings: ', ( Test::More->can('explain') ? Test::More::explain(\@warnings) : join("\n", '', @warnings) ) if $ENV{AUTHOR_TESTING}; + + |