]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - modulemanager
Headers: update remaining scripts too
[user/henk/code/inspircd.git] / modulemanager
1 #!/usr/bin/env perl
2
3 #
4 # InspIRCd -- Internet Relay Chat Daemon
5 #
6 #   Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net>
7 #
8 # This file is part of InspIRCd.  InspIRCd is free software: you can
9 # redistribute it and/or modify it under the terms of the GNU General Public
10 # License as published by the Free Software Foundation, version 2.
11 #
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15 # details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20
21
22 use strict;
23 use warnings FATAL => qw(all);
24 use LWP::Simple;
25
26 use make::configure;
27
28 our @modlist;
29
30 my %installed;
31 # $installed{name} = $version
32
33 my %modules;
34 # $modules{$name}{$version} = {
35 #       url => URL of this version
36 #       depends => [ 'm_foo 1.2.0-1.3.0', ... ]
37 #       conflicts => [ ]
38 #       from => URL of source document
39 #       mask => Reason for not installing (INSECURE/DEPRECATED)
40 #       description => some string
41 # }
42
43 my %url_seen;
44
45 sub parse_url;
46
47 # retrieve and parse entries from sources.list
48 sub parse_url {
49         my $src = shift;
50         return if $url_seen{$src};
51         $url_seen{$src}++;
52
53         my $doc = get($src);
54         die "Could not retrieve $_" unless defined $doc;
55
56         my $mod;
57         for (split /\n+/, $doc) {
58                 s/^\s+//; # ignore whitespace at start
59                 next if /^#/;
60                 if (/^module (\S+) (\S+) (\S+)/) {
61                         my($name, $ver, $url) = ($1,$2,$3);
62                         if ($modules{$name}{$ver}) {
63                                 my $origsrc = $modules{$name}{$ver}{from};
64                                 warn "Overriding module $name $ver defined from $origsrc with one from $src";
65                         }
66                         $mod = {
67                                 from => $src,
68                                 url => $url,
69                                 depends => [],
70                                 conflicts => [],
71                         };
72                         $modules{$name}{$ver} = $mod;
73                 } elsif (/^depends (.*)/) {
74                         push @{$mod->{depends}}, $1;
75                 } elsif (/^conflicts (.*)/) {
76                         push @{$mod->{conflicts}}, $1;
77                 } elsif (/^description (.*)/) {
78                         $mod->{description} = $1;
79                 } elsif (/^mask (.*)/) {
80                         $mod->{mask} = $1;
81                 } elsif (m#^source (http://\S+)#) {
82                         parse_url $1;
83                 } else {
84                         print "Unknown line in $src: $_\n";
85                 }
86         }
87 }
88
89 # hash of installed module versions from our mini-database, key (m_foobar) to version (00abacca..).
90 my %mod_versions;
91
92 # useless helper stub
93 sub getmodversion {
94         my ($file) = @_;
95         return $mod_versions{$file};
96 }
97
98 # read in installed versions
99 if (-e '.modulemanager')
100 {
101         open SRC, '.modulemanager' or die ".modulemanager exists but i can't read it: $!";
102         while (<SRC>)
103         {
104                 s/\n//;
105                 (my $mod, my $ver) = split(/ /, $_);
106                 $mod_versions{$mod} = $ver;
107         }
108         close SRC;
109 }
110
111 # read in external URL sources
112 open SRC, 'sources.list' or die "Could not open sources.list: $!";
113 while (<SRC>) {
114         next if /^\s*#/;
115         parse_url($_);
116 }
117 close SRC;
118
119 getmodules(1);
120
121 # determine core version
122 `./src/version.sh` =~ /InspIRCd-([0-9.]+)/ or die "Cannot determine inspircd version";
123 $installed{core} = $1;
124 for my $mod (keys %modules) {
125         MODVER: for my $mver (keys %{$modules{$mod}}) {
126                 for my $dep (@{$modules{$mod}{$mver}{depends}}) {
127                         next unless $dep =~ /^core (.*)/;
128                         if (!ver_in_range($installed{core}, $1)) {
129                                 delete $modules{$mod}{$mver};
130                                 next MODVER;
131                         }
132                 }
133         }
134         delete $modules{$mod} unless %{$modules{$mod}};
135 }
136 $modules{core}{$1} = {
137         url => 'NONE',
138         depends => [],
139         conflicts => [],
140         from => 'local file',
141 };
142
143 # set up core module list
144 for my $modname (@modlist) {
145         my $mod = "m_$modname";
146         my $modfile = "src/modules/$mod.cpp";
147         my $ver = getmodversion($mod) || '0.0';
148         $ver =~ s/\$Rev: (.*) \$/$1/; # for storing revision in SVN
149         $installed{$mod} = $ver;
150         next if $modules{$mod}{$ver};
151         $modules{$mod}{$ver} = {
152                 url => 'NONE',
153                 depends => [],
154                 conflicts => [],
155                 from => 'local file',
156         };
157 }
158
159 my %todo = %installed;
160
161 sub ver_cmp {
162         ($a,$b) = @_ if @_;
163
164         if ($a !~ /^[0-9.]+$/ or $b !~ /^[0-9.]+$/)
165         {
166                 # not a valid version number, don't try to sort
167                 return $a ne $b;
168         }
169
170         # else it's probably a numerical type version.. i.e. 1.0
171         my @a = split /\./, $a;
172         my @b = split /\./, $b;
173         push @a, 0 while $#a < $#b;
174         push @b, ($_[2] || 0) while $#b < $#a;
175         for my $i (0..$#a) {
176                 my $d = $a[$i] <=> $b[$i];
177                 return $d if $d;
178         }
179         return 0;
180 }
181
182 sub ver_in_range {
183         my($ver, $range) = @_;
184         return 1 unless defined $range;
185         my($l,$h) = ($range, $range);
186         if ($range =~ /(.*)-(.*)/) {
187                 ($l,$h) = ($1,$2);
188         }
189         return 0 if $l && ver_cmp($ver, $l) < 0;
190         return 0 if $h && ver_cmp($ver, $h, 9999) > 0;
191         return 1;
192 }
193
194 sub find_mod_in_range {
195         my($mod, $vers, $force) = @_;
196         my @versions = keys %{$modules{$mod}};
197         @versions = sort { -ver_cmp() } @versions;
198         for my $ver (@versions) {
199                 next if $modules{$mod}{$ver}{mask} && !$force;
200                 return $ver if ver_in_range($ver, $vers);
201         }
202         return undef;
203 }
204
205 sub resolve_deps {
206         my($trial) = @_;
207         my $tries = 100;
208         my $changes = 'INIT';
209         my $fail = undef;
210         while ($changes && $tries) {
211                 $tries--;
212                 $changes = '';
213                 $fail = undef;
214                 my @modsnow = sort keys %todo;
215                 for my $mod (@modsnow) {
216                         my $ver = $todo{$mod};
217                         my $info = $modules{$mod}{$ver} or die "no dependency information on $mod $ver";
218                         for my $dep (@{$info->{depends}}) {
219                                 $dep =~ /^(\S+)(?: (\S+))?/ or die "Bad dependency $dep from $info->{from}";
220                                 my($depmod, $depvers) = ($1,$2);
221                                 next if $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers);
222                                 # need to install a dependency
223                                 my $depver = find_mod_in_range($depmod, $depvers);
224                                 if (defined $depver) {
225                                         $todo{$depmod} = $depver;
226                                         $changes .= " $mod-$ver->$depmod-$depver";
227                                 } else {
228                                         $fail ||= "Could not find module $depmod $depvers required by $mod $ver";
229                                 }
230                         }
231                         for my $dep (@{$info->{conflicts}}) {
232                                 $dep =~ /^(\S+)(?: (\S+))?/ or die "Bad dependency $dep from $info->{from}";
233                                 my($depmod, $depvers) = ($1,$2);
234                                 next unless $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers);
235                                 # if there are changes this round, maybe the conflict won't come up after they are resolved.
236                                 $fail ||= "Cannot install: module $mod ($ver) conflicts with $depmod version $todo{$depmod}";
237                         }
238                 }
239         }
240         if ($trial) {
241                 return !($changes || $fail);
242         }
243         if ($changes) {
244                 print "Infinite dependency loop:$changes\n";
245                 exit 1;
246         }
247         if ($fail) {
248                 print "$fail\n";
249                 exit 1;
250         }
251 }
252
253 my $action = lc shift @ARGV;
254
255 if ($action eq 'install') {
256         for my $mod (@ARGV) {
257                 my $vers = $mod =~ s/=([-0-9.]+)// ? $1 : undef;
258                 $mod = lc $mod;
259                 unless ($modules{$mod}) {
260                         print "Cannot find module $mod\n";
261                         exit 1;
262                 }
263                 my $ver = find_mod_in_range($mod, $vers, $vers ? 1 : 0);
264                 unless ($ver) {
265                         print "Cannot find suitable version of $mod\n";
266                         exit 1;
267                 }
268                 $todo{$mod} = $ver;
269         }
270 } elsif ($action eq 'upgrade') {
271         my @installed = sort keys %installed;
272         for my $mod (@installed) {
273                 next unless $mod =~ /^m_/;
274                 my %saved = %todo;
275                 $todo{$mod} = find_mod_in_range($mod);
276                 if (!resolve_deps(1)) {
277                         %todo = %saved;
278                 }
279         }
280 } elsif ($action eq 'list') {
281         my @all = sort keys %modules;
282         for my $mod (@all) {
283                 my @vers = sort { ver_cmp() } keys %{$modules{$mod}};
284                 my $desc = '';
285                 for my $ver (@vers) {
286                         # latest defined description wins
287                         $desc = $modules{$mod}{$ver}{description} || $desc;
288                 }
289                 next if @vers == 1 && $modules{$mod}{$vers[0]}{url} eq 'NONE';
290                 my $instver = $installed{$mod} || '';
291                 my $vers = join ' ', map { $_ eq $instver ? "\e[1m$_\e[m" : $_ } @vers;
292                 print "$mod ($vers) - $desc\n";
293         }
294 } else {
295         print <<ENDUSAGE
296 Use: $0 <action> <args>
297 Action is one of the following
298  install   install new modules
299  upgrade   upgrade installed modules
300  list      lists available modules
301
302 For installing a package, specify its name or name=version to force the
303 installation of a specific version.
304 ENDUSAGE
305 ;exit 1;
306 }
307
308 resolve_deps(0);
309
310 $| = 1; # immediate print of lines without \n
311
312 print "Processing changes for $action...\n";
313 for my $mod (keys %installed) {
314         next if $todo{$mod};
315         print "Uninstalling $mod $installed{$mod}\n";
316         unlink "src/modules/$mod.cpp";
317 }
318
319 my $count = scalar keys %todo;
320 print "Checking $count items...\n";
321 for my $mod (sort keys %todo) {
322         my $ver = $todo{$mod};
323         my $oldver = $installed{$mod};
324         if ($modules{$mod}{$ver}{mask}) {
325                 print "Module $mod $ver is masked: $modules{$mod}{$ver}{mask}\n";
326         }
327         next if $oldver && $oldver eq $ver;
328         my $url = $modules{$mod}{$ver}{url};
329         if ($oldver) {
330                 print "Upgrading $mod from $oldver to $ver using $url"
331         } else {
332                 print "Installing $mod $ver from $url";
333         }
334         $mod_versions{$mod} = $ver;
335
336         my $stat = getstore($url, "src/modules/$mod.cpp");
337         if ($stat == 200) {
338                 print " - done\n";
339         } else {
340                 print " - HTTP $stat\n";
341         }
342 }
343
344 # write database of installed versions
345 open SRC, '>.modulemanager' or die "can't write installed versions to .modulemanager, won't be able to track upgrades properly: $!";
346 foreach my $key (keys %mod_versions)
347 {
348         print SRC "$key $mod_versions{$key}\n";
349 }
350 close SRC;
351
352 print "Finished!\n";