#!/usr/bin/perl use strict; use warnings FATAL => qw(all); use LWP::Simple; use make::configure; our @modlist; my %installed; # $installed{name} = $version my %modules; # $modules{$name}{$version} = { # url => URL of this version (or INSECURE/DEPRECATED) # depends => [ 'm_foo 1.2.0-1.3.0', ... ] # conflicts => [ ] # from => URL of source document # } my %url_seen; sub parse_url; sub parse_url { my $src = shift; return if $url_seen{$src}; $url_seen{$src}++; my $doc = get($src); die "Could not retrieve $_" unless defined $doc; my $mod; for (split /\n+/, $doc) { s/^\s+//; # ignore whitespace at start if (/^module (\S+) ([0-9.]+) (\S+)/) { my($name, $ver, $url) = ($1,$2,$3); if ($modules{$name}{$ver}) { my $origsrc = $modules{$name}{$ver}{from}; warn "Overriding module $name $ver defined from $origsrc with one from $src"; } $mod = { from => $src, url => $url, depends => [], conflicts => [], }; $modules{$name}{$ver} = $mod; } elsif (/^depends (.*)/) { push @{$mod->{depends}}, $1; } elsif (/^conflicts (.*)/) { push @{$mod->{conflicts}}, $1; } elsif (m#^source (http://\S+)#) { parse_url $1; } else { print "Unknown line in $src: $_\n"; } } } open SRC, 'sources.list' or die "Could not open sources.list: $!"; while () { parse_url($_); } close SRC; getmodules(); open my $verfile, 'src/version.sh' or die "Cannot determine inspircd version: $!"; $_ = join '', <$verfile>; die "Cannot determine inspircd version" unless /InspIRCd-([0-9.]+)/; $installed{core} = $1; $modules{core}{$1} = { url => 'NONE', depends => [], conflicts => [], from => 'local file', }; close $verfile; for my $modname (@modlist) { my $mod = "m_$modname"; my $modfile = "src/modules/$mod.cpp"; my $ver = getmodversion($modfile) || '0.0'; $installed{$mod} = $ver; next if $modules{$mod}{$ver}; $modules{$mod}{$ver} = { url => 'NONE', depends => [], conflicts => [], from => 'local file', }; } my %todo = %installed; sub ver_cmp { ($a,$b) = @_ if @_; my @a = split /\./, $a; my @b = split /\./, $b; push @a, 0 while $#a < $#b; push @b, 0 while $#b < $#a; for my $i (0..$#a) { my $d = $a[$i] <=> $b[$i]; return $d if $d; } return 0; } sub ver_in_range { my($ver, $range) = @_; return 1 unless defined $range; if ($range =~ /(.*)-(.*)/) { my($l,$h) = ($1,$2); return 0 unless ver_cmp($ver, $l) >= 0; return 0 unless ver_cmp($ver, $h) <= 0; return 1; } return !ver_cmp($ver, $range); } sub find_mod_in_range { my($mod,$vers) = @_; my @versions = keys %{$modules{$mod}}; @versions = sort { ver_cmp() } @versions; for my $ver (reverse @versions) { return $ver if ver_in_range($ver, $vers); } return undef; } sub resolve_deps { my $tries = 100; my $changes = 'INIT'; my $fail = undef; while ($changes && $tries) { $tries--; $changes = ''; $fail = undef; my @modsnow = sort keys %todo; for my $mod (@modsnow) { my $ver = $todo{$mod}; my $info = $modules{$mod}{$ver} or die "no dependency information on $mod $ver"; for my $dep (@{$info->{depends}}) { $dep =~ /^(\S+)(?: ([-0-9.]+))?/ or die "Bad dependency $dep from $info->{from}"; my($depmod, $depvers) = ($1,$2); next if $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers); # need to install a dependency my $depver = find_mod_in_range($depmod, $depvers); if (defined $depver) { $todo{$depmod} = $depver; $changes .= " $mod-$ver->$depmod-$depver"; } else { $fail ||= "Could not find module $depmod $depver required by $mod $ver"; } } for my $dep (@{$info->{conflicts}}) { $dep =~ /^(\S+)(?: ([-0-9.]+))?/ or die "Bad dependency $dep from $info->{from}"; my($depmod, $depvers) = ($1,$2); next unless $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers); # if there are changes this round, maybe the conflict won't come up after they are resolved. $fail ||= "Cannot install: module $mod ($ver) conflicts with versions $depmod version $todo{depmod}"; } } } if ($changes) { print "Infinite dependency loop:$changes\n"; exit 1; } if ($fail) { print "$fail\n"; exit 1; } } my $action = lc shift @ARGV; if ($action eq 'install') { for my $mod (@ARGV) { my $vers = $mod =~ s/=([-0-9.]+)// ? $1 : undef; $mod = lc $mod; unless ($modules{$mod}) { print "Cannot find module $mod\n"; exit 1; } my $ver = find_mod_in_range($mod, $vers); unless ($ver) { print "Cannot find suitable version of $mod\n"; exit 1; } $todo{$mod} = $ver; } } elsif ($action eq 'upgrade') { for my $mod (keys %installed) { next unless $mod =~ /^m_/; $todo{$mod} = find_mod_in_range($mod); } } else { die "Unknown action $action" } resolve_deps(); $| = 1; # immediate print of lines without \n for my $mod (keys %installed) { next if $todo{$mod}; print "Uninstalling $mod $installed{$mod}\n"; unlink "src/modules/$mod.cpp"; } for my $mod (sort keys %todo) { my $ver = $todo{$mod}; my $oldver = $installed{$mod}; my $url = $modules{$mod}{$ver}{url}; if ($url eq 'INSECURE') { print "WARNING: Version $ver of $mod is insecure!\n"; next; } elsif ($url eq 'DEPRECATED') { print "Note: version $ver of $mod is deprecated\n"; next; } next if $oldver && $oldver eq $ver; if ($oldver) { print "Upgrading $mod from $oldver to $ver using $url" } else { print "Installing $mod $ver from $url"; } my $stat = getstore($url, "src/modules/$mod.cpp"); if ($stat == 200) { print " - done\n"; } else { print " - HTTP $stat\n"; } }