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