5 use version
; our $VERSION = qv
('0.1.1');
7 use Pod
::Usage
qw( pod2usage );
8 use Getopt
::Long
qw( :config gnu_getopt );
9 use English
qw( -no_match_vars );
10 use File
::Basename
qw( basename dirname );
11 use File
::Spec
::Functions
qw( file_name_is_absolute catfile );
12 use File
::Temp
qw( tempfile );
13 use POSIX
qw( strftime );
14 use Cwd
qw( cwd realpath );
19 # __MOBUNDLE_INCLUSION__
23 'Text/Glob.pm' => <<'END_OF_FILE',
27 use vars qw/$VERSION @ISA @EXPORT_OK
28 $strict_leading_dot $strict_wildcard_slash/;
31 @EXPORT_OK = qw( glob_to_regex glob_to_regex_string match_glob );
33 $strict_leading_dot = 1;
34 $strict_wildcard_slash = 1;
36 use constant debug
=> 0;
40 my $regex = glob_to_regex_string
($glob);
44 sub glob_to_regex_string
47 my ($regex, $in_curlies, $escaping);
50 for ($glob =~ m/(.)/gs) {
52 if ($strict_leading_dot) {
53 $regex .= '(?=[^\.])' unless $_ eq '.';
60 if ($_ eq '.' || $_ eq '(' || $_ eq ')' || $_ eq '|' ||
61 $_ eq '+' || $_ eq '^' || $_ eq '$' || $_ eq '@' || $_ eq '%' ) {
65 $regex .= $escaping ?
"\\*" :
66 $strict_wildcard_slash ?
"[^/]*" : ".*";
69 $regex .= $escaping ?
"\\?" :
70 $strict_wildcard_slash ?
"[^/]" : ".";
73 $regex .= $escaping ?
"\\{" : "(";
74 ++$in_curlies unless $escaping;
76 elsif ($_ eq '}' && $in_curlies) {
77 $regex .= $escaping ?
"}" : ")";
78 --$in_curlies unless $escaping;
80 elsif ($_ eq ',' && $in_curlies) {
81 $regex .= $escaping ?
"," : "|";
99 print "# $glob $regex\n" if debug
;
105 print "# ", join(', ', map { "'$_'" } @_), "\n" if debug
;
107 my $regex = glob_to_regex
$glob;
109 grep { $_ =~ $regex } @_;
117 Text
::Glob
- match globbing patterns against text
121 use Text
::Glob
qw( match_glob glob_to_regex );
123 print "matched\n" if match_glob
( "foo.*", "foo.bar" );
125 # prints foo.bar and foo.baz
126 my $regex = glob_to_regex
( "foo.*" );
127 for ( qw( foo.bar foo.baz foo bar ) ) {
128 print "matched: $_\n" if /$regex/;
133 Text
::Glob implements
glob(3) style matching that can be used to match
134 against text
, rather than fetching names from a filesystem
. If you
135 want to
do full file globbing
use the File
::Glob module instead
.
141 =item match_glob
( $glob, @things_to_test )
143 Returns the list of things which match the
glob from the source list
.
145 =item glob_to_regex
( $glob )
147 Returns a compiled regex which is the equivalent of the globbing
150 =item glob_to_regex_string
( $glob )
152 Returns a regex string which is the equivalent of the globbing
159 The following metacharacters
and rules are respected
.
163 =item C
<*> - match zero
or more characters
165 C
<a
*> matches C
<a
>, C
<aa
>, C
<aaaa
> and many many more
.
167 =item C
<?
> - match exactly one character
169 C
<a?
> matches C
<aa
>, but
not C
<a
>, or C
<aaa
>
171 =item Character sets
/ranges
173 C
<example
.[ch
]> matches C
<example
.c
> and C
<example
.h
>
175 C
<demo
.[a
-c
]> matches C
<demo
.a
>, C
<demo
.b
>, and C
<demo
.c
>
179 C
<example
.{foo
,bar
,baz
}> matches C
<example
.foo
>, C
<example
.bar
>, and
182 =item leading
. must be explictly matched
184 C
<*.foo
> does
not match C
<.bar
.foo
>. For this you must either specify
185 the leading
. in the
glob pattern
(C
<.*.foo
>), or set
186 C
<$Text::Glob
::strict_leading_dot
> to a false value
while compiling
189 =item C
<*> and C
<?
> do not match
/
191 C
<*.foo
> does
not match C
<bar
/baz
.foo
>. For this you must either
192 explicitly match the
/ in the glob (C<*/*.foo
>), or set
193 C
<$Text::Glob
::strict_wildcard_slash
> to a false value with compiling
200 The code uses
qr// to produce compiled regexes
, therefore this module
201 requires perl version
5.005_03
or newer
.
205 Richard Clamp
<richardc
@unixbeard.net
>
209 Copyright
(C
) 2002, 2003, 2006, 2007 Richard Clamp
. All Rights Reserved
.
211 This module is free software
; you can redistribute it
and/or modify it
212 under the same terms as Perl itself
.
216 L
<File
::Glob
>, glob(3)
222 'File/Find/Rule.pm' => <<'END_OF_FILE',
225 package File::Find::Rule;
228 use Text::Glob 'glob_to_regex';
231 use File::Find (); # we're only wrapping for now
233 our $VERSION = '0.33';
235 # we'd just inherit from Exporter, but I want the colon
239 for my $sym ( qw( find rule ) ) {
241 *{"$to\::$sym"} = \
&{$sym};
243 for (grep /^:/, @_) {
244 my ($extension) = /^:(.*)/;
245 eval "require File::Find::Rule::$extension";
246 croak
"couldn't bootstrap File::Find::Rule::$extension: $@" if $@
;
252 File
::Find
::Rule
- Alternative interface to File
::Find
256 use File
::Find
::Rule
;
257 # find all the subdirectories of a given directory
258 my @subdirs = File
::Find
::Rule
->directory->in( $directory );
260 # find all the .pm files in @INC
261 my @files = File
::Find
::Rule
->file()
265 # as above, but without method chaining
266 my $rule = File
::Find
::Rule
->new;
268 $rule->name( '*.pm' );
269 my @files = $rule->in( @INC );
273 File
::Find
::Rule is a friendlier interface to File
::Find
. It allows
274 you to build rules which specify the desired files
and directories
.
278 # the procedural shim
282 my $object = __PACKAGE__
->new();
289 if ($method =~ s/^\!//) {
290 # jinkies, we're really negating this
295 unless (defined prototype $method) {
297 @args = ref $args eq 'ARRAY' ? @
$args : $args;
301 @args = $object->new->$method(@args);
305 my @return = $object->$method(@args);
306 return @return if $method eq 'in';
318 A constructor
. You need
not invoke C
<new
> manually
unless you wish
319 to
, as
each of the rule
-making methods will auto
-create a suitable
320 object
if called as
class methods
.
325 my $referent = shift;
326 my $class = ref $referent || $referent;
339 $object = $object->new()
346 =head2 Matching Rules
350 =item C
<name
( @patterns )>
352 Specifies names that should match
. May be globs
or regular
355 $set->name( '*.mp3', '*.ogg' ); # mp3s or oggs
356 $set->name( qr/\.(mp3|ogg)$/ ); # the same as a regex
357 $set->name( 'foo.bar' ); # just things named foo.bar
365 ref $item eq 'ARRAY' ?
push @_, @
{ $item } : push @flat, $item;
371 my $self = _force_object
shift;
372 my @names = map { ref $_ eq "Regexp" ?
$_ : glob_to_regex
$_ } _flatten
( @_ );
374 push @
{ $self->{rules
} }, {
376 code
=> join( ' || ', map { "m{$_}" } @names ),
385 Synonyms are provided
for each of the
-X tests
. See L
<perlfunc
/-X
> for
386 details
. None of these methods take arguments
.
388 Test
| Method Test
| Method
389 ------|------------- ------|----------------
390 -r
| readable
-R
| r_readable
391 -w
| writeable
-W
| r_writeable
392 -w
| writable
-W
| r_writable
393 -x
| executable
-X
| r_executable
394 -o
| owned
-O
| r_owned
396 -e
| exists -f
| file
397 -z
| empty
-d
| directory
398 -s
| nonempty
-l
| symlink
400 -u
| setuid
-S
| socket
401 -g
| setgid
-b
| block
402 -k
| sticky
-c
| character
405 -A
| accessed
-T
| ascii
406 -C
| changed
-B
| binary
408 Though some tests are fairly meaningless as binary flags
(C
<modified
>,
409 C
<accessed
>, C
<changed
>), they have been included
for completeness
.
411 # find nonempty files
417 use vars
qw( %X_tests );
419 -r => readable => -R => r_readable =>
420 -w => writeable => -W => r_writeable =>
421 -w => writable => -W => r_writable =>
422 -x => executable => -X => r_executable =>
423 -o => owned => -O => r_owned =>
425 -e => exists => -f => file =>
426 -z => empty => -d => directory =>
427 -s => nonempty => -l => symlink =>
429 -u => setuid => -S => socket =>
430 -g => setgid => -b => block =>
431 -k => sticky => -c => character =>
434 -A => accessed => -T => ascii =>
435 -C => changed => -B => binary =>
438 for my $test (keys %X_tests) {
439 my $sub = eval 'sub () {
440 my $self = _force_object shift;
441 push @{ $self->{rules} }, {
442 code => "' . $test . ' \$_",
443 rule => "'.$X_tests{$test}.'",
448 *{ $X_tests{$test} } = $sub;
454 The following C<stat> based methods are provided: C<dev>, C<ino>,
455 C<mode>, C<nlink>, C<uid>, C<gid>, C<rdev>, C<size>, C<atime>,
456 C<mtime>, C<ctime>, C<blksize>, and C<blocks>. See L<perlfunc/stat>
459 Each of these can take a number of targets, which will follow
460 L<Number::Compare> semantics.
462 $rule->size( 7 ); # exactly 7
463 $rule->size( ">7Ki" ); # larger than 7 * 1024 * 1024 bytes
465 ->size( "<=90" ); # between 7 and 90, inclusive
466 $rule->size( 7, 9, 42 ); # 7, 9 or 42
470 use vars qw( @stat_tests );
471 @stat_tests = qw( dev ino mode nlink uid gid rdev
472 size atime mtime ctime blksize blocks );
475 for my $test (@stat_tests) {
476 my $index = $i++; # to close over
478 my $self = _force_object
shift;
480 my @tests = map { Number
::Compare
->parse_to_perl($_) } @_;
482 push @
{ $self->{rules
} }, {
485 code
=> 'do { my $val = (stat $_)['.$index.'] || 0;'.
486 join ('||', map { "(\$val $_)" } @tests ).' }',
495 =item C
<any
( @rules )>
497 =item C
<or( @rules )>
499 Allows shortcircuiting boolean evaluation as an alternative to the
500 default and-like nature of combined rules
. C
<any
> and C
<or> are
503 # find avis, movs, things over 200M and empty files
504 $rule->any( File
::Find
::Rule
->name( '*.avi', '*.mov' ),
505 File
::Find
::Rule
->size( '>200M' ),
506 File
::Find
::Rule
->file->empty,
512 my $self = _force_object
shift;
513 # compile all the subrules to code fragments
514 push @
{ $self->{rules
} }, {
516 code
=> '(' . join( ' || ', map '( ' . $_->_compile . ' )', @_ ). ')',
520 # merge all the subs hashes of the kids into ourself
521 %{ $self->{subs
} } = map { %{ $_->{subs
} } } $self, @_;
527 =item C
<none
( @rules )>
529 =item C
<not( @rules )>
531 Negates a rule
. (The inverse of C
<any
>.) C
<none
> and C
<not> are
534 # files that aren't 8.3 safe
536 ->not( $rule->new->name( qr/^[^.]{1,8}(\.[^.]{0,3})?$/ ) );
541 my $self = _force_object
shift;
543 push @
{ $self->{rules
} }, {
546 code
=> '(' . join ( ' && ', map { "!(". $_->_compile . ")" } @_ ) . ")",
549 # merge all the subs hashes into us
550 %{ $self->{subs
} } = map { %{ $_->{subs
} } } $self, @_;
558 Traverse
no further
. This rule always matches
.
563 my $self = _force_object
shift;
565 push @
{ $self->{rules
} },
568 code
=> '$File::Find::prune = 1'
575 Don
't keep this file. This rule always matches.
580 my $self = _force_object shift;
582 push @{ $self->{rules} }, {
584 code => '$discarded = 1',
589 =item C<exec( \&subroutine( $shortname, $path, $fullname ) )>
591 Allows user-defined rules. Your subroutine will be invoked with C<$_>
592 set to the current short name, and with parameters of the name, the
593 path you're
in, and the full relative filename
.
595 Return a true value
if your rule matched
.
597 # get things with long names
598 $rules->exec( sub { length > 20 } );
603 my $self = _force_object
shift;
606 push @
{ $self->{rules
} }, {
613 =item C
<grep( @specifiers )>
615 Opens a file
and tests it
each line at a
time.
617 For
each line it evaluates
each of the specifiers
, stopping at the
618 first successful match
. A specifier may be a regular expression
or a
619 subroutine
. The subroutine will be invoked with the same parameters
620 as an
->exec subroutine
.
622 It is possible to provide a set of negative specifiers by enclosing
623 them
in anonymous arrays
. Should a negative specifier match the
624 iteration is aborted
and the clause is failed
. For example
:
626 $rule->grep( qr/^#!.*\bperl/, [ sub { 1 } ] );
628 Is a passing clause
if the first line of a file looks like a perl
634 my $self = _force_object
shift;
638 ?
map { [ ( ref $_ ?
$_ : qr/$_/ ) => 0 ] } @
$_
645 open FILE
, $_ or return;
648 for my $p (@pattern) {
649 my ($rule, $ret) = @
$p;
651 if ref $rule eq 'Regexp'
660 =item C
<maxdepth
( $level )>
662 Descend at most C
<$level> (a non
-negative integer
) levels of directories
663 below the starting point
.
665 May be invoked many
times per rule
, but only the most recent value is
668 =item C
<mindepth
( $level )>
670 Do
not apply any tests at levels less than C
<$level> (a non
-negative
673 =item C
<extras
( \
%extras )>
675 Specifies extra
values to pass through to C
<File
::File
::find
> as part
678 For example this allows you to specify following of symlinks like so
:
680 my $rule = File
::Find
::Rule
->extras({ follow
=> 1 });
682 May be invoked many
times per rule
, but only the most recent value is
687 for my $setter (qw( maxdepth mindepth extras )) {
689 my $self = _force_object
shift;
690 $self->{$setter} = shift;
700 Trim the leading portion of any path found
705 my $self = _force_object
shift;
706 $self->{relative
} = 1;
712 Negated version of the rule
. An effective shortand related to
! in
713 the procedural interface
.
715 $foo->not_name('*.pl');
717 $foo->not( $foo->new->name('*.pl' ) );
724 $AUTOLOAD =~ /::not_([^:]*)$/
725 or croak
"Can't locate method $AUTOLOAD";
729 my $self = _force_object
shift;
730 $self->not( $self->new->$method(@_) );
745 =item C
<in( @directories )>
747 Evaluates the rule
, returns a list of paths to matching files
and
753 my $self = _force_object
shift;
756 my $fragment = $self->_compile;
757 my %subs = %{ $self->{subs
} };
759 warn "relative mode handed multiple paths - that's a bit silly\n"
760 if $self->{relative
} && @_ > 1;
764 (my $path = $File::Find::name) =~ s#^(?:\./+)+##;
765 my @args = ($_, $File::Find::dir, $path);
766 my $maxdepth = $self->{maxdepth};
767 my $mindepth = $self->{mindepth};
768 my $relative = $self->{relative};
770 # figure out the relative path and depth
771 my $relpath = $File::Find::name;
772 $relpath =~ s{^\Q$topdir\E/?}{};
773 my $depth = scalar File::Spec->splitdir($relpath);
774 #print "name: \'$File::Find::name\' ";
775 #print "relpath: \'$relpath\' depth: $depth relative: $relative\n";
777 defined $maxdepth && $depth >= $maxdepth
778 and $File::Find::prune = 1;
780 defined $mindepth && $depth < $mindepth
783 #print "Testing \'$_\'\n";
786 return unless ' . $fragment . ';
787 return if $discarded;
789 push @found, $relpath if $relpath ne "";
797 #print Dumper \%subs;
798 #warn "Compiled sub: '$code'\n";
800 my $sub = eval "$code" or die "compile error '$code' $@";
802 # $topdir is used for relative and maxdepth
804 # slice off the trailing slash if there is one (the
805 # maxdepth/mindepth code is fussy)
807 unless $topdir eq '/';
808 $self->_call_find( { %{ $self->{extras
} }, wanted
=> $sub }, $path );
816 File
::Find
::find
( @_ );
822 return '1' unless @
{ $self->{rules
} };
823 my $code = join " && ", map {
824 if (ref $_->{code
}) {
825 my $key = "$_->{code}";
826 $self->{subs
}{$key} = $_->{code
};
827 "\$subs{'$key'}->(\@args) # $_->{rule}\n";
830 "( $_->{code} ) # $_->{rule}\n";
832 } @
{ $self->{rules
} };
838 =item C
<start
( @directories )>
840 Starts a find across the specified directories
. Matching items may
841 then be queried using L
</match
>. This allows you to
use a rule as an
844 my $rule = File
::Find
::Rule
->file->name("*.jpeg")->start( "/web" );
845 while ( defined ( my $image = $rule->match ) ) {
852 my $self = _force_object
shift;
854 $self->{iterator
} = [ $self->in( @_ ) ];
860 Returns the
next file which matches
, false
if there are
no more
.
865 my $self = _force_object
shift;
867 return shift @
{ $self->{iterator
} };
878 Extension modules are available from CPAN
in the File
::Find
::Rule
879 namespace
. In order to
use these extensions either
use them directly
:
881 use File
::Find
::Rule
::ImageSize
;
882 use File
::Find
::Rule
::MMagic
;
884 # now your rules can use the clauses supplied by the ImageSize and
887 or, specify that File
::Find
::Rule should load them
for you
:
889 use File
::Find
::Rule
qw( :ImageSize :MMagic );
891 For notes on implementing your own extensions
, consult
892 L
<File
::Find
::Rule
::Extending
>
894 =head2 Further examples
898 =item Finding perl scripts
900 my $finder = File
::Find
::Rule
->or
902 File
::Find
::Rule
->name( '*.pl' ),
903 File
::Find
::Rule
->exec(
905 if (open my $fh, $_) {
908 return $shebang =~ /^#!.*\bperl/;
914 Based upon this message http
://use
.perl
.org
/comments
.pl?sid
=7052&cid
=10842
916 =item ignore CVS directories
918 my $rule = File
::Find
::Rule
->new;
926 Note here the
use of a null rule
. Null rules match anything they see
,
927 so the effect is to match
(and discard
) directories called
'CVS' or to
932 =head1 TWO FOR THE PRICE OF ONE
934 File
::Find
::Rule also gives you a procedural interface
. This is
935 documented
in L
<File
::Find
::Rule
::Procedural
>
941 =head1 TAINT MODE INTERACTION
943 As of
0.32 File
::Find
::Rule doesn
't capture the current working directory in
944 a taint-unsafe manner. File::Find itself still does operations that the taint
945 system will flag as insecure but you can use the L</extras> feature to ask
946 L<File::Find> to internally C<untaint> file paths with a regex like so:
948 my $rule = File::Find::Rule->extras({ untaint => 1 });
950 Please consult L<File::Find>'s documentation
for C
<untaint
>,
951 C
<untaint_pattern
>, and C
<untaint_skip
> for more information
.
955 The code makes
use of the C
<our> keyword
and as such requires perl version
958 Currently it isn
't possible to remove a clause from a rule object. If
959 this becomes a significant issue it will be addressed.
963 Richard Clamp <richardc@unixbeard.net> with input gained from this
964 use.perl discussion: http://use.perl.org/~richardc/journal/6467
966 Additional proofreading and input provided by Kake, Greg McCarroll,
967 and Andy Lester andy@petdance.com.
971 Copyright (C) 2002, 2003, 2004, 2006, 2009, 2011 Richard Clamp. All Rights Reserved.
973 This module is free software; you can redistribute it and/or modify it
974 under the same terms as Perl itself.
978 L<File::Find>, L<Text::Glob>, L<Number::Compare>, find(1)
980 If you want to know about the procedural interface, see
981 L<File::Find::Rule::Procedural>, and if you have an idea for a neat
982 extension L<File::Find::Rule::Extending>
986 Implementation notes:
988 $self->rules is an array of hashrefs. it may be a code fragment or a call
991 Anonymous subroutines are stored in the $self->subs hashref keyed on the
992 stringfied version of the coderef.
994 When one File::Find::Rule object is combined with another, such as in the any
995 and not operations, this entire hash is merged.
997 The _compile method walks the rules element and simply glues the code
998 fragments together so they can be compiled into an anyonymous File::Find
1002 [*] There's probably a win to be made with the current model
in making
1003 stat calls
use C
<_
>. For
1005 find
( file
=> size
=> "> 20M" => size
=> "< 400M" );
1007 up to
3 stats will happen
for each candidate
. Adding a priming _
1008 would be a bit blind
if the first operation was C
< name
=> 'foo' >,
1009 since that can be tested by a single regex
. Simply checking what the
1010 next type of operation doesn
't work since any arbritary exec sub may
1011 or may not stat. Potentially worse, they could stat something else
1014 # extract from the worlds stupidest make(1)
1015 find( exec => sub { my $f = $_; $f =~ s/\.c$/.o/ && !-e $f } );
1017 Maybe the best way is to treat C<_> as invalid after calling an exec,
1018 and doc that C<_> will only be meaningful after stat and -X tests if
1019 they're wanted
in exec blocks
.
1023 'Number/Compare.pm' => <<'END_OF_FILE',
1024 package Number::Compare;
1027 use vars qw
/$VERSION/;
1031 my $referent = shift;
1032 my $class = ref $referent || $referent;
1033 my $expr = $class->parse_to_perl( shift );
1035 bless eval "sub { \$_[0] $expr }", $class;
1043 ([<>]=?
)?
# comparison
1045 ([kmg
]i?
)?
# magnitude
1047 or croak
"don't understand '$test' as a test";
1049 my $comparison = $1 || '==';
1051 my $magnitude = $3 || '';
1052 $target *= 1000 if lc $magnitude eq 'k';
1053 $target *= 1024 if lc $magnitude eq 'ki';
1054 $target *= 1000000 if lc $magnitude eq 'm';
1055 $target *= 1024*1024 if lc $magnitude eq 'mi';
1056 $target *= 1000000000 if lc $magnitude eq 'g';
1057 $target *= 1024*1024*1024 if lc $magnitude eq 'gi';
1059 return "$comparison $target";
1062 sub test
{ $_[0]->( $_[1] ) }
1070 Number
::Compare
- numeric comparisons
1074 Number
::Compare
->new(">1Ki")->test(1025); # is 1025 > 1024
1076 my $c = Number
::Compare
->new(">1M");
1077 $c->(1_200_000
); # slightly terser invocation
1081 Number
::Compare compiles a simple comparison to an anonymous
1082 subroutine
, which you can call with a value to be tested again
.
1084 Now this would be very pointless
, if Number
::Compare didn
't understand
1087 The target value may use magnitudes of kilobytes (C<k>, C<ki>),
1088 megabytes (C<m>, C<mi>), or gigabytes (C<g>, C<gi>). Those suffixed
1089 with an C<i> use the appropriate 2**n version in accordance with the
1090 IEC standard: http://physics.nist.gov/cuu/Units/binary.html
1094 =head2 ->new( $test )
1096 Returns a new object that compares the specified test.
1098 =head2 ->test( $value )
1100 A longhanded version of $compare->( $value ). Predates blessed
1101 subroutine reference implementation.
1103 =head2 ->parse_to_perl( $test )
1105 Returns a perl code fragment equivalent to the test.
1109 Richard Clamp <richardc@unixbeard.net>
1113 Copyright (C) 2002,2011 Richard Clamp. All Rights Reserved.
1115 This module is free software; you can redistribute it and/or modify it
1116 under the same terms as Perl itself.
1120 http://physics.nist.gov/cuu/Units/binary.html
1129 my ($me, $packfile) = @_;
1130 return unless exists $file_for{$packfile};
1131 (my $text = $file_for{$packfile}) =~ s/^\ //gmxs;
1132 chop($text); # added \n at the end
1133 open my $fh, '<', \$text or die "open(): $!\n";
1137 # __MOBUNDLE_INCLUSION__
1139 use File::Find::Rule;
1143 remote => catfile(dirname(realpath(__FILE__)), 'remote
'),
1155 usage! help! man! version!
1163 include-archive-tar|T!
1168 rootdir|in-root|R=s@
1172 workdir|work-directory|deploy-directory|w=s
1174 ) or pod2usage
(message
=> "invalid command line", -verbose
=> 99, -sections
=> ' ');
1175 pod2usage
(message
=> "$0 $VERSION", -verbose
=> 99, -sections
=> ' ')
1176 if $config{version
};
1177 pod2usage
(-verbose
=> 99, -sections
=> 'USAGE') if $config{usage
};
1178 pod2usage
(-verbose
=> 99, -sections
=> 'USAGE|EXAMPLES|OPTIONS')
1180 pod2usage
(-verbose
=> 2) if $config{man
};
1183 message
=> 'working directory must be an absolute path',
1186 ) if exists $config{workdir
} && !file_name_is_absolute
($config{workdir
});
1188 if ($config{'include-archive-tar'}) {
1189 $config{remote
} = catfile
(dirname
(realpath
(__FILE__
)), 'remote-at');
1190 if (!-e
$config{remote
}) { # "make" it
1191 print {*STDERR
} "### Making remote-at...\n";
1192 my $startdir = cwd
();
1193 chdir dirname realpath __FILE__
;
1194 system {'make'} qw( make remote-at );
1196 } ## end if (!-e $config{remote...})
1197 } ## end if ($config{'include-archive-tar'...})
1199 # Establish output channel
1200 my $out_fh = \
*STDOUT
;
1201 if ($config{output
} ne '-') {
1202 open my $fh, '>', $config{output
} ## no critic
1203 or croak
"open('$config{output}'): $OS_ERROR";
1208 # Emit script code to be executed remotely. It is guaranteed to end
1209 # with __END__, so that all what comes next is data
1210 print {$out_fh} get_remote_script
();
1212 # Where all the data will be kept
1213 print_configuration
($out_fh, \
%config);
1215 print_here_stuff
($out_fh, \
%config, @ARGV);
1216 print_root_stuff
($out_fh, \
%config);
1221 if ($config{output
} ne '-') {
1222 chmod oct(755), $config{output
}
1223 or carp
"chmod(0755, '$config{output}'): $OS_ERROR";
1228 my $namesize = length $params{name
};
1229 return "$namesize $params{size}\n$params{name}";
1232 sub print_configuration
{ # FIXME
1233 my ($fh, $config) = @_;
1234 my %general_configuration;
1236 qw( workdir cleanup bundle deploy
1237 gzip bzip2 passthrough tempdir-mode )
1240 $general_configuration{$name} = $config->{$name}
1241 if exists $config->{$name};
1242 } ## end for my $name (qw( workdir cleanup bundle deploy...))
1243 my $configuration = Dumper \
%general_configuration;
1244 print {$fh} header
(name
=> 'config.pl', size
=> length($configuration)),
1245 "\n", $configuration, "\n\n";
1246 } ## end sub print_configuration
1248 # Process files and directories. All these will be reported in the
1249 # extraction directory, i.e. basename() will be applied to them. For
1250 # directories, they will be re-created
1251 sub print_here_stuff
{
1256 my $ai = Deployable
::Tar
->new($config);
1259 map { $_ => ['.'] } @
{$config->{heredir
}}
1262 print {$fh} header
(name
=> 'here', size
=> $ai->size()), "\n";
1267 } ## end sub print_here_stuff
1269 sub print_root_stuff
{
1270 my ($fh, $config) = @_;
1272 my $ai = Deployable
::Tar
->new($config);
1274 '.' => $config->{rootdir
},
1275 (undef, $config->{tarfile
}),
1276 map { $_ => ['.'] } @
{$config->{root
}}
1279 print {$fh} header
(name
=> 'root', size
=> $ai->size()), "\n";
1284 } ## end sub print_root_stuff
1286 sub get_remote_script
{
1288 if (-e
$config{remote
}) {
1289 open $fh, '<', $config{remote
}
1290 or croak
"open('$config{remote}'): $OS_ERROR";
1298 last if /\A __END__ \s*\z/mxs;
1302 return join '', @lines, "__END__\n";
1303 } ## end sub get_remote_script
1305 package Deployable
::Tar
;
1308 my $package = shift;
1309 my $self = {ref $_[0] ?
%{$_[0]} : @_};
1310 $package = 'Deployable::Tar::Internal';
1311 if (!$self->{'no-tar'}) {
1312 if ((exists $self->{tar
}) || (open my $fh, '-|', 'tar', '--help')) {
1313 $package = 'Deployable::Tar::External';
1314 $self->{tar
} ||= 'tar';
1316 } ## end if (!$self->{'no-tar'})
1317 bless $self, $package;
1318 $self->initialise();
1322 package Deployable
::Tar
::External
;
1323 use File
::Temp
qw( :seekable );
1324 use English
qw( -no_match_vars );
1327 our @ISA = qw( Deployable::Tar );
1331 $self->{_temp
} = File
::Temp
->new();
1332 $self->{_filename
} = Cwd
::abs_path
($self->{_temp
}->filename());
1334 } ## end sub initialise
1338 my $tar = $self->{tar
};
1339 delete $self->{_compressed
};
1341 my ($directory, $stuff) = splice @_, 0, 2;
1342 my @stuff = @
$stuff;
1343 if (defined $directory) {
1345 my @chunk = splice @stuff, 0, 50;
1346 system {$tar} $tar, 'rvf', $self->{_filename
},
1347 '-C', $directory, '--', @chunk;
1349 } ## end if (defined $directory)
1350 else { # it's another TAR file, concatenate
1352 my @chunk = splice @stuff, 0, 50;
1353 system {$tar} $tar, 'Avf', $self->{_filename
}, '--', @chunk;
1355 } ## end else [ if (defined $directory)]
1362 return if exists $self->{_compressed
};
1364 $self->{_temp
}->sysseek(0, SEEK_SET
);
1365 if ($self->{bzip2
}) {
1366 require IO
::Compress
::Bzip2
;
1367 $self->{_compressed
} = File
::Temp
->new();
1369 # double-quotes needed to force usage of filename
1370 # instead of filehandle
1371 IO
::Compress
::Bzip2
::bzip2
($self->{_temp
}, "$self->{_compressed}");
1372 } ## end if ($self->{bzip2})
1373 elsif ($self->{gzip
}) {
1374 require IO
::Compress
::Gzip
;
1375 $self->{_compressed
} = File
::Temp
->new();
1377 # double-quotes needed to force usage of filename
1378 # instead of filehandle
1379 IO
::Compress
::Gzip
::gzip
($self->{_temp
}, "$self->{_compressed}");
1380 } ## end elsif ($self->{gzip})
1382 $self->{_compressed
} = $self->{_temp
};
1386 } ## end sub _compress
1391 return (stat $self->{_compressed
})[7];
1395 my ($self, $out_fh) = @_;
1397 my $in_fh = $self->{_compressed
};
1398 $in_fh->sysseek(0, SEEK_SET
);
1400 my $nread = $in_fh->sysread(my $buffer, 4096);
1401 croak
"sysread(): $OS_ERROR" unless defined $nread;
1403 print {$out_fh} $buffer;
1404 } ## end while ('true')
1406 } ## end sub copy_to
1408 package Deployable
::Tar
::Internal
;
1409 use Archive
::Tar
();
1411 use File
::Find
::Rule
();
1412 use Carp qw
< croak
>;
1413 our @ISA = qw( Deployable::Tar );
1417 $self->{_tar
} = Archive
::Tar
->new();
1423 delete $self->{_string
};
1424 my $tar = $self->{_tar
};
1425 my $cwd = Cwd
::getcwd
();
1427 my ($directory, $stuff) = splice @_, 0, 2;
1428 if (defined $directory) {
1430 for my $item (@
$stuff) {
1431 $tar->add_files($_) for File
::Find
::Rule
->in($item);
1434 } ## end if (defined $directory)
1435 else { # It's another TAR file to be concatenated
1436 for my $item (@
$stuff) {
1437 my $iterator = Archive
::Tar
->iter($item);
1438 while (my $f = $iterator->()) {
1439 $tar->add_files($f);
1449 $self->{_string
} = $self->{_tar
}->write()
1450 unless exists $self->{_string
};
1451 return length $self->{_string
};
1455 my ($self, $out_fh) = @_;
1456 $self->{_string
} = $self->{_tar
}->write()
1457 unless exists $self->{_string
};
1458 print {$out_fh} $self->{_string
};
1459 } ## end sub copy_to
1463 deployable - create a deploy script for some files/scripts
1467 See version at beginning of script, variable $VERSION, or call
1469 shell$ deployable --version
1473 deployable [--usage] [--help] [--man] [--version]
1475 deployable [--bundle|--all-exec|-X] [--bzip2|--bz2|-j] [--cleanup|-c]
1476 [--deploy|--exec|d <program>] [--gzip|-gz|-z]
1477 [--heredir|-H <dirname>] [--include-archive-tar|-T]
1478 [--no-tar] [--output|-o <filename>] [--root|-r <dirname>]
1479 [--rootdir|--in-root|-R <dirname>] [--tar|-t <program-path>]
1480 [--tarfile|-F <filename>] [--tempdir-mode|-m <mode>]
1481 [--workdir|-w <path>] [ files or directories... ]
1485 # pack some files and a deploy script together.
1486 shell$ deployable script.sh file.txt some/directory -d script.sh
1488 # Use a directory's contents as elements for the target root
1489 shell$ ls -1 /path/to/target/root
1494 # The above will be deployed as /etc, /opt, /usr and /var
1495 shell$ deployable -o dep.pl --root /path/to/target/root
1497 # Include sub-directory etc/ for inclusion and extraction
1499 shell$ deployable -o dep.pl --in-root etc/
1503 This is a meta-script to create deploy scripts. The latter ones are
1504 suitable to be distributed in order to deploy something.
1506 You basically have to provide two things: files to install and programs
1507 to be executed. Files can be put directly into the deployed script, or
1508 can be included in gzipped tar archives.
1510 When called, this script creates a deploy script for you. This script
1511 includes all the specified files, and when executed it will extract
1512 those files and execute the given programs. In this way, you can ship
1513 both files and logic needed to correctly install those files, but this
1514 is of course of of scope.
1516 All files and archives will be extracted under a configured path
1517 (see L<--workdir> below), which we'll call I<workdir> from now on. Under
1518 the I<workdir> a temporary directory will be created, and the files
1519 will be put in the temporary directory. You can specify if you want to
1520 clean up this temporary directory or keep it, at your choice. (You're able
1521 to both set a default for this cleanup when invoking deployable, or when
1522 invoking the deploy script itself). The temporary directory will be
1523 called I<tmpdir> in the following.
1525 There are several ways to embed files to be shipped:
1531 pass the name of an already-prepared tar file via L</--tarfile>. The
1532 contents of this file will be assumed to be referred to the root
1537 specify the file name directly on the command line. A file given in this
1538 way will always be extracted into the I<tmpdir>, whatever its initial path
1543 specify the name of a directory on the command line. In this case,
1544 C<tar> will be used to archive the directory, with the usual option to
1545 turn absolute paths into relative ones; this means that directories will
1546 be re-created under I<tmpdir> when extraction is performed;
1550 give the name of a directory to be used as a "here directory", using
1551 the C<--heredir|-H> option. This is much the same as giving the directory
1552 name (see above), but in this case C<tar> will be told to change into the
1553 directory first, and archive '.'. This means that the contents of the
1554 "here-directory" will be extracted directly into I<tmpdir>.
1558 =head2 Extended Example
1560 Suppose you have a few server which have the same configuration, apart
1561 from some specific stuff (e.g. the hostname, the IP addresses, etc.).
1562 You'd like to perform changes to all with the minimum work possible...
1563 so you know you should script something.
1565 For example, suppose you want to update a few files in /etc, setting these
1566 files equal for all hosts. You would typically do the following:
1569 shell$ mkdir -p /tmp/newfiles/etc
1570 shell$ cd /tmp/newfiles/etc
1571 # Craft the new files
1573 shell$ tar cvzf newetc.tar.gz etc
1575 # Now, for each server:
1576 shell$ scp newetc.tar.gz $server:/tmp
1577 shell$ ssh $server tar xvzf /tmp/newetc.tar.gz -C /
1580 So far, so good. But what if you need to kick in a little more logic?
1581 For example, if you update some configuration files, you'll most likey
1582 want to restart some services. So you could do the following:
1584 shell$ mkdir -p /tmp/newfiles/tmp
1585 shell$ cd /tmp/newfiles/tmp
1586 # craft a shell script to be executed remotely and set the exec bit
1587 # Suppose it's called deploy.sh
1589 shell$ tar cvzf newetc.tar.gz etc tmp
1591 # Now, for each server:
1592 shell$ scp newetc.tar.gz $server:/tmp
1593 shell$ ssh $server tar xvzf /tmp/newetc.tar.gz -C /
1594 shell$ ssh $server /tmp/deploy.sh
1596 And what if you want to install files depending on the particular machine?
1597 Or you have a bundle of stuff to deploy and a bunch of scripts to execute?
1598 You can use deployable. In this case, you can do the following:
1600 shell$ mkdir -p /tmp/newfiles/etc
1601 shell$ cd /tmp/newfiles/etc
1602 # Craft the new files
1604 # craft a shell script to be executed remotely and set the exec bit
1605 # Suppose it's called deploy.sh
1606 shell$ deployable -o deploy.pl -R etc deploy.sh -d deploy.sh
1608 # Now, for each server
1609 shell$ scp deploy.pl $server:/tmp
1610 shell$ ssh $server /tmp/deploy.pl
1612 And you're done. This can be particularly useful if you have another
1613 layer of deployment, e.g. if you have to run a script to decide which
1614 of a group of archives should be deployed. For example, you could craft
1615 a different new "etc" for each server (which is particularly true if
1616 network configurations are in the package), and produce a simple script
1617 to choose which file to use based on the MAC address of the machine. In
1618 this case you could have:
1622 =item newetc.*.tar.gz
1624 a bunch of tar files with the configurations for each different server
1628 a list file with the association between the MAC addresses and the
1629 real tar file to deploy from the bunch in the previous bullet
1631 =item deploy-the-right-stuff.sh
1633 a script to get the real MAC address of the machine, select the right
1634 tar file and do the deployment.
1638 So, you can do the following:
1640 shell$ deployable -o deploy.pl newetc.*.tar.gz newetc.list \
1641 deploy-the-right-stuff.sh --exec deploy-the-right-stuff.sh
1643 # Now, for each server:
1644 shell$ scp deploy.pl $server:/tmp
1645 shell$ ssh $server /tmp/deploy.pl
1647 So, once you have the deploy script on the target machine all you need
1648 to do is to execute it. This can come handy when you cannot access the
1649 machines from the network, but you have to go there physically: you
1650 can prepare all in advance, and just call the deploy script.
1661 print a somewhat more verbose help, showing usage, this description of
1662 the options and some examples from the synopsis.
1666 print out the full documentation for the script.
1670 print a concise usage line and exit.
1674 print the version of the script.
1682 =item B<< --bundle | --all-exec | -X >>
1684 Set bundle flag in the produced script. If the bundle flag is set, the
1685 I<deploy script> will treat all executables in the main deployment
1686 directory as scripts to be executed.
1688 By default the flag is not set.
1690 =item B<< --bzip2 | --bz2 | -j >>
1692 Compress tar archives with bzip2.
1694 =item B<< --cleanup | -c >>
1696 Set cleanup flag in the produced script. If the cleanup flag is set, the
1697 I<deploy script> will clean up after having performed all operations.
1699 You can set this flag to C<0> by using C<--no-cleanup>.
1701 =item B<< --deploy | --exec | -d <filename> >>
1703 Set the name of a program to execute after extraction. You can provide
1704 multiple program names, they will be executed in the same order.
1706 =item B<< --gzip | --gz | -z >>
1708 Compress tar archives with gzip.
1710 =item B<< --heredir | -H <path> >>
1712 Set the name of a "here directory" (see L<DESCRIPTION>). You can use this
1713 option multiple times to provide multiple directories.
1715 =item B<< --include-archive-tar | -T >>
1717 Embed L<Archive::Tar> (with its dependencies L<Archive::Tar::Constant> and
1718 L<Archive::Tar::File>) inside the final script. Use this when you know (or
1719 aren't sure) that L<Archive::Tar> will not be available in the target
1722 =item B<< --no-tar >>
1724 Don't use system C<tar>.
1726 =item B<< --output | -o <filename> >>
1728 Set the output file name. By default the I<deploy script> will be given
1729 out on the standard output; if you provide a filename (different from
1730 C<->, of course!) the script will be saved there and the permissions will
1733 =item B<< --root | -r <dirname> >>
1735 Include C<dirname> contents for deployment under root directory. The
1736 actual production procedure is: hop into C<dirname> and grab a tarball
1737 of C<.>. During deployment, hop into C</> and extract the tarball.
1739 This is useful if you're already building up the absolute deployment
1740 layout under a given directory: just treat that directory as if it were
1741 the root of the target system.
1743 =item B<< --rootdir | --in-root | -R <filename> >>
1745 Include C<filename> as an item that will be extracted under root
1746 directory. The actual production procedure is: grab a tarball of
1747 C<filename>. During deployment, hop into C</> and extract the tarball.
1749 This is useful e.g. if you have a directory (or a group of directories)
1750 that you want to deploy directly under the root.
1752 Note that the C<--rootdir> alias is kept for backwards compatibility
1753 but is not 100% correct - you can specify both a dirname (like it was
1754 previously stated) or a single file with this option. This is why it's
1755 more readably to use C<--in-root> instead.
1757 =item B<< --tar | -t <program-path> >>
1759 Set the system C<tar> program to use.
1761 =item B<< --tempdir-mode | -m >>
1763 set default permissions for temporary directory of deployable script
1765 =item B<< --workdir | --deploy-directory | -w <path> >>
1767 Set the working directory for the deploy.
1771 =head1 ROOT OR ROOTDIR?
1773 There are two options that allow you to specify things to be deployed
1774 in C</>, so what should you use? Thing is... whatever you want!
1776 If you have a bunch of directories that have to appear under root, probably
1777 your best bet is to put them all inside a directory called C<myroot> and
1778 use option C<--root>:
1780 shell$ mkdir -p myroot/{etc,opt,var,lib,usr,whatever}
1781 # Now put stuff in the directories created above...
1782 shell$ deployable --root myroot ...
1784 On the other hand, if you just want to put stuff starting from one or
1785 two directories that have to show up in C</>, you can avoid creating
1786 the extra C<myroot> directory and use C<--in-root> instead:
1788 shell$ mkdir -p etc/whatever
1789 # Now put stuff in etc/whatever...
1790 shell$ deployable --in-root etc ...
1792 They are indeed somehow equivalent, the first avoiding you much typing
1793 when you have many directories to be deployed starting from root (just
1794 put them into the same subdirectory), the second allowing you to avoid
1795 putting an extra directory layer.
1797 There is indeed an additional catch that makes them quite different. When
1798 you use C<root>, the whole content of the directory specified will be
1799 used as a base, so you will end up with a listing like this:
1803 opt/local/application/
1804 opt/local/application/myfile.txt
1805 opt/local/application/otherfile.txt
1807 i.e. all intermediate directories will be saved. On the other hand, when
1808 you specify a directory with C<--in-root>, you're not limited to provide
1809 a "single-step" directory, so for example:
1811 shell$ deployable --in-root opt/local/application
1813 will result in the following list of files/directories to be stored:
1815 opt/local/application/
1816 opt/local/application/myfile.txt
1817 opt/local/application/otherfile.txt
1819 i.e. the upper level directories will not be included. What is better for
1820 you is for you to judge.
1822 =head1 THE DEPLOY SCRIPT
1824 The net result of calling this script is to produce another script,
1825 that we call the I<deploy script>. This script is made of two parts: the
1826 code, which is fixed, and the configurations/files, which is what is
1827 actually produced. The latter part is put after the C<__END__> marker,
1830 Stuff in the configuration part is always hexified in order to prevent
1831 strange tricks or errors. Comments will help you devise what's inside the
1832 configurations themselves.
1834 The I<deploy script> has options itself, even if they are quite minimal.
1835 In particular, it supports the same options C<--workdir|-w> and
1836 C<--cleanup> described above, allowing the final user to override the
1837 configured values. By default, the I<workdir> is set to C</tmp>
1838 and the script will clean up after itself.
1840 The following options are supported in the I<deploy script>:
1844 =item B<--usage | --man | --help>
1846 print a minimal help and exit
1850 print script version and exit
1852 =item B<--bundle | --all-exec | -X>
1854 treat all executables in the main deployment directory as scripts
1857 =item B<--cleanup | --no-cleanup>
1859 perform / don't perform temporary directory cleanup after work done
1861 =item B<< --deploy | --no-deploy >>
1863 deploy scripts are executed by default (same as specifying '--deploy')
1864 but you can prevent it.
1866 =item B<--dryrun | --dry-run>
1868 print final options and exit
1870 =item B<< --filelist | --list | -l >>
1872 print a list of files that are shipped in the deploy script
1874 =item B<< --heretar | --here-tar | -H >>
1876 print out the tar file that contains all the files that would be
1877 extracted in the temporary directory, useful to redirect to file or
1878 pipe to the tar program
1880 =item B<< --inspect <dirname> >>
1882 just extract all the stuff into <dirname> for inspection. Implies
1883 C<--no-deploy>, C<--no-tempdir>, ignores C<--bundle> (as a consequence of
1884 C<--no-deploy>), disables C<--cleanup> and sets the working directory
1887 =item B<< --no-tar >>
1889 don't use system C<tar>
1891 =item B<< --rootar | --root-tar | -R >>
1893 print out the tar file that contains all the files that would be
1894 extracted in the root directory, useful to redirect to file or
1895 pipe to the tar program
1897 =item B<--show | --show-options | -s>
1899 print configured options and exit
1901 =item B<< --tar | -t <program-path> >>
1903 set the system C<tar> program to use.
1905 =item B<< --tarfile | -F <filename> >>
1907 add the specified C<filename> (assumed to be an uncompressed
1908 TAR file) to the lot for root extraction. This can come handy
1909 when you already have all the files backed up in a TAR archive
1910 and you're not willing to expand them (e.g. because your
1911 filesystem is case-insensitive...).
1913 =item B<< --tempdir | --no-tempdir >>
1915 by default a temporary directory is created (same as specifying
1916 C<--tempdir>), but you can execute directly in the workdir (see below)
1917 without creating it.
1919 =item B<< --tempdir-mode | -m >>
1921 temporary directories (see C<--tempdir>) created by File::Temp have
1922 permission 600 that prevents group/others from even looking at the
1923 contents. You might want to invoke some of the internal scripts
1924 from another user (e.g. via C<su>), so you can pass a mode to be
1925 set on the temporary directory.
1927 Works only if C<--tempdir> is active.
1929 =item B<--workdir | --work-directory | --deploy-directory | -w>
1931 working base directory (a temporary subdirectory will be created
1936 Note the difference between C<--show> and C<--dryrun>: the former will
1937 give you the options that are "embedded" in the I<deploy script> without
1938 taking into account other options given on the command line, while the
1939 latter will give you the final options that would be used if the script
1940 were called without C<--dryrun>.
1942 =head2 Deploy Script Example Usage
1944 In the following, we'll assume that the I<deploy script> is called
1947 To execute the script with the already configured options, you just have
1952 If you just want to see which configurations are in the I<deploy script>:
1954 shell$ ./deploy.pl --show
1956 To see which files are included, you have two options. One is asking the
1959 shell$ ./deploy.pl --filelist
1961 the other is piping to tar:
1963 shell$ ./deploy.pl --tar | tar tvf -
1965 Extract contents of the script in a temp directory and simply inspect
1968 # extract stuff into subdirectory 'inspect' for... inspection
1969 shell$ ./deploy.pl --no-tempdir --no-deploy --workdir inspect
1971 =head2 Deploy Script Requirements
1973 You'll need a working Perl with version at least 5.6.2.
1975 If you specify L</--include-archive-tar>, the module L<Archive::Tar> will
1976 be included as well. This should ease your life and avoid you to have
1977 B<tar> on the target machine. On the other hand, if you already know
1978 that B<tar> will be available, you can avoid including C<Archive::Tar>
1979 and have the generated script use it (it could be rather slower anyway).
1983 Each error message should be enough explicit to be understood without the
1984 need for furter explainations. Which is another way to say that I'm way
1985 too lazy to list all possible ways that this script has to fail.
1988 =head1 CONFIGURATION AND ENVIRONMENT
1990 deployable requires no configuration files or environment variables.
1992 Please note that deployable B<needs> to find its master B<remote> file
1993 to produce the final script. This must be put in the same directory where
1994 deployable is put. You should be able to B<symlink> deployable where you
1995 think it's better, anyway - it will go search for the original file
1996 and look for B<remote> inside the same directory. This does not apply to
1997 hard links, of course.
2002 All core modules, apart the following:
2006 =item B<< Archive::Tar >>
2008 =item B<< File::Find::Rule >>
2012 =head1 BUGS AND LIMITATIONS
2014 No bugs have been reported.
2016 Please report any bugs or feature requests to the AUTHOR below.
2018 Be sure to read L<CONFIGURATION AND ENVIRONMENT> for a slight limitation
2019 about the availability of the B<remote> script.
2023 Flavio Poletti C<flavio [AT] polettix.it>
2026 =head1 LICENSE AND COPYRIGHT
2028 Copyright (c) 2008, Flavio Poletti C<flavio [AT] polettix.it>. All rights reserved.
2030 This script is free software; you can redistribute it and/or
2031 modify it under the same terms as Perl itself. See L<perlartistic>
2034 =head1 DISCLAIMER OF WARRANTY
2036 BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
2037 FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
2038 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
2039 PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
2040 EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2041 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
2042 ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
2043 YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
2044 NECESSARY SERVICING, REPAIR, OR CORRECTION.
2046 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
2047 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
2048 REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
2049 LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
2050 OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
2051 THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
2052 RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
2053 FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
2054 SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
2059 package main
; # ensure DATA is main::DATA
2062 # *** NOTE *** LEAVE THIS MODULE LIST AS A PARAGRAPH
2066 our $VERSION = '0.2.0';
2067 use English
qw( -no_match_vars );
2068 use Fatal
qw( close chdir opendir closedir );
2069 use File
::Temp
qw( tempdir );
2070 use File
::Path
qw( mkpath );
2071 use File
::Spec
::Functions
qw( file_name_is_absolute catfile );
2072 use File
::Basename
qw( basename dirname );
2073 use POSIX
qw( strftime );
2074 use Getopt
::Long
qw( :config gnu_getopt );
2075 use Cwd
qw( getcwd );
2076 use Fcntl
qw( :seek );
2078 # *** NOTE *** LEAVE EMPTY LINE ABOVE
2079 my %default_config = ( # default values
2088 my $DATA_POSITION = tell DATA
; # GLOBAL VARIABLE
2089 my %script_config = (%default_config, get_config
());
2091 my %config = %script_config;
2092 if ($ENV{DEPLOYABLE_DISABLE_PASSTHROUGH
} || (!$config{passthrough
})) {
2109 show|show-options|s!
2114 workdir|work-directory|deploy-directory|w=s
2117 %config = (%config, %cmdline_config);
2118 } ## end if ($ENV{DEPLOYABLE_DISABLE_PASSTHROUGH...})
2120 usage
() if $config{usage
};
2121 version
() if $config{version
};
2123 if ($config{roottar
}) {
2125 my ($fh, $size) = locate_file
('root');
2126 copy
($fh, \
*STDOUT
, $size);
2128 } ## end if ($config{roottar})
2130 if ($config{heretar
}) {
2132 my ($fh, $size) = locate_file
('here');
2133 copy
($fh, \
*STDOUT
, $size);
2135 } ## end if ($config{heretar})
2137 if ($config{show
}) {
2138 require Data
::Dumper
;
2139 print {*STDOUT
} Data
::Dumper
::Dumper
(\
%script_config);
2143 if ($config{inspect
}) {
2144 $config{cleanup
} = 0;
2145 $config{'no-exec'} = 1;
2146 $config{'tempdir'} = 0;
2147 $config{workdir
} = $config{inspect
};
2148 } ## end if ($config{inspect})
2150 if ($config{dryrun
}) {
2151 require Data
::Dumper
;
2152 print {*STDOUT
} Data
::Dumper
::Dumper
(\
%config);
2156 if ($config{filelist
}) {
2157 my $root_tar = get_sub_tar
('root');
2159 $root_tar->print_filelist();
2160 my $here_tar = get_sub_tar
('here');
2162 $here_tar->print_filelist();
2164 } ## end if ($config{filelist})
2166 # here we have to do things for real... probably, so save the current
2167 # working directory for consumption by the scripts
2168 $ENV{OLD_PWD
} = getcwd
();
2170 # go into the working directory, creating any intermediate if needed
2171 mkpath
($config{workdir
});
2172 chdir($config{workdir
});
2173 print {*STDERR
} "### Got into working directory '$config{workdir}'\n\n"
2174 if $config{verbose
};
2177 if ($config{'tempdir'}) { # Only if allowed
2178 my $me = basename
(__FILE__
) || 'deploy';
2179 my $now = strftime
('%Y-%m-%d_%H-%M-%S', localtime);
2181 join('-', $me, $now, ('X' x
10)),
2183 CLEANUP
=> $config{cleanup
}
2186 if ($config{'tempdir-mode'}) {
2187 chmod oct($config{'tempdir-mode'}), $tempdir
2188 or die "chmod('$tempdir'): $OS_ERROR\n";
2192 or die "chdir('$tempdir'): $OS_ERROR\n";
2194 if ($config{verbose
}) {
2196 "### Created and got into temporary directory '$tempdir'\n";
2197 print {*STDERR
} "### (will clean it up later)\n" if $config{cleanup
};
2198 print {*STDERR
} "\n";
2199 } ## end if ($config{verbose})
2200 } ## end if ($config{'tempdir'})
2202 eval { # Not really needed, but you know...
2203 $ENV{PATH
} = '/bin:/usr/bin:/sbin:/usr/sbin';
2205 execute_deploy_programs
() unless $config{'no-exec'};
2207 warn "$EVAL_ERROR\n" if $EVAL_ERROR;
2209 # Get back so that cleanup can successfully happen, if requested
2210 chdir '..' if defined $tempdir;
2213 my ($filename) = @_;
2215 seek $fh, $DATA_POSITION, SEEK_SET
;
2217 chomp(my $sizes = <$fh>);
2218 my ($name_size, $file_size) = split /\s+/, $sizes;
2219 my $name = full_read
($fh, $name_size);
2220 full_read
($fh, 1); # "\n"
2221 return ($fh, $file_size) if $name eq $filename;
2222 seek $fh, $file_size + 2, SEEK_CUR
; # includes "\n\n"
2223 } ## end while (!eof $fh)
2224 die "could not find '$filename'";
2225 } ## end sub locate_file
2228 my ($fh, $size) = @_;
2232 my $nread = read $fh, $buffer, $size;
2233 die "read(): $OS_ERROR" unless defined $nread;
2234 die "unexpected end of file" unless $nread;
2237 } ## end while ($size)
2239 } ## end sub full_read
2242 my ($ifh, $ofh, $size) = @_;
2245 my $nread = read $ifh, $buffer, ($size < 4096 ?
$size : 4096);
2246 die "read(): $OS_ERROR" unless defined $nread;
2247 die "unexpected end of file" unless $nread;
2248 print {$ofh} $buffer;
2250 } ## end while ($size)
2255 my ($filename) = @_;
2256 my ($fh, $size) = locate_file
($filename);
2257 return Deployable
::Tar
->new(%config, fh
=> $fh, size
=> $size);
2261 my ($fh, $size) = locate_file
('config.pl');
2262 my $config_text = full_read
($fh, $size);
2263 my $config = eval 'my ' . $config_text or return;
2264 return $config unless wantarray;
2266 } ## end sub get_config
2269 my $here_tar = get_sub_tar
('here');
2270 $here_tar->extract();
2272 my $root_dir = $config{inspect
} ?
'root' : '/';
2273 mkpath
$root_dir unless -d
$root_dir;
2276 my $root_tar = get_sub_tar
('root');
2277 $root_tar->extract();
2281 } ## end sub save_files
2283 sub execute_deploy_programs
{
2284 my @deploy_programs = @
{$config{deploy
} || []};
2286 if ($config{bundle
}) { # add all executable scripts in current directory
2287 print {*STDERR
} "### Auto-deploying all executables in main dir\n\n"
2288 if $config{verbose
};
2289 my %flag_for = map { $_ => 1 } @deploy_programs;
2290 opendir my $dh, '.';
2291 for my $item (sort readdir $dh) {
2292 next if $flag_for{$item};
2293 next unless ((-f
$item) || (-l
$item)) && (-x
$item);
2294 $flag_for{$item} = 1;
2295 push @deploy_programs, $item;
2296 } ## end for my $item (sort readdir...)
2298 } ## end if ($config{bundle})
2301 for my $deploy (@deploy_programs) {
2302 $deploy = catfile
('.', $deploy)
2303 unless file_name_is_absolute
($deploy);
2305 print {*STDERR
} "### Skipping '$deploy', not executable\n\n"
2306 if $config{verbose
};
2309 print {*STDERR
} "### Executing '$deploy'...\n"
2310 if $config{verbose
};
2311 system {$deploy} $deploy, @ARGV;
2312 print {*STDERR
} "\n"
2313 if $config{verbose
};
2314 } ## end DEPLOY: for my $deploy (@deploy_programs)
2317 } ## end sub execute_deploy_programs
2320 my $progname = basename
($0);
2321 print {*STDOUT
} <<"END_OF_USAGE" ;
2323 $progname version $VERSION - for help on calling and options, run:
2328 } ## end sub short_usage
2331 my $progname = basename($0);
2332 print {*STDOUT} <<"END_OF_USAGE
" ;
2333 $progname version $VERSION
2335 More or less, this script is intended to be launched without parameters.
2336 Anyway, you can also set the following options, which will override any
2337 present configuration (except in "--show
-options
"):
2339 * --usage | --man | --help
2340 print these help lines and exit
2343 print script version and exit
2345 * --bundle | --all-exec | -X
2346 treat all executables in the main deployment directory as scripts
2349 * --cleanup | -c | --no-cleanup
2350 perform / don't perform temporary directory cleanup after work done
2352 * --deploy | --no-deploy
2353 deploy scripts are executed by default (same as specifying '--deploy')
2354 but you can prevent it.
2356 * --dryrun | --dry-run
2357 print final options and exit
2359 * --filelist | --list | -l
2360 print a list of files that are shipped in the deploy script
2362 * --heretar | --here-tar | -H
2363 print out the tar file that contains all the files that would be
2364 extracted in the temporary directory, useful to redirect to file or
2365 pipe to the tar program
2367 * --inspect | -i <dirname>
2368 just extract all the stuff into <dirname> for inspection. Implies
2369 --no-deploy, --no-tempdir, ignores --bundle (as a consequence of
2370 --no-deploy), disables --cleanup and sets the working directory
2374 don't use system "tar
"
2376 * --roottar | --root-tar | -R
2377 print out the tar file that contains all the files that would be
2378 extracted in the root directory, useful to redirect to file or
2379 pipe to the tar program
2381 * --show | --show-options | -s
2382 print configured options and exit
2384 * --tar | -t <program-path>
2385 set the system "tar
" program to use.
2387 * --tempdir | --no-tempdir
2388 by default a temporary directory is created (same as specifying
2389 '--tempdir'), but you can execute directly in the workdir (see below)
2390 without creating it.
2392 * --tempdir-mode | -m
2393 set permissions of temporary directory (octal string)
2395 * --workdir | --work-directory | --deploy-directory | -w
2396 working base directory (a temporary subdirectory will be created
2404 print "$0 version
$VERSION\n";
2408 package Deployable::Tar;
2411 my $package = shift;
2412 my $self = {ref $_[0] ? %{$_[0]} : @_};
2413 $package = 'Deployable::Tar::Internal';
2414 if (!$self->{'no-tar'}) {
2415 if ((exists $self->{tar}) || (open my $fh, '-|', 'tar', '--help')) {
2416 $package = 'Deployable::Tar::External';
2417 $self->{tar} ||= 'tar';
2419 } ## end if (!$self->{'no-tar'})
2420 bless $self, $package;
2421 $self->initialise() if $self->can('initialise');
2425 package Deployable::Tar::External;
2426 use English qw( -no_match_vars );
2431 $self->{bzip2
} ?
'j'
2432 : $self->{gzip
} ?
'z'
2434 $self->{_list_command
} = 'tv' . $compression . 'f';
2435 $self->{_extract_command
} = 'x' . $compression . 'f';
2436 } ## end sub initialise
2438 sub print_filelist
{
2440 if ($self->{size
}) {
2441 open my $tfh, '|-', $self->{tar
}, $self->{_list_command
}, '-'
2442 or die "open() on pipe to tar: $OS_ERROR";
2443 main
::copy
($self->{fh
}, $tfh, $self->{size
});
2446 } ## end sub print_filelist
2450 if ($self->{size
}) {
2451 open my $tfh, '|-', $self->{tar
}, $self->{_extract_command
}, '-'
2452 or die "open() on pipe to tar: $OS_ERROR";
2453 main
::copy
($self->{fh
}, $tfh, $self->{size
});
2456 } ## end sub extract
2458 package Deployable
::Tar
::Internal
;
2459 use English
qw( -no_match_vars );
2464 if ($self->{size
}) {
2465 my $data = main
::full_read
($self->{fh
}, $self->{size
});
2466 open my $fh, '<', \
$data
2467 or die "open() on internal variable: $OS_ERROR";
2469 require Archive
::Tar
;
2470 $self->{_tar
} = Archive
::Tar
->new();
2471 $self->{_tar
}->read($fh);
2472 } ## end if ($self->{size})
2475 } ## end sub initialise
2477 sub print_filelist
{
2479 if ($self->{size
}) {
2480 print {*STDOUT
} " $_\n" for $self->{_tar
}->list_files();
2483 } ## end sub print_filelist
2487 if ($self->{size
}) {
2488 $self->{_tar
}->extract();
2491 } ## end sub extract