]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - modulemanager
Allow for string comparison of git IDs.
[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
16 #       depends => [ 'm_foo 1.2.0-1.3.0', ... ]
17 #       conflicts => [ ]
18 #       from => URL of source document
19 #       mask => Reason for not installing (INSECURE/DEPRECATED)
20 #       description => some string
21 # }
22
23 my %url_seen;
24
25 sub parse_url;
26
27 sub parse_url {
28         my $src = shift;
29         return if $url_seen{$src};
30         $url_seen{$src}++;
31
32         my $doc = get($src);
33         die "Could not retrieve $_" unless defined $doc;
34
35         my $mod;
36         for (split /\n+/, $doc) {
37                 s/^\s+//; # ignore whitespace at start
38                 next if /^#/;
39                 if (/^module (\S+) (\S+) (\S+)/) {
40                         my($name, $ver, $url) = ($1,$2,$3);
41                         if ($modules{$name}{$ver}) {
42                                 my $origsrc = $modules{$name}{$ver}{from};
43                                 warn "Overriding module $name $ver defined from $origsrc with one from $src";
44                         }
45                         $mod = {
46                                 from => $src,
47                                 url => $url,
48                                 depends => [],
49                                 conflicts => [],
50                         };
51                         $modules{$name}{$ver} = $mod;
52                 } elsif (/^depends (.*)/) {
53                         push @{$mod->{depends}}, $1;
54                 } elsif (/^conflicts (.*)/) {
55                         push @{$mod->{conflicts}}, $1;
56                 } elsif (/^description (.*)/) {
57                         $mod->{description} = $1;
58                 } elsif (/^mask (.*)/) {
59                         $mod->{mask} = $1;
60                 } elsif (m#^source (http://\S+)#) {
61                         parse_url $1;
62                 } else {
63                         print "Unknown line in $src: $_\n";
64                 }
65         }
66 }
67
68 open SRC, 'sources.list' or die "Could not open sources.list: $!";
69 while (<SRC>) {
70         next if /^\s*#/;
71         parse_url($_);
72 }
73 close SRC;
74
75 getmodules(1);
76
77 `./src/version.sh` =~ /InspIRCd-([0-9.]+)/ or die "Cannot determine inspircd version";
78 $installed{core} = $1;
79 $modules{core}{$1} = {
80         url => 'NONE',
81         depends => [],
82         conflicts => [],
83         from => 'local file',
84 };
85
86 for my $modname (@modlist) {
87         my $mod = "m_$modname";
88         my $modfile = "src/modules/$mod.cpp";
89         my $ver = getmodversion($modfile) || '0.0';
90         $ver =~ s/\$Rev: (.*) \$/$1/; # for storing revision in SVN
91         $installed{$mod} = $ver;
92         next if $modules{$mod}{$ver};
93         $modules{$mod}{$ver} = {
94                 url => 'NONE',
95                 depends => [],
96                 conflicts => [],
97                 from => 'local file',
98         };
99 }
100
101 my %todo = %installed;
102
103 sub ver_cmp {
104         ($a,$b) = @_ if @_;
105
106         # string versions first, git IDs
107         if ($a =~ /[a-z0-9]{40}/ or $b =~ /[a-z0-9]{40}/)
108         {
109                 # it's a string version. compare them as such.
110                 return $a ne $b;
111         }
112
113         # else it's probably a numerical type version.. i.e. 1.0
114         my @a = split /\./, $a;
115         my @b = split /\./, $b;
116         push @a, 0 while $#a < $#b;
117         push @b, 0 while $#b < $#a;
118         for my $i (0..$#a) {
119                 my $d = $a[$i] <=> $b[$i];
120                 return $d if $d;
121         }
122         return 0;
123 }
124
125 sub ver_in_range {
126         my($ver, $range) = @_;
127         return 1 unless defined $range;
128         if ($range =~ /(.*)-(.*)/) {
129                 my($l,$h) = ($1,$2);
130                 return 0 if $l && ver_cmp($ver, $l) < 0;
131                 return 0 if $h && ver_cmp($ver, $h) > 0;
132                 return 1;
133         }
134         return !ver_cmp($ver, $range);
135 }
136
137 sub find_mod_in_range {
138         my($mod, $vers, $force) = @_;
139         my @versions = keys %{$modules{$mod}};
140         @versions = sort { -ver_cmp() } @versions;
141         for my $ver (@versions) {
142                 next if $modules{$mod}{$ver}{mask} && !$force;
143                 return $ver if ver_in_range($ver, $vers);
144         }
145         return undef;
146 }
147
148 sub resolve_deps {
149         my($trial) = @_;
150         my $tries = 100;
151         my $changes = 'INIT';
152         my $fail = undef;
153         while ($changes && $tries) {
154                 $tries--;
155                 $changes = '';
156                 $fail = undef;
157                 my @modsnow = sort keys %todo;
158                 for my $mod (@modsnow) {
159                         my $ver = $todo{$mod};
160                         my $info = $modules{$mod}{$ver} or die "no dependency information on $mod $ver";
161                         for my $dep (@{$info->{depends}}) {
162                                 $dep =~ /^(\S+)(?: ([-0-9.]+))?/ or die "Bad dependency $dep from $info->{from}";
163                                 my($depmod, $depvers) = ($1,$2);
164                                 next if $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers);
165                                 # need to install a dependency
166                                 my $depver = find_mod_in_range($depmod, $depvers);
167                                 if (defined $depver) {
168                                         $todo{$depmod} = $depver;
169                                         $changes .= " $mod-$ver->$depmod-$depver";
170                                 } else {
171                                         $fail ||= "Could not find module $depmod $depvers required by $mod $ver";
172                                 }
173                         }
174                         for my $dep (@{$info->{conflicts}}) {
175                                 $dep =~ /^(\S+)(?: ([-0-9.]+))?/ or die "Bad dependency $dep from $info->{from}";
176                                 my($depmod, $depvers) = ($1,$2);
177                                 next unless $todo{$depmod} && ver_in_range($todo{$depmod}, $depvers);
178                                 # if there are changes this round, maybe the conflict won't come up after they are resolved.
179                                 $fail ||= "Cannot install: module $mod ($ver) conflicts with $depmod version $todo{$depmod}";
180                         }
181                 }
182         }
183         if ($trial) {
184                 return !($changes || $fail);
185         }
186         if ($changes) {
187                 print "Infinite dependency loop:$changes\n";
188                 exit 1;
189         }
190         if ($fail) {
191                 print "$fail\n";
192                 exit 1;
193         }
194 }
195
196 my $action = lc shift @ARGV;
197
198 if ($action eq 'install') {
199         for my $mod (@ARGV) {
200                 my $vers = $mod =~ s/=([-0-9.]+)// ? $1 : undef;
201                 $mod = lc $mod;
202                 unless ($modules{$mod}) {
203                         print "Cannot find module $mod\n";
204                         exit 1;
205                 }
206                 my $ver = find_mod_in_range($mod, $vers, $vers ? 1 : 0);
207                 unless ($ver) {
208                         print "Cannot find suitable version of $mod\n";
209                         exit 1;
210                 }
211                 $todo{$mod} = $ver;
212         }
213 } elsif ($action eq 'upgrade') {
214         my @installed = sort keys %installed;
215         for my $mod (@installed) {
216                 next unless $mod =~ /^m_/;
217                 my %saved = %todo;
218                 $todo{$mod} = find_mod_in_range($mod);
219                 if (!resolve_deps(1)) {
220                         %todo = %saved;
221                 }
222         }
223 } elsif ($action eq 'list') {
224         my @all = sort keys %modules;
225         for my $mod (@all) {
226                 my @vers = sort { ver_cmp() } keys %{$modules{$mod}};
227                 my $desc = '';
228                 for my $ver (@vers) {
229                         # latest defined description wins
230                         $desc = $modules{$mod}{$ver}{description} || $desc;
231                 }
232                 next if @vers == 1 && $modules{$mod}{$vers[0]}{url} eq 'NONE';
233                 my $instver = $installed{$mod} || '';
234                 my $vers = join ' ', map { $_ eq $instver ? "\e[1m$_\e[m" : $_ } @vers;
235                 print "$mod ($vers) - $desc\n";
236         }
237 } else {
238         print <<ENDUSAGE
239 Use: $0 <action> <args>
240 Action is one of the following
241  install   install new modules
242  upgrade   upgrade installed modules
243  list      lists available modules
244
245 For installing a package, specify its name or name=version to force the
246 installation of a specific version.
247 ENDUSAGE
248 ;exit 1;
249 }
250
251 resolve_deps(0);
252
253 $| = 1; # immediate print of lines without \n
254
255 for my $mod (keys %installed) {
256         next if $todo{$mod};
257         print "Uninstalling $mod $installed{$mod}\n";
258         unlink "src/modules/$mod.cpp";
259 }
260 for my $mod (sort keys %todo) {
261         my $ver = $todo{$mod};
262         my $oldver = $installed{$mod};
263         if ($modules{$mod}{$ver}{mask}) {
264                 print "Module $mod $ver is masked: $modules{$mod}{$ver}{mask}\n";
265         }
266         next if $oldver && $oldver eq $ver;
267         my $url = $modules{$mod}{$ver}{url};
268         if ($oldver) {
269                 print "Upgrading $mod from $oldver to $ver using $url"
270         } else {
271                 print "Installing $mod $ver from $url";
272         }
273         my $stat = getstore($url, "src/modules/$mod.cpp");
274         if ($stat == 200) {
275                 print " - done\n";
276         } else {
277                 print " - HTTP $stat\n";
278         }
279 }