Cyclic dependencies in Enswitch Perl backend
Introduction
Given that cyclic dependencies between modules may bring runtime errors, the following is recommended:
- Identify where they happen.
- Refactor the source code to break them.
The details in this page are related with the first point, specifically with the Perl based Enswitch backend.
Automatically finding them
- Use a machine where a copy of the Enswitch source code exists and take note of its directory path, for example:
- /opt/enswitch
- /opt/enswitch/4.3
- /home/user/copy_of_enswitch_4.2
- Install the packages that will be needed. For Debian or Ubuntu, run these commands:
cpanm App::PrereqGrapher apt-get install graphviz apt-get install python3-networkx python3-graphviz python3-pydot python3-pydotplus python-is-python3
- Save the following contents to a file (e.g /tmp/enswitch-cyclic-dependencies-finder.pl), which analyses the files inside the bin and lib subdirectories of Enswitch, assuming by default that they are under the /opt/enswitch directory.
use App::PrereqGrapher;
use Getopt::Long;
use Module::Metadata;
use strict;
use warnings;
my $source_dir = '/opt/enswitch';
my $out_dir;
my $depth = 2;
GetOptions( 'source-dir=s' => \$source_dir, 'out-dir=s' => \$out_dir, 'depth:i' => \$depth, );
$source_dir =~ s|/$||;
$out_dir or die "Output directory is required.\n";
$out_dir =~ s|/$||;
for my $d ( 'bin', 'lib' ) {
opendir( DIR, $source_dir . "/" . $d ) or die "Could not open $d\n";
while ( my $f = readdir( DIR ) ) {
my $info = Module::Metadata->new_from_file( "$source_dir/$d/$f" );
if ( $info->{ 'module' } && $info->{ 'module' } ne 'main' ) {
print "$source_dir/$d/$f => " . $info->{ 'module' } . "\n";
generate_dot_file( $info->{ 'module' } );
transform_dot_file( $info->{ 'module' }, 'pdf' );
transform_dot_file( $info->{ 'module' }, 'svg' );
find_dependencies_cycles( $info->{ 'module' } );
}
}
filter_bidirectional_dependencies_cycles();
}
sub generate_dot_file {
my $module = shift;
my %options = (
format => 'dot',
no_core => 1,
no_recurse_core => 1,
depth => $depth,
output_file => $out_dir . "/" . $module =~ s/::/_/gr . ".dot",
verbose => 0,
);
my $grapher = App::PrereqGrapher->new( %options );
$grapher->generate_graph( $module );
}
sub transform_dot_file {
my $module = shift;
my $format = shift;
my $dot_file = $out_dir . "/" . $module =~ s/::/_/gr . ".dot";
print "Converting $dot_file to $format.\n";
system( "dot -T$format -O $dot_file" );
}
sub find_dependencies_cycles {
my $module = shift;
my $finder_path = '/tmp/dot_find_cycles.py';
if ( ! -f $finder_path ) {
system( "wget --output-document=$finder_path https://github.com/jantman/misc-scripts/raw/master/dot_find_cycles.py" );
system( "chmod +x $finder_path" );
}
my $dot_file = $out_dir . "/" . $module =~ s/::/_/gr . ".dot";
my $txt_file = $out_dir . "/" . $module =~ s/::/_/gr . ".cycles.txt";
print "Finding dependencies cycles in $dot_file.\n";
system( "$finder_path $dot_file > $txt_file" );
}
sub filter_bidirectional_dependencies_cycles {
my $txts = $out_dir . "/" . "*.cycles.txt";
my $filtered_file = $out_dir . "/" . "bidirectional.cycles.txt";
print "Filtering bi-directional dependencies cycles in file patterns $txts.\n";
system( "cat $txts | sort | uniq | " . 'egrep "^(.+) -> ([^->]+) -> \1$"' . " > $filtered_file" );
}
- Create a directory to store the results, for example /tmp/enswitch-cyclic-dependencies-results.
- Run the previously saved script like this:
perl /tmp/enswitch-cyclic-dependencies-finder.pl --out-dir=/tmp/enswitch-cyclic-dependencies-results --source-dir=/home/myuser/enswitch_4.4_copy
- The results include these:
- DOT files for the found Enswitch packages.
- PDF and SVG files for visual representations of those DOT files.
- Text files listing the found cyclic dependencies.
- A text file named bidirectional.cycles.txt that lists the cyclic dependencies more likely to raise runtime errors and that are recommended to be refactored.
References
- https://blogs.perl.org/users/neilb/2012/12/prereq-grapher.html
- https://metacpan.org/pod/App::PrereqGrapher
- https://coderwall.com/p/5ri4dw/resolve-circular-dependencies-with-carp-always
- https://metacpan.org/pod/circular::require
- https://blog.jasonantman.com/2012/03/python-script-to-find-dependency-cycles-in-graphviz-dot-files/
- https://github.com/jantman/misc-scripts/blob/master/dot_find_cycles.py