Hi! Please consider following me on twitter: @hanekomu.

HTML stack trace from the Perl debugger

Tatsuhiko Miyagawa released Devel::StackTrace::AsHTML and blogged about it.

I thought this would make a neat Perl debugger command, so I wrote DB::Pluggable::StackTraceAsHTML. It is a plugin to DB::Pluggable. It adds the Th command to the debugger, which displays a stack trace in HTML format, with lexical variables. It then opens the page in the default browser.

Here is an example of how to use it:

$ perl -d test.pl

Loading DB routines from perl5db.pl version 1.3
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:14): my $n = 12;
  DB<1> r                                                                                                   main::fib(test.pl:12):      return fib($i - 1) + fib($i - 2);
  DB<1> Th                                                                                                  

The result would look something like:

To enable the plugin, just add it to your ~/.perldb, like so:

use DB::Pluggable;
use YAML;

$DB::PluginHandler = DB::Pluggable->new(config => Load <<EOYAML);
global:
  log:
    level: error

plugins:
  - module: BreakOnTestNumber
  - module: StackTraceAsHTML
EOYAML

$DB::PluginHandler->run;

By the way, to be minimally invasive to the existing Perl debugger, the command is defined using the debugger's aliasing mechanism. Normally you define an alias as a regular expression that will change the command the user enters to a known command, but here we circumvent that and call our command handler directly. The following method is from DB::Pluggable::Plugin:

sub make_command {
    my ($self, $cmd_name, $code) = @_;
    no strict 'refs';
    my $sub_name = "DB::cmd_$cmd_name";
    *{$sub_name} = $code;
    $DB::alias{$cmd_name} = "/./; &$sub_name;";
}

To define a new foo command in a plugin, you then use:

package DB::Pluggable::StackTraceAsHTML;
use strict;
use warnings;
use base qw(DB::Pluggable::Plugin);

sub register {
    my ($self, $context) = @_;
    $self->make_command(
        foo => sub {
            # ...
        }
    );
}

Write a comment | Bookmark and Share

posted at: 22:09 | path: /dev | permalink | 0 comments | 0 trackbacks

Repeatedly installing Task::* distributions

First there were Bundle::* distributions to install several dependencies at once without the actual bundle distribution doing anything, but this required magic in the toolchain. A newer concept is that of a Task::* distribution, as described in Task.

Task modules are just normal Perl modules; they don't require toolchain magic. When the dependencies listed in Makefile.PL have been installed, the actual Task module, for example Task::Catalyst, will be installed as well.

This is a problem because if the task's dependencies are being updated, the task module needs to be updated as well. For example, DBD::SQLite is a dependency of Task::Catalyst. If there is a new version of DBD::SQLite, just running install Task::Catalyst in the CPAN shell won't have any effect, because the CPAN shell will see that the latest version of Task::Catalyst is already installed. Of course you can force with force install Task::Catalyst, but even if that would work, it would be ugly because I don't want to install dependencies that fail their tests.

One solution is to prevent the task module itself to be installed. That way, every time you rerun install Task::Foo in the CPAN shell, it will see that the task module isn't installed and try to install it again, with all the desired dependency checking.

How do we prevent the module from being installed? Put these lines in your Makefile.PL — I always use Module::Install, but I think this should work for all MakeMaker-based files:

exit 0 if $ENV{AUTOMATED_TESTING};
sub MY::install { "install ::\n" }

First, we don't need to waste the time of CPAN testers by having them install all the dependencies for a task module that doesn't really do anything anyway. Second, and more importantly, we override the install :: target that is generated for the Makefile to effectively be a no-op.

I have demonstrated this technique in Task::BeLike::hanekomu, a somewhat pointless distribution that serves mainly as proof-of-concept, and as a way to install my favourite modules onto a new perl installation. If you don't mind the dependencies, then go ahead and repeatedly run install Task::BeLike::hanekomu in the CPAN shell; the task module itself should never be installed and it should recheck the dependencies every time.

I think this would be a good thing to put in existing task distributions such as Task::Kensho and Task::Catalyst.

Write a comment | Bookmark and Share

posted at: 12:27 | path: /dev | permalink | 6 comments | 1 trackback