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