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