#!perl -wT # Copyright Dominique Quatravaux 2006 - Licensed under the same terms as Perl itself use strict; use warnings; use 5.006; # "our" keyword =head1 NAME My::Module::Build - Helper for releasing my (DOMQ's) code to CPAN =head1 SYNOPSIS This module works mostly like L with a few differences highlighted below. Put this in Build.PL: =for My::Tests::Below "synopsis" begin use strict; use warnings; ## Replace # use Module::Build; ## with use FindBin; use lib "$FindBin::Bin/inc"; use My::Module::Build; ## Replace # my $builder = Module::Build->new( ## With my $builder = My::Module::Build->new( ## ... Use ordinary Module::Build arguments here ... build_requires => { 'Acme::Pony' => 0, My::Module::Build->requires_for_build(), }, add_to_no_index => { namespace => [ "My::Private::Stuff" ] }, ); ## The remainder of the script works like with stock Module::Build =for My::Tests::Below "synopsis" end =head1 DESCRIPTION DOMQ is a guy who releases CPAN packages from time to time - you are probably frobbing into one of them right now. This module is a subclass to L by Ken Williams, and a helper that supports DOMQ's coding style for Perl modules so as to facilitate relasing my code to the world. =head2 How to use My::Module::Build for a new CPAN package This part of the documentation is probably only useful to myself, but hey, you never know - Feel free to share and enjoy! =over =item 1. If not already done, prepare a skeletal CPAN module that uses L as its build support class. L and its companion command C is B recommended for this purpose, e.g. module-starter --mb --module=Main::Screen::Turn::On \ --author='Dominique Quatravaux' --email='domq@cpan.org' --force =item 2. create an C subdirectory at the CPAN module's top level and drop this file there. (While you are there, you could put the rest of the My:: stuff along with it, and the t/maintainer/ test cases - see L.) =item 3. Amend the Build.PL as highlighted in the L. =item 4. B Arrange for My::Module::Build and friends to B be indexed on the CPAN, lest the Perl deities' wrath fall upon you. This is done by adding the following lines to the META.yml file: =for My::Tests::Below "META.yml excerpt" begin no_index: directory: - examples - inc - t =for My::Tests::Below "META.yml excerpt" end (indentation is meaningful - "no_index:" must start at the very first column and the indenting quantum is exactly 2 spaces, B) If you prefer the META.yml file to be built automatically, do a =for My::Tests::Below "distmeta" begin ./Build manifest ./Build distmeta =for My::Tests::Below "distmeta" end and the aforementioned no_index exclusions will be set up automatically (but B). =back =head2 Coding Style and Practices supported by this module No, I don't want to go into silly regulations regarding whether I should start a new line before the opening bracket in a sub declaration. This would be coding syntax, or coding grammar. The stuff here is about style, and only the subset thereof that is somehow under control of the CPAN build process. =head3 Unit tests A large fraction of the unit tests are written as perlmodlib-style __END__ documents attached directly to the module to test. See L for details. My::Module::Build removes the test footer at build time so as not to waste any resources on the install target platform. =head3 Extended C action The C<./Build test> action allows one to specify a list of individual test scripts to run, in a less cumbersome fashion than straight L: ./Build test t/sometest.t lib/Foo/Bar.pm For the developper's comfort, if only one test is specified in this way, I assumes that I mode is wanted (see L). This DWIM can be reversed on the command line: ./Build test verbose=0 t/sometest.t In the case of running a single test, I also automatically detects that we are running under Emacs' perldb mode and runs the required test script under the Perl debugger. Running a particular test under Emacs perldb is therefore as simple as typing: M-x perldb /path/to/CPAN/module/Build test MyModule.pm If a relative path is passed (as shown), it is interpreted relative to the current directory set by Emacs (which, except under very bizarre conditions, will be the directory of the file currently being edited). The verbose switch above applies here by default, conveniently causing the test script to run in verbose mode in the debugger. Like the original L, C<./Build test> accepts supplemental key=value command line switches, as exemplified above with C. Additional switches are provided by I: =over =item I<< use_blib=0 >> Load modules from the B directory (e.g. C) instead of the build directories (e.g. C and C). I use this to debug L code in a tight tweak-run-tweak-run loop, a situation in which the MD5-on-C-code feature of L saves a lot of rebuilds. =item I Sets the FULL_DEBUGGING environment variable to 1 while running C<./Build test>, in addition to any environment customization already performed by L. Packages of mine that use L enable extra debugging when this environment variable is set. =back =head3 Dependent Option Graph This feature wraps around L, L and L to streamline the programming of optional features into a ./Build.PL script. Here is a short synopsis for this feature: =for My::Tests::Below "option-graph" begin my $class = My::Module::Build->subclass(code => <<'CODE'); sub install_everything: Config_Option { question => "Install everything", default => 1; } sub install_module_foo: Config_Option(type="boolean") { my $build = shift; return (default => 1) # Don't even bother asking the question if $build->option_value("install_everything"); question => "Install module foo", default => 0; } CODE my $builder = $class->new(...) # See SYNOPSIS =for My::Tests::Below "option-graph" end Options can then be fed from the command line (e.g. C<< ./Build.PL --gender=f >>) or by answering the questions interactively on the terminal. I will ask the questions at L time, in the correct order if they depend on each other (as shown in the example), detect circular dependencies, and die if a mandatory question does not get an appropriate answer. =head3 Syntax As shown above, options are methods in a subclass to I with a subroutine attribute of the form C<< Config_Option(key1=value1, ...) >>. Right now the following keys are defined: =over =item I The datatype of this option, either as a word (e.g. "boolean", "integer" or "string") or as a L qualifier (e.g. "!", "=s" or "=i"). The default is to guess from the name of the option: "install_foo" and "enable_bar" are supposed to be booleans, "baz_port" an integer, and everything else a string. =back The name of the method is the internal key for the corresponding option (e.g. for L). It is also the name of the corresponding command-line switch, except that all underscores are converted to dashes. The method shall return a (key, value) "flat hash" with the following keys recognized: =over =item I The question to ask, as text. A question mark is appended automatically for convenience if there isn't already one. If no question is set, I will not ask anything for this question even in interactive mode, and will attempt to use the default value instead (see below). =item I In batch mode, the value to use if none is available from the command line or the persisted answer set from previous attempts to run ./Build.PL. In interactive mode, the value to offer to the user as the default. =item I A Boolean indicating whether answering the question with a non-empty value is mandatory (see also L for a twist on what "non-empty" exactly means). The default mandatoryness is 1 if I is not returned, 0 if I is returned (even with an undef value). =back =head1 REFERENCE =cut package My::Module::Build; use strict; use warnings; use base "Module::Build"; use IO::File; use File::Path qw(mkpath); use File::Spec::Functions qw(catfile catdir splitpath splitdir); use File::Basename qw(dirname); use File::Spec::Unix (); use File::Find; =begin internals =head2 Global variables =head3 $running_under_emacs_debugger Set by L if (you guessed it) we are currently running under the Emacs debugger. =cut our $running_under_emacs_debugger; =head2 Constants =head3 is_win32 Your usual bugware-enabling OS checks. =cut use constant is_win32 => scalar($^O =~ /^(MS)?Win32$/); =head2 read_file($file) =head2 write_file($file, @lines) Like in L. =cut sub read_file { my ($filename) = @_; defined(my $file = IO::File->new($filename, "<")) or die <<"MESSAGE"; Cannot open $filename for reading: $!. MESSAGE return wantarray? <$file> : join("", <$file>); } sub write_file { my ($filename, @contents) = @_; defined(my $file = IO::File->new($filename, ">")) or die <<"MESSAGE"; Cannot open $filename for writing: $!. MESSAGE ($file->print(join("", @contents)) and $file->close()) or die <<"MESSAGE"; Cannot write into $filename: $!. MESSAGE } =end internals =head2 Constructors and Class Methods These are intended to be called directly from Build.PL =over =item I Overloaded from parent class in order to call L if L is true. Also sets the C property to true by default (see L), since I like to store maintainer-only tests in C (as documented in L). In addition to the %named_options documented in L, I provides support for the following switches: =over =item I<< add_to_no_index => $data_structure >> Appends the aforementioned directories and/or namespaces to the list that L stores in META.yml. Useful to hide some of the Perl modules from the CPAN index. =back =cut sub new { my ($class, %opts) = @_; $opts{recursive_test_files} = 1 if (! defined $opts{recursive_test_files}); my $self = $class->SUPER::new(%opts); if ($self->maintainer_mode_enabled()) { print "Running specific maintainer checks...\n"; $self->check_maintainer_dependencies(); } $self->_process_options; $self; } =item I Returns a list of packages that are required by I itself, and should therefore be appended to the C hash as shown in L. =cut sub requires_for_build { ('IO::File' => 0, 'File::Path' => 0, 'File::Spec' => 0, 'File::Spec::Functions' => 0, 'File::Spec::Unix' => 0, 'File::Find' => 0, 'Module::Build' => 0, 'Module::Build::Compat' => 0, 'FindBin' => 0, # As per L # The following are actually requirements for tests: 'File::Temp' => 0, # for tempdir() in My::Tests::Below 'Fatal' => 0, # Used to cause tests to die early if fixturing # fails, see sample in this module's test suite # (at the bottom of this file) ); } { no warnings "once"; *requires_for_tests = \&requires_for_build; # OBSOLETE misnomer } =item I Returns true iff we are running "./Build.PL" or "./Build" off a revision control system of some kind. Returns false in all other situations, especially if we are running on an untarred package downloaded from CPAN. =cut sub maintainer_mode_enabled { my $self = shift; foreach my $vc_dir (qw(CVS .svn .hg .git)) { return 1 if -d catdir($self->base_dir, $vc_dir); } my $svk_cmd = sprintf("yes n | svk info '%s' 2>%s", catdir($self->base_dir, "Build.PL"), File::Spec->devnull); `$svk_cmd`; return 1 if ! $?; # `svk info` puts the terminal into non-echo mode if run before # initializing a main depot. Shame, shame... eval { require Term::ReadKey; 1 } and Term::ReadKey::ReadMode("normal"); return 0; } =item I Checks that the modules required for B the CPAN package are installed on the target system, and displays a friendly, non-fatal message otherwise. This method is automatically run from L if appropriate (that is, if L is true). =cut sub check_maintainer_dependencies { my $self = shift; unless ($self->check_installed_status('YAML', 0)->{ok}) { $self->show_warning(<<"MESSAGE"); } The YAML module from CPAN is missing on your system. YAML is required for the "./Build distmeta" operation. You have to run that command to regenerate META.yml every time you add a new .pm, change dependencies or otherwise alter the namespace footprint of this CPAN package. You will therefore only be able to contribute small bugfixes until you install YAML. MESSAGE foreach my $testmod (qw(Test::NoBreakpoints Test::Pod Test::Pod::Coverage)) { unless ($self->check_installed_status($testmod, 0)->{ok}) { $self->show_warning(<<"MESSAGE")}; The $testmod module from CPAN is missing on your system. One of the tests in t/maintainer will fail because of that. Please install the corresponding module to run the full test suite. MESSAGE } } =item I Displays a multi-line message $message to the user, and prompts him/her to "Press RETURN to continue". =cut sub show_warning { my ($self, $message) = @_; $message = "\n$message" until ($message =~ m/^\n\n/); $message .= "\n" until ($message =~ m/\n\n$/); warn $message; $self->prompt("Press RETURN to continue"); 1; } =item I Like L, but throws an exception after displaying $message. =cut sub show_fatal_error { my ($self, $message) = @_; $self->show_warning($message); die "Fatal error, bailing out.\n"; } =back =head2 Methods These are intended to be called directly from Build.PL =over =item I Returns the directory in which C resides. =cut sub topdir { # TODO: probably not good enough in some cases. require FindBin; no warnings "once"; return $FindBin::Bin; } =item I Converts $packagename (e.g. C) into its OS-specific notation (e.g. C). =cut sub package2filename { my ($self, $package) = @_; my @components = split m/::/, $package; $components[$#components] .= ".pm"; return catfile(@components); } =item I Arranges for L code contained in $filename to be compiled into .bs's and .so's. @preload_modules is a list of Perl packages (in Perl C notation, eg C instead of C) that should be loaded with C before starting the L install process. Uses a stamp file in C to avoid compiling anew if neither $filename nor @preload_modules changed. =cut sub process_Inline_C_file { my ($self, $filename, @preload_modules) = @_; my $stampfile = do { my ($volume, $dir, $base) = splitpath($filename); catfile(qw(blib stamp Inline-C), join("_", splitdir($dir), $base)); }; return if $self->up_to_date ([$filename, map { catfile("lib", $self->package2filename($_)) } @preload_modules], [$stampfile]); # Remove any leftovers from a (failed) previous run. do { unlink($_) or die "Cannot unlink($_): $!" } for glob("*.inl"); # And now some ugly kludge to make everything hold together. # Inline::C wants to use MakeMaker; we don't. So let's call it in # a sub-Perl. my $version = $self->dist_version; my $module_name = $self->module_name; my $script = <<"SET_VERSION"; BEGIN { \$${module_name}::VERSION = '$version' ; } SET_VERSION $script .= "use $_; " foreach (@preload_modules); $script .= <<"ACTIVATE_INLINE_COMPILE"; use Inline qw(_INSTALL_); require "$filename"; ACTIVATE_INLINE_COMPILE $script =~ s/\n/ /g; my @cmdline = ($^X, "-I" => catdir($self->topdir, "lib"), -e => $script, $version, catdir(qw(blib arch))); warn(join(" ", @cmdline, "\n")); local %ENV = $self->customize_env(%ENV); system(@cmdline); die "Command exited with status " . ($? >> 8) if $?; # Remove the leftovers again. do { unlink($_) or die "Cannot unlink($_): $!" } for glob("*.inl"); rmdir("arch"); # Update timestamp if (! -d (my $stampdir = dirname($stampfile))) { mkpath($stampdir, 0, 0777) or die "cannot create directory $stampdir: $!"; } local *STAMP; open(STAMP, ">>", $stampfile) or die "cannot create or update timestamp file $stampfile: $!"; close(STAMP); utime((time) x 2, $stampfile); } =item I =item I Returns false if the user specified C on the command line, and true otherwise. See L action> for details. The form with a parameter allows one to set the value that will subsequently be returned by I, thereby overriding the command line. =cut sub use_blib { my $self = shift; if (! @_) { return 1 if (! exists $self->{args}->{use_blib}); return ! ! $self->{args}->{use_blib}; } else { $self->{args}->{use_blib} = ! ! shift; } } =back =head2 Dependent Option Graph Methods =cut # These "use" statements are specific to the dependent option graph # to facilitate refactoring. use Getopt::Long; use Carp; use overload; # for overload::StrVal =over =item I Returns the value selected for the option $optionname. From within an option declaration sub, this call may result in the question for $optionname (and its own dependencies, recursively) being asked on the terminal at once. If a loop is detected so doing, I will die with a messsage that starts with the word "RECURSION". Answers to questions are persisted using Module::Build's I<< ->notes >> mechanism: outside the option declaration subs, I is therefore an alias for I. =cut sub option_value { my ($self, $key) = @_; $key =~ s/-/_/g; my $noteskey = "option:$key"; my $cached = $self->notes($noteskey); return $cached if defined $cached; my $answer = $self->_option_value_nocache($key); $self->notes($noteskey, $answer); return $answer; } =begin internals =item I Automatically invoked by Perl when parsing subroutine attributes (see L); parses and stores the C attributes described in L. =cut our %declared_options; our %option_type; sub MODIFY_CODE_ATTRIBUTES { my ($package, $coderef, @attrs) = @_; $coderef = overload::StrVal($coderef); my @retval; ATTRIBUTE: foreach my $attr (@attrs) { unless ($attr =~ m/^\s*Config_Option\s*(?:|\(([^()]*)\))\s*$/) { push @retval, $attr; # Pass to downstream handlers next ATTRIBUTE; } $declared_options{$coderef}++; next ATTRIBUTE if ! defined $1; # No keys / values foreach my $keyval (split qr/\s*,\s*/, $1) { if ($keyval =~ m/^type\s*=\s*(\S+)\s*$/) { my $type = $1; $type =~ s/^"(.*)"$/$1/s; $type =~ s/^'(.*)'$/$1/s; my %canonicaltype = ( (map { $_ => "string" } qw(=s string)), (map { $_ => "integer" } qw(=i int integer)), (map { $_ => "boolean" } qw(! bool boolean)), ); defined ($option_type{$coderef} = $canonicaltype{$type}) or die qq'Bad type "$type" in attribute "$attr"'; } else { die qq'Unknown key "$keyval" in attribute "$attr"'; } } } return @retval; } =item I<_option_value_nocache($key)> The workhorse behind L, which is just a caching wrapper. =cut sub _option_value_nocache { my ($self, $key) = @_; # return $self->_option_default_value($key) if # ($self->_option_phase($key) ne $self->{phase}); do { # Look at command line my $keyopt = lc($key); $keyopt =~ s/_/-/g; my %type2getopt = ("string" => "=s", "integer" => "=i", "boolean" => "!"); my $getopt = new Getopt::Long::Parser(config => [qw(pass_through)]); my $type = $self->_option_type($key); my $retval; $getopt->getoptions($keyopt . $type2getopt{$type} => \$retval) or die "Bad value for --$keyopt command-line option". " (expected $type)\n"; if (defined $retval) { $self->_option_check_value($key, \$retval); return $retval; } }; my $default = $self->_option_default_value($key); if (defined(my $question = $self->_option_question($key))) { # Ask user if ($self->_option_type($key) eq "boolean") { $default = $default ? "yes" : "no"; } ASK_AGAIN: { my $answer = $self->prompt($question, $default); my $problem = $self->_option_check_value($key, \$answer); return $answer if (! $problem); if (-t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT))) { warn $problem; redo ASK_AGAIN; } else { die $problem; } } }; return $default; } =item I<_option_type($key)> Returns the type for option $key (either "boolean", "integer" or "string"), or undef if no such option exists. =cut sub _option_type { my ($self, $key) = @_; croak "Unknown question $key" unless (my $meth = $self->can($key)); my $type = $option_type{overload::StrVal($meth)}; $type ||= $key =~ m/^(install_|enable_)/ ? "boolean" : $key =~ m/(_port)$/ ? "integer" : "string"; return $type; } =item I<_option_is_mandatory($key)> Returns true if a value is mandatory for $key; always false in the case of a boolean. =cut sub _option_is_mandatory { my ($self, $key) = @_; return if $self->_option_type eq "boolean"; my $t = $self->_option_compute_template($key); return $t->{mandatory} if exists $t->{mandatory}; return (exists $t->{default}); } =item I<_option_default_value($key)> Returns the option's default value, taken either from the answers from the previous run of Build.PL (if available) or from the option template method's return value. =cut sub _option_default_value { my ($self, $key) = @_; my $previousrun = $self->notes("option:$key"); return $previousrun if defined $previousrun; return $self->_option_compute_template($key)->{default}; } =item I<_option_question($key)> Returns the question to ask interactively in order to get a value for option $key. The return value may be undef, indicating that the question shall not be asked interactively. =cut sub _option_question { my ($self, $key) = @_; my $question = $self->_option_compute_template($key)->{question}; return if ! defined $question; $question .= '?' unless ($question =~ m/\?/); return $question; } =item I<_option_compute_template($key)> Returns a reference to a hash with keys C, C and C according to what the option template method returned. Arranges to run the template method only once. =cut sub _option_compute_template { my ($self, $key) = @_; return $self->{"option_template"}->{$key} if (exists $self->{"option_template"}->{$key}); croak "Unknown question $key" unless (my $meth = $self->can($key)); return ($self->{"option_template"}->{$key} = { $meth->($self) }); } =pod =item I<_option_check_value($key, $answerref)> Checks that the answer pointed to by $answerref matches the relevant invariants (namely type and mandatoryness). $$answerref may not be undef. It is canonicalized through in-place modification if need be (e.g. "yes" becomes 1 for booleans). In scalar context, returns undef if all went well or a warning message as text. In void context and in case of a problem, raises this same warning message as an exception. =cut sub _option_check_value { my ($self, $key, $answerref) = @_; my $problem; my $type = $self->_option_type($key); if (! defined $$answerref) { $problem = "Internal error: \$\$answerref may not be undef"; } elsif ($type eq "boolean") { $$answerref = 0 if (! $$answerref); $$answerref =~ s/(yes|y|true)/1/i; $$answerref =~ s/(no|n|false)/0/i; $problem = "Option $key expects a boolean value" unless ($$answerref =~ m/^\s*(0|1)\s*$/); $$answerref = $1; } elsif (! length $$answerref) { $problem = "Option $key is mandatory" if $self->_option_is_mandatory($key); } elsif ($type eq "integer") { $problem = "Option $key expects an integer" unless ($$answerref =~ m/^\s*(-?\d+)\s*$/); $$answerref = $1; } die $problem if ($problem && ! defined wantarray); return $problem; } =item I<_process_options()> Runs L for all known options, which in turn causes the command line switches to be processed and/or all appropriate interactive questions to be asked and answered. =cut sub _process_options { my ($self) = @_; # Walks @ISA looking for the names of all methods that are # command-line options. Inspired from DB::methods_via in # perl5db.pl my $walk_isa; $walk_isa = sub { my ($class, $seenref, $resultref) = @_; return if $seenref->{$class}++; my $symtab = do { no strict "refs"; \%{"${class}::"}; }; push @$resultref, grep { my $symbol = $symtab->{$_}; ref(\$symbol) eq "GLOB" && defined(*{$symbol}{CODE}) && $declared_options{overload::StrVal(*{$symbol}{CODE})}; } (keys %$symtab); no strict "refs"; $walk_isa->($_, $seenref, $resultref) foreach @{"${class}::ISA"}; }; my @alloptions; $walk_isa->( (ref($self) or $self), {}, \@alloptions); $self->option_value($_) foreach @alloptions; return @alloptions; } =end internals =back =head2 Other Public Methods Those methods will be called automatically from within the generated ./Build, but on the other hand one probably shouldn't call them directly from C . One may wish to overload some of them in a package-specific subclass, however. =over =item I Overloaded to add L as a dependency. =cut sub ACTION_build { my $self = shift; $self->depends_on("buildXS"); $self->SUPER::ACTION_build(@_); } =item I Overloaded so that typing C<./Build dist> does The Right Thing and regenerates everything that is needed in order to create the distribution tarball. This includes the C if so requested (see L) and the C file (see L). On the other hand, the C file is not regenerated automatically, so that the author has the option of maintaining it by hand. =cut sub ACTION_dist { my $self = shift; $self->do_create_makefile_pl if $self->create_makefile_pl; $self->do_create_readme if $self->create_readme; $self->depends_on("manifest"); $self->SUPER::ACTION_dist(@_); } =item I Does nothing. Intended for overloading by packages that have XS code, which e.g. may want to call L there. =cut sub ACTION_buildXS { } =item I Overloaded to add t/lib and t/inc to the test scripts' @INC (we sometimes put helper test classes in there), and also to implement the features described in L action>. See also L for more bits of the Emacs debugger support code. =cut sub ACTION_test { my $self = shift; # Tweak @INC (done this way, works regardless of whether we'll be # doing the harnessing ourselves or not) local @INC = (@INC, catdir($self->base_dir, "t", "lib"), catdir($self->base_dir, "t", "inc")); # use_blib feature, part 1: $self->depends_on("buildXS") if $self->use_blib; my @files_to_test = map { our $initial_cwd; # Set at BEGIN time, see L<_startperl> File::Spec->rel2abs($_, $initial_cwd) } (@{$self->{args}->{ARGV} || []}); if ($running_under_emacs_debugger && @files_to_test == 1) { # We want to run this script under a slave_editor debugger, so # as to implement the documented trick. The simplest way # (although inelegant) is to bypass Module::Build and # Test::Harness entirely, and run the child Perl # ourselves. Most of the code below was therefore cobbled # together from the real T::H version 2.40 and M::B 0.26 $self->depends_on('code'); # As in original ACTION_test # Compute adequate @INC for sub-perl: my @inc = do { my %inc_dupes; grep { !$inc_dupes{$_}++ } @INC }; if (is_win32) { s/[\\\/+]$// foreach @inc; } # Add blib/lib and blib/arch like the original ACTION_test does: if ($self->use_blib) { unshift @inc, catdir($self->base_dir(), $self->blib, 'lib'), catdir($self->base_dir(), $self->blib, 'arch'); } else { unshift @inc, catdir($self->base_dir(), 'lib'); } # Parse shebang line to set taintedness properly: local *TEST; open(TEST, $files_to_test[0]) or die "Can't open $files_to_test[0]. $!\n"; my $shebang = ; close(TEST) or print "Can't close $files_to_test[0]. $!\n"; my $taint = ( $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/ ); my ($perl) = ($^X =~ m/^(.*)$/); # Untainted system($perl, "-d", ($taint ? ("-T") : ()), (map { ("-I" => $_) } @inc), $files_to_test[0], "-emacs"); return; } # Localize stuff in order to fool our superclass for fun & profit local %ENV = $self->customize_env(%ENV); local $self->{FORCE_find_test_files_result}; # See L $self->{FORCE_find_test_files_result} = \@files_to_test if @files_to_test; # DWIM for ->{verbose} (see POD) local $self->{properties} = { %{$self->{properties}} }; if (@files_to_test == 1) { $self->{properties}->{verbose} = 1 if (! defined $self->{properties}->{verbose}); } # use_blib feature, cont'd: no warnings "once"; local *blib = sub { my $self = shift; return File::Spec->curdir if ! $self->use_blib; return $self->SUPER::blib(@_); }; $self->SUPER::ACTION_test(@_); } =item I Overloaded to ensure that .pm modules in inc/ don't get indexed and that the C parameter to L is honored. =cut sub ACTION_distmeta { my $self = shift; eval { require YAML } or die ($@ . <<"MESSAGE"); YAML is required for distmeta to produce accurate results. Please install it and re-run this command. MESSAGE # Steals a reference to the YAML object that will be constructed # by the parent class (duhh) local our $orig_yaml_node_new = \&YAML::Node::new; local our $node; no warnings "redefine"; local *YAML::Node::new = sub { $node = $orig_yaml_node_new->(@_); }; my $retval = $self->SUPER::ACTION_distmeta; die "Failed to steal the YAML node" unless defined $node; $node->{no_index} = $self->{properties}->{add_to_no_index} || {}; $node->{no_index}->{directory} ||= []; unshift(@{$node->{no_index}->{directory}}, qw(examples inc t), (map { File::Spec::Unix->catdir("lib", split m/::/) } (@{$node->{no_index}->{namespace} || []}))); foreach my $package (keys %{$node->{provides}}) { delete $node->{provides}->{$package} if (grep {$package =~ m/^\Q$_\E/} @{$node->{no_index}->{namespace} || []}); delete $node->{provides}->{$package} if (grep {$package eq $_} @{$node->{no_index}->{package} || []}); } my $metafile = $self->can("metafile") ? # True as of Module::Build 0.2805 $self->metafile() : $self->{metafile}; # YAML API changed after version 0.30 my $yaml_sub = ($YAML::VERSION le '0.30' ? \&YAML::StoreFile : \&YAML::DumpFile); $yaml_sub->($metafile, $node) or die "Could not write to $metafile: $!"; ; } =item I Returns a copy of %env, an environment hash, modified in a package-specific fashion. To be used typically as local %ENV = $self->customize_env(%ENV); The default implementation sets PERL_INLINE_BUILD_NOISY to 1 and also sets FULL_DEBUGGING if so directed by the command line (see L). =cut sub customize_env { my ($self, %env) = @_; delete $env{FULL_DEBUGGING}; $env{PERL_INLINE_BUILD_NOISY} = 1; $env{FULL_DEBUGGING} = 1 if ($self->{args}->{full_debugging}); return %env; } =item I Called internally in Build to convert lib/**.pm files into their blib/**.pm counterpart; overloaded here to remove the test suite (see L) and standardize the copyright of the files authored by me. =cut sub process_pm_files { no warnings "once"; local *copy_if_modified = \*process_pm_file_if_modified; my $self = shift; return $self->SUPER::process_pm_files(@_); } =item I Does the same as L (which it actually replaces while L runs), except that the L is applied instead of performing a vanilla copy as L does. =cut sub process_pm_file_if_modified { my ($self, %args) = @_; my ($from, $to) = @args{qw(from to)}; return if $self->up_to_date($from, $to); # Already fresh mkpath(dirname($to), 0, 0777); # Do a filtering copy print "$from -> $to\n" if $args{verbose}; die "Cannot open $from for reading: $!\n" unless (my $fromfd = new IO::File($from, "r")); die "Cannot open $to for writing: $!\n" unless (my $tofd = new IO::File($to, "w")); my $filter = $self->new_pm_filter; while(my $line = <$fromfd>) { my $moretext = $filter->filter($line); if (defined($moretext) && length($moretext)) { $tofd->print($moretext) or die "Cannot write to $to: $!\n"; } last if $filter->eof_reached(); } $tofd->close() or die "Cannot close to $to: $!\n"; } =item I Creates and returns a fresh filter object (see L) that will be used by L to process the text of the .pm files. Subclasses may find it convenient to overload I in order to provide a different filter. The filter object should obey the API set forth in L, although it need not inherit from same. =cut sub new_pm_filter { My::Module::Build::PmFilter->new } =item I Overloaded from parent class to treat all .pm files in C and C as unit tests if they use L, to look for C<.t> files in C, and to retain C<.t> test files in C if and only if L is true. =cut sub find_test_files { my $self = shift; # Short-cut activated by L: return $self->{FORCE_find_test_files_result} if (defined $self->{FORCE_find_test_files_result}); my @tests = @{$self->SUPER::find_test_files(@_)}; # Short-cut activated by putting a 'test_files' key in the constructor # arguments: return @tests if $self->{test_files}; @tests = grep { ! m/^t.maintainer/ } @tests unless ($self->maintainer_mode_enabled()); File::Find::find ({no_chdir => 1, wanted => sub { push(@tests, $_) if $self->find_test_files_predicate(); }}, $self->find_test_files_in_directories); return \@tests; } =item I =item I Those two methods are used as callbacks by L; subclasses of I may therefore find it convenient to overload them. I should return a list of the directories in which to search for test files. I gets passed the name of each file found in these directories in the same way as a L C sub would (that is, using $_ and B the argument list); it should return a true value iff this file is a test file. =cut sub find_test_files_predicate { my ($self) = @_; return 1 if m/My.Tests.Below\.pm$/; return if m/\b[_.]svn\b/; # Subversion metadata return 1 if m/\.t$/; my $module = catfile($self->base_dir, $_); local *MODULE; unless (open(MODULE, "<", $module)) { warn "Cannot open $module: $!"; return; } return 1 if grep { m/^require\s+My::Tests::Below\s+unless\s+caller/ } (); return; } sub find_test_files_in_directories { grep { -d } ("lib", catdir("t", "lib"), "examples"); } =back =begin internals =head1 INTERNAL DOCUMENTATION This section describes how My::Module::Build works internally. It should be useful only to people who intend to modify it. =over =item I =item I Overloaded respectively from L and L so that typing =for My::Tests::Below "great justice" begin perl Makefile.PL make your time =for My::Tests::Below "great justice" end produces a helpful message in packages that have a Makefile.PL (see L for how to do that). You won't get signal if you use a "traditional" style Makefile.PL (but on the other hand the rest of I will not work either, so don't do that). This easter egg was a feature of an old GNU-make based build framework that I created in a former life. So there. =cut sub do_create_makefile_pl { my ($self, %args) = @_; warn("Cannot take off any Zig, sorry"), return $self->SUPER::do_create_makefile_pl(%args) if ($args{fh}); $args{file} ||= 'Makefile.PL'; my $retval = $self->SUPER::do_create_makefile_pl(%args); my $MakefilePL = read_file($args{file}); $MakefilePL = <<'PREAMBLE' . $MakefilePL; use FindBin qw($Bin); use lib "$Bin/inc"; PREAMBLE $MakefilePL =~ s|Module::Build::Compat->write_makefile|My::Module::Build::HowAreYouGentlemen->write_makefile|; write_file($args{file}, $MakefilePL); return $retval; } { package My::Module::Build::HowAreYouGentlemen; our @ISA=qw(Module::Build::Compat); # Do not explicitly load it because # Makefile.PL will set up us the Module::Build::Compat itself (and # also we want to take off every zig of bloat when # My::Module::Build is loaded from elsewhere). Moreover, "use # base" is not yet belong to us at this time. sub fake_makefile { my $self = shift; return $self->SUPER::fake_makefile(@_). <<'MAIN_SCREEN_TURN_ON'; # In 2101 AD war was beginning... your: @echo @echo -n " All your codebase" time: @echo " are belong to us !" @echo MAIN_SCREEN_TURN_ON } } =head2 Overloaded Internal Methods Yeah I know, that's a pretty stupid thing to do, but that's the best I could find to get Module::Build to do my bidding. =over =item I Overloaded from L to set @ISA at compile time and to the correct value in the sub-classes generated from the C<< code >> named argument. We need @ISA to be set up at compile-time so that the method attributes work correctly; also we work around a bug present in Module::Build 0.26 and already fixed in the development branch whence, ironically, ->subclass does not work from a subclass. =cut sub subclass { my ($pack, %opts) = @_; $opts{code} = <<"KLUDGE_ME_UP" if defined $opts{code}; # Kludge inserted by My::Module::Build to work around some brokenness # in the \@ISA setup code above: use base "My::Module::Build"; our \@ISA; BEGIN { our \@ISAorig = \@ISA; } \@ISA = our \@ISAorig; $opts{code} KLUDGE_ME_UP return $pack->SUPER::subclass(%opts); } =item I<_startperl> Overloaded from parent to attempt a chdir() into the right place in ./Build during initialization. This is an essential enabler to the Emacs debugger support (see L) because we simply cannot tell where Emacs will be running us from. =cut sub _startperl { my $self = shift; my $basedir = $self->base_dir; $basedir = Win32::GetShortPathName($basedir) if is_win32; return $self->SUPER::_startperl(@_) . <<"MORE"; # Hack by My::Module::Build to give the Emacs debugger one # more chance to work: use Cwd; BEGIN { \$My::Module::Build::initial_cwd = \$My::Module::Build::initial_cwd = Cwd::cwd; chdir("$basedir") || 1; } MORE } =item I<_packages_inside($file)> Returns a list of Perl packages to be found inside $file. Overloaded from the parent class so as to refrain from parsing after the __END__ marker. =cut sub _packages_inside { # Copied 'n modified from the parent class, doubleplusshame on me! my ($self, $file) = @_; my $fh = IO::File->new($file) or die "Can't read $file: $!"; my @packages; while(my (undef, $p) = $self->_next_code_line ($fh, qr/^(?:__END__$|__DATA__$|[\s\{;]*package\s+([\w:]+))/)) { last if ! defined $p; push @packages, $p; } return @packages; } =back =head2 Other Private Methods =over =item I<_massage_ARGV($ref_to_ARGV)> Called as part of this module's startup code, in order to debogosify the @ARGV array (to be passed as a reference) when we are invoked from Emacs' M-x perldb. L will afterwards be able to take advantage of the Emacs debugger we run under, by bogosifying the command line back before invoking the script to test. =cut _massage_ARGV(\@ARGV); sub _massage_ARGV { my ($argvref) = @_; my @argv = @$argvref; return unless ($ENV{EMACS} && (grep {$_ eq "-emacs"} @argv)); $running_under_emacs_debugger = 1; @argv = grep { $_ ne "-emacs" } @argv; shift @argv if $argv[0] eq "-d"; # Was gratuitously added by former Emacsen # XEmacs foolishly assumes that the second word in the perldb # line is a filename and turns it into e.g. "/my/path/test": (undef, undef, $argv[0]) = File::Spec->splitpath($argv[0]); @$argvref = @argv; } =back =head2 My::Module::Build::PmFilter Ancillary Class This ancillary class, serving both as an object-oriented interface and as a default implementation thereof, is the workhorse behind L and L. It consists of a very simple filter API to transform the text of .pm files as they are copied over from lib/ to blib/ during the build process. The base-class implementation simply replaces copyright placeholders of the form "(C) DOMQ" with appropriate legalese, and removes the L test suite if one is found. Subclasses of I need only overload L in order to provide a different implementation of this .pm filter. The object returned by said overloaded I needs only obey the API documented below for methods I and I; it may or may not elicit to inherit from I in order to do so. =over =cut package My::Module::Build::PmFilter; =item I Object constructor. Does nothing in the base class. =cut sub new { bless {}, shift } =item I Given $line, a line read from a .pm file in lib, returns a piece of text that L should replace this line with. Note that it is perfectly appropriate for a filter implementation to buffer stuff, and therefore not always return something from I. The base class does not buffer. Rather, it substitutes standard copyright stanzas, and detects the end-of-file on behalf of L. =cut sub filter { my $self = shift; local $_ = shift; return "" if $self->eof_reached; my $copyrightstring = sprintf( "Copyright Dominique Quatravaux %d -". " Licensed under the same terms as Perl itself", (localtime(time))[5] + 1900 ); s/^ (.*) # Leading cruft (e.g. comment markers) (?:\(C\)|\x{A9}) # "copyright" sign (?:[ -]) .* # spacer (?i:DOMQ|Quatravaux) # Yours truly (case insensitive) /$1$copyrightstring/x; if (m/^require My::Tests::Below unless caller/) { $self->eof_reached(1); return "1;\n"; } else { return $_; } } =item I Shall return true iff the end-of-file is reached and calling L further would just be a waste of time. Called exactly once by L after each call to I. In the base class, I is just a passive accessor whose value is set by L. =cut sub eof_reached { my $self = shift; if (@_) { $self->{eof} = shift; } else { return $self->{eof}; } } =back =end internals =head1 BUGS The zero-wing easter egg only works through the Makefile.PL compatibility mode. On the other hand, "./Build your time" would not sound quite right, would it? Perhaps the L features should be repackaged as a standalone Module::Build plug-in. =head1 SEE ALSO L t/maintainer/*.t =cut require My::Tests::Below unless caller; 1; __END__ use Test::More "no_plan"; ########### Dependent graph stuff ################ # We keep the tests in a separate package so that if we later decide # to refactor the dependent graph stuff into a standalone # Module::Build plug-in, a simple cut-n-paste operation will do the # job. do { # We re-route the process of creating a Module::Build object to # a fake package, so as not to make Module::Build itself part # of the tests over the dependent graph stuff: local @My::Module::Build::ISA=qw(Fake::Module::Build); package Fake::Module::Build; sub new { bless {}, shift } # Various stuff that is being called by My::Module::Build as part # of this test, and that we therefore need to stub out: no warnings "redefine"; local *My::Module::Build::maintainer_mode_enabled = sub { 0 }; local *My::Module::Build::subclass = sub { my ($self, %opts) = @_; eval <<'HEADER' . $opts{code}; die $@ if $@; package Fake::Subclass; BEGIN { our @ISA=qw(My::Module::Build); } HEADER return "Fake::Subclass"; }; sub notes { my ($self, $k, @v) = @_; if (@v) { $self->{notes}->{$k} = $v[0]; } return $self->{notes}->{$k}; } # "batch" version of ->prompt() our %answers = ("Install module foo?" => 1); sub prompt { my ($self, $question) = @_; die "Unexpected question $question" if (! exists $answers{$question}); return delete $answers{$question}; # Will not answer twice # the same question } package main_screen; # Do not to pollute the namespace of "main" with # the "use" directives below - Still keeping refactoring in mind. BEGIN { *write_file = \&My::Module::Build::write_file; *read_file = \&My::Module::Build::read_file; } use Test::More; use Fatal qw(mkdir chdir); local @ARGV = qw(--noinstall-everything); my $define_options = My::Tests::Below->pod_code_snippet("option-graph"); $define_options =~ s/\.\.\.//g; my $builder = eval $define_options; die $@ if $@; isa_ok($builder, "Fake::Module::Build", "construction of builder successful"); is(scalar keys %My::Module::Build::declared_options, 2, "Number of declarations seen"); is(scalar(keys %answers), 0, "All questions have been asked"); ok(! $builder->notes("option:install_everything"), "note install_everything"); ok($builder->notes("option:install_module_foo"), "note install_module_foo"); ok(! $builder->option_value("install_everything"), "install_everything"); ok($builder->option_value("install_module_foo"), "install_module_foo"); # Some whitebox testing here: is($builder->_option_type("install_everything"), "boolean", "implicit typing"); is($builder->_option_type("install_module_foo"), "boolean", "explicit typing"); }; # End of fixture for option graph tests ####################### Main test suite ########################### use File::Copy qw(copy); use File::Spec::Functions qw(catfile catdir); use IO::Pipe; BEGIN { *write_file = \&My::Module::Build::write_file; *read_file = \&My::Module::Build::read_file; } # Probably wise to add this in real test suites too: use Fatal qw(mkdir chdir copy); mkdir(my $fakemoduledir = My::Tests::Below->tempdir() . "/Fake-Module"); my $sample_Build_PL = My::Tests::Below->pod_code_snippet("synopsis"); $sample_Build_PL =~ s/^(.*Acme::Pony.*)$/#$1/m; # As we say in french, # faut pas _que_ deconner non plus. my $ordinary_arguments = <<'ORDINARY_ARGUMENTS'; module_name => 'Fake::Module', license => 'perl', dist_author => 'Octave Hergebelle ', dist_version_from => 'lib/Fake/Module.pm', dist_abstract => 'required for Module::Build 0.2805, sheesh', requires => { 'Module::Build' => 0, }, create_makefile_pl => 'passthrough', ORDINARY_ARGUMENTS ok($sample_Build_PL =~ s/^(.*##.*ordinary.*arguments.*)$/$ordinary_arguments/m, "substitution 1 in synopsis"); my $remainder = <<'REMAINDER'; $builder->create_build_script(); 1; REMAINDER ok($sample_Build_PL =~ s/^(.*##.*remainder.*)$/$remainder/m, "Substitution 2 in synopsis"); write_file("$fakemoduledir/Build.PL", $sample_Build_PL); mkdir("$fakemoduledir/lib"); mkdir("$fakemoduledir/lib/Fake"); =begin this_pod_is_not_mine =cut my $fakemodule = <<'FAKE_MODULE'; #!perl -w # (C) DOMQ use strict; package Fake::Module; our $VERSION = '0.42'; =head1 NAME Fake::Module - This module is for testing My::Module::Build.pm =head1 SYNOPSIS Hey, gimme a break, this is a *bogus* package for Pete's sake! =sorry, you're right =cut the schizoid POD freakiness now will you? This is not M-x doctor! # Good. package Fake::Module::Ancillary::Class; 1; __END__ package This::Package::Should::Not::Be::Reported::In::METAyml; FAKE_MODULE =end this_pod_is_not_mine =cut write_file("$fakemoduledir/lib/Fake/Module.pm", $fakemodule); mkdir("$fakemoduledir/$_") foreach (qw(inc inc/My inc/My/Module lib/My lib/My/Private lib/My/Private/Stuff)); use FindBin qw($Bin $Script); copy(catfile($Bin, $Script), "$fakemoduledir/inc/My/Module/Build.pm"); write_file(catfile($fakemoduledir, qw(lib My Private Stuff Indeed.pm)), <<"BOGON"); #!perl -w package My::Private::Stuff::Indeed; use strict; 1; BOGON my ($perl) = ($^X =~ m/^(.*)$/); # Untainted chdir($fakemoduledir); my $pipe = new IO::Pipe(); $pipe->reader($perl, "$fakemoduledir/Build.PL"); my $log = join('', <$pipe>); $pipe->close(); is($?, 0, "Running Build.PL"); like($log, qr/version.*0.42/, "Build.PL found the version string"); SKIP: { skip "Not testing Build distmeta (YAML not available)", 2 unless eval { require YAML }; my $snippet = My::Tests::Below->pod_data_snippet("distmeta"); my $errfile = "$fakemoduledir/meta-yml-error.log"; my $script = <<"SCRIPT"; exec > "$errfile" 2>&1 set -x cd "$fakemoduledir" $snippet SCRIPT system($script); is($?, 0, "creating META.yml using documented procedure") or diag($script . read_file($errfile)); my $META_yml = read_file("$fakemoduledir/META.yml"); my $excerpt = My::Tests::Below->pod_data_snippet("META.yml excerpt"); $excerpt =~ s/\n+/\n/gs; $excerpt =~ s/^\n//s; like($META_yml, qr/\Q$excerpt\E/, "META.yml contains provisions against indexing My::* modules"); like($META_yml, qr|My\b.*\bPrivate\b.*\bStuff|, "these provisions can be customized"); like($META_yml, qr/\bFake::Module\b/, "Fake::Module is indexed"); like($META_yml, qr/\bFake::Module::Ancillary::Class\b/, "Fake::Module::Ancillary::Class is indexed"); unlike($META_yml, qr/This::Package::Should::Not::Be::Reported/, "META.yml should not index stuff that is after __END__"); unlike($META_yml, qr/Indeed/, "META.yml should not index stuff that is in add_to_no_index"); } # You have no chance to survive... test_Makefile_PL_your_time($_) for ($sample_Build_PL, <<'SUBCLASSED_BUILD_PL'); use strict; use warnings; use FindBin; use lib "$FindBin::Bin/inc"; use My::Module::Build; my $subclass = My::Module::Build->subclass(code => ""); my $builder = $subclass->new( module_name => 'Fake::Module', license => 'perl', dist_author => 'Octave Hergebelle ', dist_version_from => 'lib/Fake/Module.pm', dist_abstract => 'required for Module::Build 0.2805, sheesh', requires => { 'Module::Build' => 0, }, create_makefile_pl => 'passthrough', build_requires => { # 'Acme::Pony' => 0, My::Module::Build->requires_for_build(), }, ); $builder->create_build_script(); 1; SUBCLASSED_BUILD_PL sub test_Makefile_PL_your_time { my ($Build_PL_contents) = @_; write_file("$fakemoduledir/Build.PL", $Build_PL_contents); system($perl, "$fakemoduledir/Build.PL"); is($?, 0, "Running Build.PL"); system("$fakemoduledir/Build", "dist"); is($?, 0, "Running Build dist"); unlink("$fakemoduledir/Fake-Module-0.42.tar.gz"); write_file("$fakemoduledir/test.sh", <<"PREAMBLE", set -e cd $fakemoduledir PREAMBLE My::Tests::Below->pod_data_snippet("great justice")); $pipe = new IO::Pipe; $pipe->reader("/bin/sh", "$fakemoduledir/test.sh"); my $text = join('', <$pipe>); $pipe->close(); is($?, 0, "You are on the way to destruction") or warn $text; like($text, qr/belong/, "Still first hit on Google, after all these years!"); }