之所以需要骇客,是因为require
(因此use
)在返回之前都编译并执行了模块。
同样适用eval
。eval
在不执行代码的情况下不能用于编译代码。
我发现的最不干扰的解决方案是重写DB::postponed
。在评估已编译的所需文件之前将调用此方法。不幸的是,它仅在调试(perl -d
)时调用。
另一个解决方案是读取文件,对其进行修改并评估修改后的文件,类似于以下内容:
use File::Slurper qw( read_binary );
eval(read_binary("Foo.pm") . <<'__EOS__') or die $@;
package Foo {
no warnings qw( redefine );
sub bar { 7 }
}
__EOS__
上面的设置不正确%INC
,它弄乱了警告使用的文件名,因此不调用DB::postponed
,等等。下面是一个更可靠的解决方案:
use IO::Unread qw( unread );
use Path::Class qw( dir );
BEGIN {
my $preamble = '
UNITCHECK {
no warnings qw( redefine );
*Foo::bar = sub { 7 };
}
';
my @libs = @INC;
unshift @INC, sub {
my (undef, $fn) = @_;
return undef if $_[1] ne 'Foo.pm';
for my $qfn (map dir($_)->file($fn), @libs) {
open(my $fh, '<', $qfn)
or do {
next if $!{ENOENT};
die $!;
};
unread $fh, "$preamble\n#line 1 $qfn\n";
return $fh;
}
return undef;
};
}
use Foo;
我UNITCHECK
之所以使用它(是在编译后但在执行之前调用的),是因为我在重写之前(使用unread
)而不是在其中读取整个文件并追加新定义。如果要使用该方法,则可以使用返回文件句柄
open(my $fh_for_perl, '<', \$modified_code);
return $fh_for_perl;
@Grinnz赞扬@INC
钩子。
Foo::bar
,但是use Foo
它将在编译阶段(如果先前在此定义过,则重新定义bar)和Foo的运行时阶段一起运行。我唯一能想到的就是@INC
修改Foo的加载方式的钩子。