scripts/feeds: ignore virtual packages
[openwrt.git] / scripts / feeds
1 #!/usr/bin/perl
2 use Getopt::Std;
3 use FindBin;
4 use Cwd;
5 use lib "$FindBin::Bin";
6 use metadata;
7 use warnings;
8 use strict;
9 use Cwd 'abs_path';
10
11 chdir "$FindBin::Bin/..";
12 $ENV{TOPDIR}=getcwd();
13
14 my $mk=`which gmake`;   # select the right 'make' program
15 chomp($mk);             # trim trailing newline
16 $mk or $mk = "make";    # default to 'make'
17
18 # check version of make
19 my @mkver = split /\s+/, `$mk -v`, 4;
20 my $valid_mk = 1;
21 $mkver[0] =~ /^GNU/ or $valid_mk = 0;
22 $mkver[1] =~ /^Make/ or $valid_mk = 0;
23 $mkver[2] >= "3.81" or $valid_mk = 0;
24 $valid_mk or die "Unsupported version of make found: $mk\n";
25
26 my @feeds;
27 my %build_packages;
28 my %installed;
29
30 sub parse_config() {
31         my $line = 0;
32         my %name;
33
34         open FEEDS, "feeds.conf" or
35                 open FEEDS, "feeds.conf.default" or
36                 die "Unable to open feeds configuration";
37         while (<FEEDS>) {
38                 chomp;
39                 s/#.+$//;
40                 next unless /\S/;
41                 my @line = split /\s+/, $_, 3;
42                 my @src;
43                 $line++;
44
45                 my $valid = 1;
46                 $line[0] =~ /^src-\w+$/ or $valid = 0;
47                 $line[1] =~ /^\w+$/ or $valid = 0;
48                 @src = split /\s+/, $line[2];
49                 $valid or die "Syntax error in feeds.list, line: $line\n";
50
51                 $name{$line[1]} and die "Duplicate feed name '$line[1]', line: $line\n";
52                 $name{$line[1]} = 1;
53
54                 push @feeds, [$line[0], $line[1], \@src];
55         }
56         close FEEDS;
57 }
58
59 sub update_location($$)
60 {
61         my $name = shift;
62         my $url  = shift;
63         my $old_url;
64
65         -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
66
67         if( open LOC, "< ./feeds/$name.tmp/location" )
68         {
69                 chomp($old_url = readline LOC);
70                 close LOC;
71         }
72
73         if( !$old_url || $old_url ne $url )
74         {
75                 if( open LOC, "> ./feeds/$name.tmp/location" )
76                 {
77                         print LOC $url, "\n";
78                         close LOC;
79                 }
80                 return $old_url ? 1 : 0;
81         }
82
83         return 0;
84 }
85
86 sub update_index($)
87 {
88         my $name = shift;
89
90         -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
91         -d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
92
93         system("$mk -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
94         system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"packageinfo\" SCAN_DIR=\"feeds/$name\" SCAN_NAME=\"package\" SCAN_DEPS=\"$ENV{TOPDIR}/include/package*.mk\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
95         system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
96
97         return 0;
98 }
99
100 my %update_method = (
101         'src-svn' => {
102                 'init'          => "svn checkout '%s' '%s'",
103                 'update'        => "svn update",
104                 'controldir'    => ".svn"},
105         'src-cpy' => {
106                 'init'          => "cp -Rf '%s' '%s'",
107                 'update'        => ""},
108         'src-link' => {
109                 'init'          => "ln -s '%s' '%s'",
110                 'update'        => ""},
111         'src-git' => {
112                 'init'          => "git clone --depth 1 '%s' '%s'",
113                 'update'        => "git pull",
114                 'controldir'    => ".git"},
115         'src-bzr' => {
116                 'init'          => "bzr checkout --lightweight '%s' '%s'",
117                 'update'        => "bzr update",
118                 'controldir'    => ".bzr"},
119         'src-hg' => {
120                 'init'          => "hg clone '%s' '%s'",
121                 'update'        => "hg pull --update",
122                 'controldir'    => ".hg"}
123 );
124
125 # src-git: pull broken
126 # src-cpy: broken if `basename $src` != $name
127
128 sub update_feed_via($$$$) {
129         my $type = shift;
130         my $name = shift;
131         my $src = shift;
132         my $relocate = shift;
133         
134         my $m = $update_method{$type};
135         my $localpath = "./feeds/$name";
136         my $safepath = $localpath;
137         $safepath =~ s/'/'\\''/;
138
139         if( $relocate || !$m->{'update'} || !-d "$localpath/$m->{'controldir'}" ) {
140                 system("rm -rf '$safepath'");
141                 system(sprintf($m->{'init'}, $src, $safepath)) == 0 or return 1;
142         } else {
143                 system("cd '$safepath'; $m->{'update'}") == 0 or return 1;
144         }
145         
146         return 0;
147 }
148
149 sub get_feed($) {
150         my $feed = shift;
151         my $file = "./feeds/$feed.index";
152
153         clear_packages();
154
155         -f $file or do {
156                 print "Ignoring feed '$feed' - index missing\n";
157                 return;
158         };
159         parse_package_metadata($file) or return;
160         return { %package };
161 }
162
163 sub get_installed() {
164         system("$mk -s prepare-tmpinfo");
165         clear_packages();
166         parse_package_metadata("./tmp/.packageinfo");
167         %installed = %package;
168 }
169
170 sub search_feed {
171         my $feed = shift;
172         my @substr = @_;
173         my $display;
174
175         return unless @substr > 0;
176         get_feed($feed);
177         foreach my $name (sort { lc($a) cmp lc($b) } keys %package) {
178                 my $pkg = $package{$name};
179                 my $substr;
180                 my $pkgmatch = 1;
181
182                 next if $pkg->{vdepends};
183                 foreach my $substr (@substr) {
184                         my $match;
185                         foreach my $key (qw(name title description src)) {
186                                 $pkg->{$key} and $substr and $pkg->{$key} =~ m/$substr/i and $match = 1;
187                         }
188                         $match or undef $pkgmatch;
189                 };
190                 $pkgmatch and do {
191                         $display or do {
192                                 print "Search results in feed '$feed':\n";
193                                 $display = 1;
194                         };
195                         printf "\%-25s\t\%s\n", $pkg->{name}, $pkg->{title};
196                 };
197         }
198         return 0;
199 }
200
201 sub search {
202         my %opts;
203
204         getopt('r:', \%opts);
205         foreach my $feed (@feeds) {
206                 search_feed($feed->[1], @ARGV) if (!defined($opts{r}) or $opts{r} eq $feed->[1]);
207         }
208 }
209
210 sub list_feed {
211         my $feed = shift;
212
213         get_feed($feed);
214         foreach my $name (sort { lc($a) cmp lc($b) } keys %package) {
215                 my $pkg = $package{$name};
216                 next if $pkg->{vdepends};
217                 if($pkg->{name}) {
218                         printf "\%-32s\t\%s\n", $pkg->{name}, $pkg->{title};
219                 }
220         }
221
222         return 0;
223 }
224
225 sub list {
226         my %opts;
227
228         getopts('r:sh', \%opts);
229         if ($opts{h}) {
230                 usage();
231                 return 0;
232         }
233         if ($opts{s}) {
234                 foreach my $feed (@feeds) {
235                         printf "\%-32s\tURL: %s\n", $feed->[1], join(", ", @{$feed->[2]});
236                 }
237                 return 0;
238         }
239         foreach my $feed (@feeds) {
240                 list_feed($feed->[1], @ARGV) if (!defined($opts{r}) or $opts{r} eq $feed->[1]);
241         }
242         return 0;
243 }
244
245 sub install_generic() {
246         my $feed = shift;
247         my $pkg = shift;
248         my $path = $pkg->{makefile};
249
250         if($path) {
251                 $path =~ s/\/Makefile$//;
252
253                 -d "./package/feeds" or mkdir "./package/feeds";
254                 -d "./package/feeds/$feed->[1]" or mkdir "./package/feeds/$feed->[1]";
255                 system("ln -sf ../../../$path ./package/feeds/$feed->[1]/");
256         } else {
257                 warn "Package is not valid\n";
258                 return 1;
259         }
260
261         return 0;
262 }
263
264 my %install_method = (
265         'src-svn' => \&install_generic,
266         'src-cpy' => \&install_generic,
267         'src-link' => \&install_generic,
268         'src-git' => \&install_generic,
269         'src-bzr' => \&install_generic,
270         'src-hg' => \&install_generic,
271 );
272
273 my %feed;
274
275 sub lookup_package($$) {
276         my $feed = shift;
277         my $package = shift;
278
279         foreach my $feed ($feed, @feeds) {
280                 next unless $feed->[1];
281                 next unless $feed{$feed->[1]};
282                 $feed{$feed->[1]}->{$package} and return $feed;
283         }
284         return;
285 }
286
287 sub install_package {
288         my $feed = shift;
289         my $name = shift;
290         my $ret = 0;
291
292         $feed = lookup_package($feed, $name);
293         $feed or do {
294                 $installed{$name} and return 0;
295                 # TODO: check if it's already installed within ./package directory
296                 $srcpackage{$name} or -d "./package/$name" or warn "WARNING: No feed for package '$name' found, maybe it's already part of the standard packages?\n";
297                 return 0;
298         };
299
300         my $pkg = $feed{$feed->[1]}->{$name} or return 1;
301         $pkg->{name} or do {
302                 $installed{$name} and return 0;
303                 # TODO: check if this is an alias package, maybe it's known by another name
304                 warn "WARNING: Package '$name' is not available in feed $feed->[1].\n";
305                 return 0;
306         };
307         my $src = $pkg->{src};
308         my $type = $feed->[0];
309         $src or $src = $name;
310
311         # previously installed packages set the runtime package
312         # newly installed packages set the source package
313         $installed{$src} and return 0;
314
315         # check previously installed packages
316         $installed{$name} and return 0;
317         $installed{$src} = 1;
318         warn "Installing package '$src'\n";
319
320         $install_method{$type} or do {
321                 warn "Unknown installation method: '$type'\n";
322                 return 1;
323         };
324
325         &{$install_method{$type}}($feed, $pkg) == 0 or do {
326                 warn "failed.\n";
327                 return 1;
328         };
329
330         # install all dependencies
331         foreach my $vpkg (@{$srcpackage{$src}}, $pkg) {
332                 foreach my $dep (@{$vpkg->{depends}}, @{$vpkg->{builddepends}}, @{$vpkg->{"builddepends/host"}}) {
333                         next if $dep =~ /@/;
334                         $dep =~ s/^\+//;
335                         $dep =~ s/^.+://;
336                         $dep =~ s/\/.+$//;
337                         next unless $dep;
338                         install_package($feed, $dep) == 0 or $ret = 1;
339                 }
340         }
341
342         return $ret;
343 }
344
345 sub refresh_config {
346         my $default = shift;
347
348         # workaround for timestamp check
349         system("rm -f tmp/.packageinfo");
350
351         # refresh the config
352         if ($default) {
353                 system("$mk oldconfig CONFDEFAULT=\"$default\" Config.in >/dev/null 2>/dev/null");
354         } else {
355                 system("$mk defconfig Config.in >/dev/null 2>/dev/null");
356         }
357 }
358
359 sub install {
360         my $name;
361         my %opts;
362         my $feed;
363         my $ret = 0;
364
365         getopts('ap:d:h', \%opts);
366
367         if ($opts{h}) {
368                 usage();
369                 return 0;
370         }
371
372         get_installed();
373
374         foreach my $f (@feeds) {
375                 # index all feeds
376                 $feed{$f->[1]} = get_feed($f->[1]);
377
378                 # look up the preferred feed
379                 $opts{p} and $f->[1] eq $opts{p} and $feed = $f;
380         }
381
382         if($opts{a}) {
383                 foreach my $f (@feeds) {
384                         if (!defined($opts{p}) or $opts{p} eq $f->[1]) {
385                                 printf "Installing all packages from feed %s.\n", $f->[1];
386                                 get_feed($f->[1]);
387                                 foreach my $name (sort { lc($a) cmp lc($b) } keys %package) {
388                                         my $p = $package{$name};
389                                         next if $p->{vdepends};
390                                         if( $p->{name} ) {
391                                                 install_package($feed, $p->{name}) == 0 or $ret = 1;
392                                         }
393                                 }
394                         }
395                 }
396         } else {
397                 while ($name = shift @ARGV) {
398                         install_package($feed, $name) == 0 or $ret = 1;
399                 }
400         }
401
402         # workaround for timestamp check
403
404         # set the defaults
405         if ($opts{d} and $opts{d} =~ /^[ymn]$/) {
406                 refresh_config($opts{d});
407         }
408
409         return $ret;
410 }
411
412 sub uninstall {
413         my %opts;
414         my $name;
415         my $uninstall;
416
417         getopts('ah', \%opts);
418
419         if ($opts{h}) {
420                 usage();
421                 return 0;
422         }
423
424         if ($opts{a}) {
425                 system("rm -rvf ./package/feeds");
426                 $uninstall = 1;
427         } else {
428                 if($#ARGV == -1) {
429                         warn "WARNING: no package to uninstall\n";
430                         return 0;
431                 }
432                 get_installed();
433                 while ($name = shift @ARGV) {
434                         my $pkg = $installed{$name};
435                         $pkg or do {
436                                 warn "WARNING: $name not installed\n";
437                                 next;
438                         };
439                         $pkg->{src} and $name = $pkg->{src};
440                         warn "Uninstalling package '$name'\n";
441                         system("rm -f ./package/feeds/*/$name");
442                         $uninstall = 1;
443                 }
444         }
445         $uninstall and refresh_config();
446         return 0;
447 }
448
449 sub update_feed($$$$)
450 {
451         my $type=shift;
452         my $name=shift;
453         my $src=shift;
454         my $perform_update=shift;
455         my $force_relocate=update_location( $name, "@$src" );
456
457         if( $force_relocate ) {
458                 warn "Source of feed $name has changed, replacing copy\n";
459         }
460         $update_method{$type} or do {
461                 warn "Unknown type '$type' in feed $name\n";
462                 return 1;
463         };
464         $perform_update and do {
465                 my $failed = 1;
466                 foreach my $feedsrc (@$src) {
467                         warn "Updating feed '$name' from '$feedsrc' ...\n";
468                         next unless update_feed_via($type, $name, $feedsrc, $force_relocate) == 0;
469                         $failed = 0;
470                         last;
471                 }
472                 $failed and do {
473                         warn "failed.\n";
474                         return 1;
475                 };
476         };
477         warn "Create index file './feeds/$name.index' \n";
478         update_index($name) == 0 or do {
479                 warn "failed.\n";
480                 return 1;
481         };
482         return 0;
483 }
484
485 sub update {
486         my %opts;
487         my $feed_name;
488         my $perform_update=1;
489
490         $ENV{SCAN_COOKIE} = $$;
491         $ENV{KBUILD_VERBOSE} = 99;
492
493         getopts('ahi', \%opts);
494
495         if ($opts{h}) {
496                 usage();
497                 return 0;
498         }
499
500         if ($opts{i}) {
501                 # don't update from (remote) repository
502                 # only re-create index information
503                 $perform_update=0;
504         }
505
506         -d "feeds" or do {
507                         mkdir "feeds" or die "Unable to create the feeds directory";
508                 };
509
510         if ( ($#ARGV == -1) or $opts{a}) {
511                 foreach my $feed (@feeds) {
512                         my ($type, $name, $src) = @$feed;
513                         update_feed($type, $name, $src, $perform_update);
514                 }
515         } else {
516                 while ($feed_name = shift @ARGV) {
517                         foreach my $feed (@feeds) {
518                                 my ($type, $name, $src) = @$feed;
519                                 if($feed_name ne $name) {
520                                         next;
521                                 }
522                                 update_feed($type, $name, $src, $perform_update);
523                         }
524                 }
525         }
526
527         refresh_config();
528
529         return 0;
530 }
531
532 sub usage() {
533         print <<EOF;
534 Usage: $0 <command> [options]
535
536 Commands:
537         list [options]: List feeds and their content
538         Options:
539             -s :           List of feed names and their URL.
540             -r <feedname>: List packages of specified feed.
541
542         install [options] <package>: Install a package
543         Options:
544             -a :           Install all packages from all feeds or from the specified feed using the -p option.
545             -p <feedname>: Prefer this feed when installing packages.
546             -d <y|m|n>:    Set default for newly installed packages.
547
548         search [options] <substring>: Search for a package
549         Options:
550             -r <feedname>: Only search in this feed
551
552         uninstall -a|<package>: Uninstall a package
553         Options:
554             -a :           Uninstalls all packages.
555
556         update -a|<feedname(s)>: Update packages and lists of feeds in feeds.conf .
557         Options:
558             -a :           Update all feeds listed within feeds.conf. Otherwise the spezified feeds will be updated.
559             -i :           Recreate the index only. No feed update from repository is performed.
560
561         clean:             Remove downloaded/generated files.
562
563 EOF
564         exit(1);
565 }
566
567 my %commands = (
568         'list' => \&list,
569         'update' => \&update,
570         'install' => \&install,
571         'search' => \&search,
572         'uninstall' => \&uninstall,
573         'clean' => sub {
574                 system("rm -rf feeds");
575         }
576 );
577
578 my $arg = shift @ARGV;
579 $arg or usage();
580 parse_config;
581 foreach my $cmd (keys %commands) {
582         $arg eq $cmd and do {
583                 exit(&{$commands{$cmd}}());
584         };
585 }
586 usage();