--
Linuxhotel GmbH, Geschäftsführer Dipl.-Ing. Ingo Wichmann
HRB 20463 Amtsgericht Essen, UStID DE 814 943 641
Antonienallee 1, 45279 Essen, Tel.: 0201 8536-600, http://www.linuxhotel.de
#!/usr/bin/perl
=head1 NAME
fai-deps - class dependencies for FAI
=head1 SYNOPSIS
fai-deps [-h] [--man] [-d]
=head1 ABSTRACT
implements dependencies between fai classes.
=head1 DESCRIPTION
fai-deps uses files $FAI/class/*.deps to sort the classes in
$LOGDIR/FAI_CLASSES and define additional ones. While doing so, it retains the
original order as much as possible.
*.deps files may contain class names, seperated by whitespace. Comments ( after
# or ; ) are ignored
e.g. you have a class WORDPRESS that depends on the classes VHOST and POSTGRES
. VHOST again may depend on WEBSERVER.
So if you want to install the blogging software wordpress, you add a file
$FAI/class/WORDPRESS.deps
that contains the words
VHOST
POSTGRES
and a file
$FAI/class/VHOST.deps
that contains the word
WEBSERVER
The order often is important, so this script is taking care of it. The order of
the example above would be:
WEBSERVER VHOST POSTGRES WORDPRESS
That way, in $FAI/scripts/ first the webserver would be configured, then the
vhosts, ...
It removes double entries from FAI_CLASSES and handles circular dependencies[1].
I do not recommend using circular dependencies, but if you accidentally define
them, they will not break your neck. But you'll get a warning ...
=head1 ENVIROMENT
One non-standard perl module is required:
Graph::Directed;
On debian install libgraph-perl
The following enviroment variables are used:
$LOGDIR : path to fai temporary files
$FAI : path to fai config space
=cut
BEGIN
{
unless ( $ENV{FAI} and $ENV{LOGDIR} )
{
print STDERR '$ENV{FAI} and $ENV{LOGDIR} are not defined', $/;
print STDERR 'This script should be called from within fai', $/;
exit 1;
}
}
use strict;
use warnings;
use lib "$ENV{FAI}/lib";
use Getopt::Long;
use Pod::Usage;
use Graph::Directed;
#use Text::Glob qw(match_glob);
#use Data::Dumper;
#use GraphViz;
my %opts;
GetOptions( \%opts, 'help|h', 'man', 'debug|d' ) or pod2usage(1);
pod2usage(1) if $opts{help};
pod2usage(-verbose => 2) if $opts{man};
my $debug = $opts{debug};
my $fai_classes_file = "$ENV{LOGDIR}/FAI_CLASSES";
my $class_dir = "$ENV{FAI}/class";
# main
{
# read classes and dependencies into $digraph
# retain order of first appearance in @uniq_classes
my $digraph = Graph::Directed->new;
my ( @uniq_classes ) =
read_fai_classes( $digraph, $fai_classes_file );
push @uniq_classes,
read_dependencies( $digraph, $class_dir, @uniq_classes );
exit if not $digraph->has_edges;
# debug output
if ( $debug ) {
print STDERR 'graph:', $/;
print STDERR $digraph->stringify(), $/;
print STDERR 'is strongly connected', $/
if $digraph->is_strongly_connected;
# create_graphviz_output($digraph->edges);
print STDERR 'unique list of classes, orderd by appearence', $/;
print STDERR join('-', @uniq_classes), $/;
print STDERR $/;
}
# warn if graph has cycles
if ( $digraph->has_a_cycle ) {
print STDERR 'Warning: cyclic class dependencies found:', $/;
my $copy = $digraph->copy;
while ( my @cycle = $copy->find_a_cycle ) {
print STDERR join('-', @cycle), $/;
$copy->delete_cycle(@cycle);
}
print STDERR 'I`ll try my best to retain your class order', $/;
}
# sort classes: retain order where possible, respect dependencies where
necessary
my @sorted_classes = sort_classes( $digraph, @uniq_classes );
# debug output
if ( $debug ) {
print STDERR "list of all classes after resolving
dependencies:", $/;
print STDERR "@sorted_classes", $/;
print STDERR 'in debug mode, this script has no effect at
all!', $/x5;
print STDERR 'Goodbye, and thank you for the fish', $/;
exit;
}
# rewrite $fai_classes_file
open FAI_CLASSES, ">$fai_classes_file"
or die "$!: $fai_classes_file";
print FAI_CLASSES join($/, @sorted_classes), $/;
close FAI_CLASSES;
}
exit; # end main
# sort_classes:
# topological sort classes, retaining order as much as possible
my %class_finished_for;
my @order;
sub sort_classes {
my ( $digraph, @uniq_classes ) = @_;
@order = @uniq_classes if not @order;
my @sorted_classes;
for my $class ( @uniq_classes ) {
next if exists $class_finished_for{$class};
my %unfinished_successor_for =
map { $_, 1 }
grep { not exists $class_finished_for{$_} }
successors($digraph, $class);
# retain order for successors
my @unfinished_successors =
grep { $unfinished_successor_for{$_} }
@order;
push @sorted_classes, sort_classes( $digraph,
@unfinished_successors );
push @sorted_classes, $class;
$class_finished_for{$class}++;
}
return @sorted_classes;
}
# successors: find successors for a given class
# handle circular dependencies:
# * do not return circular connected successors
# * _do_ return all successors of circular connected successors
sub successors {
my ( $digraph, $class ) = @_;
my $component =
$digraph->strongly_connected_component_by_vertex($class);
# strongly connected components to all successors, except own component
my %successor_components =
map { $_, undef } # turn list into hash for uniqueness
grep { $_ ne $component }
map { $digraph->strongly_connected_component_by_vertex($_) }
$digraph->successors($class);
# classes for these components
my %successors =
map { $_, undef } # turn list into hash for uniqueness
map { $digraph->strongly_connected_component_by_index($_) }
keys %successor_components;
return keys %successors;
}
# read_fai_classes: reads fai classes from $fai_classes_file
# usually $LOGDIR/FAI_CLASSES
sub read_fai_classes {
my ( $digraph, $fai_classes_file) = @_;
my @uniq_classes;
# read plain classes from $LOGDIR/FAI_CLASSES
open FAI_CLASSES, $fai_classes_file
or die "$!: $fai_classes_file";
while ( <FAI_CLASSES> ) {
chomp;
# skip double classes
next if $digraph->has_vertex( $_ );
push @uniq_classes, $_;
$digraph->add_vertex( $_ );
}
close FAI_CLASSES;
return @uniq_classes;
}
# read_dependencies: reads dependencies and its classes from $class_dir/*.deps
my %deps_file_seen_for;
sub read_dependencies {
my ( $digraph, $class_dir, @uniq_classes) = @_;
my @new_classes;
# read class dependencies from $class_dir/*.deps
my $prefix = quotemeta($class_dir);
my @deps_files = grep {
-f "$class_dir/$_.deps"
and not -x "$class_dir/$_.deps"
} @uniq_classes;
for my $class ( @deps_files ) {
next if $deps_file_seen_for{$class}++;
open DEPSFILE, "$class_dir/$class.deps"
or die "$!: $class";
while ( <DEPSFILE> ) {
chomp;
# remove comments, leading and trailing whitespace
s/(#|;).*// ;
s/ ^\s+ //x;
s/ \s+$ //x;
# allow multiple classes per line
my @deps = split m/\s+/;
for my $dep ( @deps ) {
push @new_classes, $dep
if not $digraph->has_vertex( $dep );
$digraph->add_edge($class, $dep);
}
}
close DEPSFILE;
push @new_classes, read_dependencies( $digraph, $class_dir,
@new_classes );
}
return @new_classes;
}
#sub create_graphviz_output {
# my @edges = @_;
# my $g = GraphViz->new();
# for ( @edges ) {
# $g->add_edge( @$_ );
# }
# return $g->as_png('graph-test.png');
#}
=head1 SEE ALSO
http://www.informatik.uni-koeln.de/fai/
=head1 AUTHOR
Copyright 2008 by Ingo Wichmann <[EMAIL PROTECTED]>
This software ist free software; you can redistribute it and/or modify
it unter the same terms as Perl itself.
=cut