]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - modulemanager
Add basic module manager, patch courtesy of danieldg.. still needs some work/review...
[user/henk/code/inspircd.git] / modulemanager
1 #!/usr/bin/perl
2 use strict;
3 use warnings FATAL => qw(all);
4 use LWP::Simple;
5
6 use make::configure;
7
8 our @modlist;
9
10 my %installed;
11 # $installed{name} = $version
12
13 my %modules;
14 # $modules{$name}{$version} = {
15 #       url => URL of this version (or INSECURE/DEPRECATED)
16 #       depends => [ 'm_foo 1.2.0-1.3.0', ... ]
17 #       conflicts => [ ]
18 #       from => URL of source document
19 # }
20
21 my %url_seen;
22
23 sub parse_url;
24
25 sub parse_url {
26         my $src = shift;
27         return if $url_seen{$src};
28         $url_seen{$src}++;
29
30         my $doc = get($src);
31         die "Could not retrieve $_" unless defined $doc;
32
33         my $mod;
34         for (split /\n+/, $doc) {
35                 s/^\s+//; # ignore whitespace at start
36                 if (/^module (\S+) ([0-9.]+) (\S+)/) {
37                         my($name, $ver, $url) = ($1,$2,$3);
38                         if ($modules{$name}{$ver}) {
39                                 my $origsrc = $modules{$name}{$ver}{from};
40                                 warn "Overriding module $name $ver defined from $origsrc with one from $src";
41                         }
42                         $mod = {
43                                 from => $src,
44                                 url => $url,
45                                 depends => [],
46                                 conflicts => [],
47                         };
48                         $modules{$name}{$ver} = $mod;
49                 } elsif (/^depends (.*)/) {
50                         push @{$mod->{depends}}, $1;
51                 } elsif (/^conflicts (.*)/) {
52                         push @{$mod->{conflicts}}, $1;
53                 } elsif (m#^source (http://\S+)#) {
54                         parse_url $1;
55                 } else {
56                         print "Unknown line in $src: $_\n";
57                 }
58         }
59 }
60
61 open SRC, 'sources.list' or die "Could not open sources.list: $!";
62 while (<SRC>) {
63         parse_url($_);
64 }
65 close SRC;
66
67 getmodules();
68
69 open my $verfile, 'src/version.sh' or die "Cannot determine inspircd version: $!";
70 $_ = join '', <$verfile>;
71 die "Cannot determine inspircd version" unless /InspIRCd-([0-9.]+)/;
72 $installed{core} = $1;
73 $modules{core}{$1} = {
74         url => 'NONE',
75         depends => [],
76         conflicts => [],
77         from => 'local file',
78 };
79 close $verfile;
80
81 for my $modname (@modlist) {
82         my $mod = "m_$modname";
83         my $modfile = "src/modules/$mod.cpp";
84         my $ver = getmodversion($modfile) || '0.0';
85         $installed{$mod} = $ver;
86         next if $modules{$mod}{$ver};
87         $modules{$mod}{$ver} = {
88                 url => 'NONE',
89                 depends => [],
90                 conflicts => [],
91                 from => 'local file',
92         };
93 }
94
95 my %todo = %installed;
96
97 sub ver_cmp {
98         ($a,$b) = @_ if @_;
99         my @a = split /\./, $a;
100         my @b = split /\./, $b;
101         push @a, 0 while $#a < $#b;
102         push @b, 0 while $#b < $#a;
103         for my $i (0..$#a) {
104                 my $d = $a[$i] <=> $b[$i];
105                 return $d if $d;
106         }
107         return 0;
108 }
109
110 sub ver_in_range {
111         my($ver, $range) = @_;
112         return 1 unless defined $range;
113         if ($range =~ /(.*)-(.*)/) {
114                 my($l,$h) = ($1,$2);
115                 return 0 unless ver_cmp($ver, $l) >= 0;
116                 return 0 unless ver_cmp($ver, $h) <= 0;
117                 return 1;
118         }
119         return !ver_cmp($ver, $range);
120 }
121
122 sub find_mod_in_range {
123         my($mod,$vers) = @_;
124         my @versions = keys %{$modules{$mod}};
125         @versions = sort { ver_cmp() } @versions;
126         for my $ver (reverse @versions) {
127                 return $ver if ver_in_range($ver, $vers);
128         }
129         return undef;
130 }
131
132 sub resolve_deps {
133         my $tries = 100;
134         my $changes = 'INIT';
135         my $fail = undef;
136         while ($changes && $tries) {
137                 $tries--;
138                 $changes = '';
139                 $fail = undef;
140                 my @modsnow = sort keys %todo;
141                 for my $mod (@modsnow) {
142                         my $ver = $todo{$mod};
143                         my $info = $modules{$mod}{$ver} or die "no dependency information on $mod $ver";
144                         for my $dep (@{$info->{depends}}) {
145                                 $dep =~ /^(\S+)(?: ([-0-9.]+))?/ or die "Bad dependency $dep from $info->{from}";
146                                 my($depmod, $depvers) = ($1,$2);
147                                 next if $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers);
148                                 # need to install a dependency
149                                 my $depver = find_mod_in_range($depmod, $depvers);
150                                 if (defined $depver) {
151                                         $todo{$depmod} = $depver;
152                                         $changes .= " $mod-$ver->$depmod-$depver";
153                                 } else {
154                                         $fail ||= "Could not find module $depmod $depver required by $mod $ver";
155                                 }
156                         }
157                         for my $dep (@{$info->{conflicts}}) {
158                                 $dep =~ /^(\S+)(?: ([-0-9.]+))?/ or die "Bad dependency $dep from $info->{from}";
159                                 my($depmod, $depvers) = ($1,$2);
160                                 next unless $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers);
161                                 # if there are changes this round, maybe the conflict won't come up after they are resolved.
162                                 $fail ||= "Cannot install: module $mod ($ver) conflicts with versions $depmod version $todo{depmod}";
163                         }
164                 }
165         }
166         if ($changes) {
167                 print "Infinite dependency loop:$changes\n";
168                 exit 1;
169         }
170         if ($fail) {
171                 print "$fail\n";
172                 exit 1;
173         }
174 }
175
176 my $action = lc shift @ARGV;
177
178 if ($action eq 'install') {
179         for my $mod (@ARGV) {
180                 my $vers = $mod =~ s/=([-0-9.]+)// ? $1 : undef;
181                 $mod = lc $mod;
182                 unless ($modules{$mod}) {
183                         print "Cannot find module $mod\n";
184                         exit 1;
185                 }
186                 my $ver = find_mod_in_range($mod, $vers);
187                 unless ($ver) {
188                         print "Cannot find suitable version of $mod\n";
189                         exit 1;
190                 }
191                 $todo{$mod} = $ver;
192         }
193 } elsif ($action eq 'upgrade') {
194         for my $mod (keys %installed) {
195                 next unless $mod =~ /^m_/;
196                 $todo{$mod} = find_mod_in_range($mod);
197         }
198 } else {
199         die "Unknown action $action"
200 }
201
202 resolve_deps();
203
204 $| = 1; # immediate print of lines without \n
205
206 for my $mod (keys %installed) {
207         next if $todo{$mod};
208         print "Uninstalling $mod $installed{$mod}\n";
209         unlink "src/modules/$mod.cpp";
210 }
211 for my $mod (sort keys %todo) {
212         my $ver = $todo{$mod};
213         my $oldver = $installed{$mod};
214         my $url = $modules{$mod}{$ver}{url};
215         if ($url eq 'INSECURE') {
216                 print "WARNING: Version $ver of $mod is insecure!\n";
217                 next;
218         } elsif ($url eq 'DEPRECATED') {
219                 print "Note: version $ver of $mod is deprecated\n";
220                 next;
221         }
222         next if $oldver && $oldver eq $ver;
223         if ($oldver) {
224                 print "Upgrading $mod from $oldver to $ver using $url"
225         } else {
226                 print "Installing $mod $ver from $url";
227         }
228         my $stat = getstore($url, "src/modules/$mod.cpp");
229         if ($stat == 200) {
230                 print " - done\n";
231         } else {
232                 print " - HTTP $stat\n";
233         }
234 }