package circular::require; use strict; use warnings; # ABSTRACT: detect circularity in use/require statements use 5.010; use Devel::OverrideGlobalRequire; use Module::Runtime 'module_notional_filename'; =head1 SYNOPSIS package Foo; use Bar; package Bar; use Foo; package main; no circular::require; use Foo; # warns or perl -M-circular::require foo.pl =head1 DESCRIPTION Perl by default just ignores cycles in require statements - if Foo.pm does C and Bar.pm does C, doing C elsewhere will start loading Foo.pm, then hit the C statement, start loading Bar.pm, hit the C statement, notice that Foo.pm has already started loading and ignore it, and continue loading Bar.pm. But Foo.pm hasn't finished loading yet, so if Bar.pm uses anything from Foo.pm (which it likely does, if it's loading it), those won't be accessible while the body of Bar.pm is being executed. This can lead to some very confusing errors, especially if introspection is happening at load time (C in L classes, for example). This module generates a warning whenever a module is skipped due to being loaded, if that module has not finished executing. This module works as a pragma, and typically pragmas have lexical scope. Lexical scope doesn't make a whole lot of sense for this case though, because the effect it's tracking isn't lexical (what does it mean to disable the pragma inside of a cycle vs. outside of a cycle? does disabling it within a cycle cause it to always be disabled for that cycle, or only if it's disabled at the point where the warning would otherwise be generated? etc.), but dynamic scope (the scope that, for instance, C uses) does, and that's how this module works. Saying C enables the module for the current dynamic scope, and C disables it for the current dynamic scope. Hopefully, this will just do what you mean. In some situations, other modules might be handling the module loading for you - C and C, for instance. To avoid these modules showing up as the source of cycles, you can use the C<-hide> parameter when using this module. For example: no circular::require -hide => [qw(base parent Class::Load)]; or perl -M'-circular::require -hide => [qw(base parent Class::Load)];' foo.pl =cut our %loaded_from; our $previous_file; my @hide; Devel::OverrideGlobalRequire::override_global_require { my ($require, $file) = @_; if (exists $loaded_from{$file}) { my @cycle = ($file); my $caller = $previous_file; while (defined($caller)) { unshift @cycle, $caller unless grep { /^$caller$/ } @hide; last if $caller eq $file; $caller = $loaded_from{$caller}; } if (_find_enable_state()) { if (@cycle > 1) { warn "Circular require detected:\n " . join("\n ", @cycle) . "\n"; } else { warn "Circular require detected in $file (from unknown file)\n"; } } } local $loaded_from{$file} = $previous_file; local $previous_file = $file; $require->(); }; sub import { # not delete, because we want to see it being explicitly disabled $^H{'circular::require'} = 0; } sub unimport { my $class = shift; my %params = @_; @hide = ref($params{'-hide'}) ? @{ $params{'-hide'} } : ($params{'-hide'}) if exists $params{'-hide'}; @hide = map { /\.pm$/ ? $_ : module_notional_filename($_) } @hide; $^H{'circular::require'} = 1; } sub _find_enable_state { my $depth = 0; while (defined(scalar(caller(++$depth)))) { my $hh = (caller($depth))[10]; next unless defined $hh; next unless exists $hh->{'circular::require'}; return $hh->{'circular::require'}; } return 0; } =head1 CAVEATS This module works by overriding C, and so other modules which do this may cause issues if they aren't written properly. See L for more information. =head1 BUGS No known bugs. Please report any bugs to GitHub Issues at L. =head1 SUPPORT You can find this documentation for this module with the perldoc command. perldoc circular::require You can also look for information at: =over 4 =item * MetaCPAN L =item * RT: CPAN's request tracker L =item * Github L =item * CPAN Ratings L =back =begin Pod::Coverage unimport =end Pod::Coverage =cut 1;