Root/scripts/feeds

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

Archive Download this file



interactive