scripts/feeds: Add support for git feeds.
[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_svn($$) {
45         my $name = shift;
46         my $src = shift;
47
48         system("svn co $src ./feeds/$name") == 0 or return 1;
49         -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
50         -d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
51
52         system("make -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
53         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\"");
54         system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
55
56         return 0;
57 }
58
59 sub update_cpy($$) {
60         my $name = shift;
61         my $src = shift;
62
63         system("cp -Rf $src ./feeds/$name");
64         -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
65         -d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
66
67         system("make -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
68         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\"");
69         system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
70
71         return 0;
72 }
73
74 sub update_link($$) {
75         my $name = shift;
76         my $src = abs_path(shift);
77
78         system("ln -sf $src ./feeds/$name");
79         -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
80         -d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
81
82         system("make -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
83         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\"");
84         system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
85
86         return 0;
87 }
88
89 sub update_git($$) {
90         my $name = shift;
91         my $src = shift;
92
93     if ( ! -d "./feeds/$name" ) {
94         system("git clone $src ./feeds/$name") == 0 or return 1;
95     } else {
96         system("GIT_DIR=./feeds/$name/.git git pull") == 0 or return 1;
97     }
98
99         -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
100         -d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
101
102         system("make -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
103         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\"");
104         system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
105
106         return 0;
107 }
108
109 sub get_feed($) {
110         my $feed = shift;
111
112         clear_packages();
113         parse_package_metadata("./feeds/$feed.index") or return;
114         return { %package };
115 }
116
117 sub get_installed() {
118         system("make -s prepare-tmpinfo");
119         clear_packages();
120         parse_package_metadata("./tmp/.packageinfo");
121         %installed = %package;
122 }
123
124 sub search_feed {
125         my $feed = shift;
126         my @substr = @_;
127         my $display;
128
129         return unless @substr > 0;
130         get_feed($feed);
131         foreach my $name (sort { lc($a) cmp lc($b) } keys %package) {
132                 my $pkg = $package{$name};
133                 my $substr;
134                 my $pkgmatch = 1;
135
136                 foreach my $substr (@substr) {
137                         my $match;
138                         foreach my $key (qw(name title description)) {
139                                 $pkg->{$key} and $substr and $pkg->{$key} =~ m/$substr/i and $match = 1;
140                         }
141                         $match or undef $pkgmatch;
142                 };
143                 $pkgmatch and do {
144                         $display or do {
145                                 print "Search results in feed '$feed':\n";
146                                 $display = 1;
147                         };
148                         printf "\%-25s\t\%s\n", $pkg->{name}, $pkg->{title};
149                 };
150         }
151         return 0;
152 }
153
154
155 sub search {
156         my %opts;
157
158         getopt('r:', \%opts);
159         foreach my $feed (@feeds) {
160                 search_feed($feed->[1], @ARGV) if (!defined($opts{r}) or $opts{r} eq $feed->[1]);
161         }
162 }
163
164 sub install_generic() {
165         my $feed = shift;
166         my $pkg = shift;
167         my $path = $pkg->{makefile};
168         
169         if($path) {
170         
171                 $path =~ s/\/Makefile$//;
172
173                 -d "./package/feeds" or mkdir "./package/feeds";
174                 -d "./package/feeds/$feed->[1]" or mkdir "./package/feeds/$feed->[1]";
175                 system("ln -sf ../../../$path ./package/feeds/$feed->[1]/");
176
177         } else {
178                 warn "Package is not valid\n";
179                 return 1;
180         }
181         return 0;
182 }
183
184 my %install_method = (
185         'src-svn' => \&install_generic,
186         'src-cpy' => \&install_generic,
187         'src-link' => \&install_generic
188 );
189
190 my %feed;
191
192 sub lookup_package($$) {
193         my $feed = shift;
194         my $package = shift;
195
196         foreach my $feed ($feed, @feeds) {
197                 next unless $feed->[1];
198                 next unless $feed{$feed->[1]};
199                 $feed{$feed->[1]}->{$package} and return $feed;
200         }
201         return;
202 }
203
204 sub install_package {
205         my $feed = shift;
206         my $name = shift;
207         my $ret = 0;
208
209         $feed = lookup_package($feed, $name);
210         $feed or do {
211                 $installed{$name} and return 0;
212                 warn "WARNING: No feed for package '$name' found.\n";
213                 return 1;
214         };
215
216         my $pkg = $feed{$feed->[1]}->{$name} or return 1;
217         $pkg->{name} or do {
218                 $installed{$name} and return 0;
219                 warn "WARNING: Package '$name' is not available in feed $feed->[1].\n";
220                 return 1;
221         };
222         my $src = $pkg->{src};
223         my $type = $feed->[0];
224         $src or $src = $name;
225
226         # previously installed packages set the runtime package
227         # newly installed packages set the source package
228         $installed{$src} and return 0;
229
230         # install all dependencies
231         foreach my $dep (@{$pkg->{depends}}) {
232                 next if $dep =~ /@/;
233                 $dep =~ s/^\+//;
234                 install_package($feed, $dep) == 0 or $ret = 1;
235         }
236
237         # check previously installed packages
238         $installed{$name} and return 0;
239         $installed{$src} = 1;
240         warn "Installing package '$src'\n";
241
242         $install_method{$type} or do {
243                 warn "Unknown installation method: '$type'\n";
244                 return 1;
245         };
246
247         &{$install_method{$type}}($feed, $pkg) == 0 or do {
248                 warn "failed.\n";
249                 return 1;
250         };
251
252         return $ret;
253 }
254
255 sub refresh_config {
256         my $default = shift;
257         $default or $default = "o";
258
259         # workaround for timestamp check
260         system("rm -f tmp/.packageinfo");
261
262         # refresh the config
263         system("make oldconfig CONFDEFAULT=\"$default\" Config.in >/dev/null 2>/dev/null");
264 }
265
266 sub install {
267         my $name;
268         my %opts;
269         my $feed;
270         my $ret = 0;
271
272         getopts('ap:d:', \%opts);
273         get_installed();
274
275         foreach my $f (@feeds) {
276                 # index all feeds
277                 $feed{$f->[1]} = get_feed($f->[1]);
278
279                 # look up the preferred feed
280                 $opts{p} and $f->[1] eq $opts{p} and $feed = $f;
281         }
282
283         if($opts{a}) {
284                 foreach my $f (@feeds) {
285                         if (!defined($opts{p}) or $opts{p} eq $f->[1]) {
286                                 printf "Installing all packages from feed %s.\n", $f->[1];
287                                 get_feed($f->[1]);
288                                 foreach my $name (sort { lc($a) cmp lc($b) } keys %package) {
289                                         my $p = $package{$name};
290                                         if( $p->{name} ) {
291                                                 install_package($feed, $p->{name}) == 0 or $ret = 1;
292                                         } else {
293                                                 warn "WARNING: Package '$name' is not available\n";
294                                         }
295                                 }
296                         }
297                 }
298         } else {
299                 while ($name = shift @ARGV) {
300                         install_package($feed, $name) == 0 or $ret = 1;
301                 }
302         }
303
304         # workaround for timestamp check
305
306         # set the defaults
307         if ($opts{d} and $opts{d} =~ /^[ymn]$/) {
308                 refresh_config($opts{d});
309         }
310
311         return $ret;
312 }
313
314 sub uninstall {
315         my $name;
316         my $uninstall;
317
318         if ($ARGV[0] eq '-a') {
319                 system("rm -rf ./package/feeds");
320                 $uninstall = 1;
321         } else {
322                 get_installed();
323                 while ($name = shift @ARGV) {
324                         my $pkg = $installed{$name};
325                         $pkg or do {
326                                 warn "WARNING: $name not installed\n";
327                                 next;
328                         };
329                         $pkg->{src} and $name = $pkg->{src};
330                         warn "Uninstalling package '$name'\n";
331                         system("rm -f ./package/feeds/*/$name");
332                         $uninstall = 1;
333                 }
334         }
335         $uninstall and refresh_config();
336         return 0;
337 }
338
339 sub usage() {
340         print <<EOF;
341 Usage: $0 <command> [options]
342
343 Commands:
344         install [options] <package>: Install a package
345         Options:
346             -a installs all packages from all feeds or from the specified feed
347             -p <feedname>: Prefer this feed when installing packages
348             -d <y|m|n>:    Set default for newly installed packages
349
350         search [options] <substring>: Search for a package
351         Options:
352             -r <feedname>: Only search in this feed
353
354         uninstall -a|<package>: Uninstall a package
355             -a uninstalls all packages
356
357         update: Update packages and lists of feeds in feeds.list
358         clean: Remove downloaded/generated files
359
360 EOF
361         exit(1);
362 }
363
364 my %update_method = (
365         'src-svn' => \&update_svn,
366         'src-cpy' => \&update_cpy,
367         'src-link' => \&update_link,
368         'src-git' => \&update_git
369 );
370
371 my %commands = (
372         'update' => sub {
373                 -d "feeds" or do {
374                         mkdir "feeds" or die "Unable to create the feeds directory";
375                 };
376                 $ENV{SCAN_COOKIE} = $$;
377                 $ENV{KBUILD_VERBOSE} = 99;
378                 foreach my $feed (@feeds) {
379                         my ($type, $name, $src) = @$feed;
380                         $update_method{$type} or do {
381                                 warn "Unknown type '$type' in feed $name\n";
382                                 next;
383                         };
384                         warn "Updating feed '$name'...\n";
385                         &{$update_method{$type}}($name, $src) == 0 or do {
386                                 warn "failed.\n";
387                                 return 1;
388                         };
389                 }
390                 return 0;
391         },
392         'install' => \&install,
393         'search' => \&search,
394         'uninstall' => \&uninstall,
395         'clean' => sub {
396                 system("rm -rf feeds");
397         }
398 );
399
400 my $arg = shift @ARGV;
401 $arg or usage();
402 parse_config;
403 foreach my $cmd (keys %commands) {
404         $arg eq $cmd and do {
405                 exit(&{$commands{$cmd}}());
406         };
407 }
408 usage();