#!/usr/bin/perl use Getopt::Std; use FindBin; use Cwd; use lib "$FindBin::Bin"; use metadata; use warnings; use strict; use Cwd 'abs_path'; chdir "$FindBin::Bin/.."; $ENV{TOPDIR}=getcwd(); my $mk=`which gmake`; # select the right 'make' program chomp($mk); # trim trailing newline $mk or $mk = "make"; # default to 'make' my @feeds; my %build_packages; my %installed; sub parse_config() { my $line = 0; my %name; open FEEDS, "feeds.conf" or open FEEDS, "feeds.conf.default" or die "Unable to open feeds configuration"; while () { chomp; s/#.+$//; next unless /\S/; my @line = split /\s+/, $_, 3; my @src; $line++; my $valid = 1; $line[0] =~ /^src-\w+$/ or $valid = 0; $line[1] =~ /^\w+$/ or $valid = 0; @src = split /\s+/, $line[2]; $valid or die "Syntax error in feeds.list, line: $line\n"; $name{$line[1]} and die "Duplicate feed name '$line[1]', line: $line\n"; $name{$line[1]} = 1; push @feeds, [$line[0], $line[1], \@src]; } close FEEDS; } sub update_location($$) { my $name = shift; my $url = shift; my $old_url; -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1; if( open LOC, "< ./feeds/$name.tmp/location" ) { chomp($old_url = readline LOC); close LOC; } if( !$old_url || $old_url ne $url ) { if( open LOC, "> ./feeds/$name.tmp/location" ) { print LOC $url, "\n"; close LOC; } return $old_url ? 1 : 0; } return 0; } sub update_index($) { my $name = shift; -d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1; -d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1; system("$mk -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\""); 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\""); system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index"); return 0; } my %update_method = ( 'src-svn' => { 'init' => "svn checkout '%s' '%s'", 'update' => "svn update", 'controldir' => ".svn"}, 'src-cpy' => { 'init' => "cp -Rf '%s' '%s'", 'update' => ""}, 'src-link' => { 'init' => "ln -s '%s' '%s'", 'update' => ""}, 'src-git' => { 'init' => "git clone --depth 1 '%s' '%s'", 'update' => "git pull", 'controldir' => ".git"}, 'src-bzr' => { 'init' => "bzr checkout --lightweight '%s' '%s'", 'update' => "bzr update", 'controldir' => ".bzr"}, 'src-hg' => { 'init' => "hg clone '%s' '%s'", 'update' => "hg pull --update", 'controldir' => ".hg"} ); # src-git: pull broken # src-cpy: broken if `basename $src` != $name sub update_feed_via($$$$) { my $type = shift; my $name = shift; my $src = shift; my $relocate = shift; my $m = $update_method{$type}; my $localpath = "./feeds/$name"; my $safepath = $localpath; $safepath =~ s/'/'\\''/; if( $relocate || !$m->{'update'} || !-d "$localpath/$m->{'controldir'}" ) { system("rm -rf '$safepath'"); system(sprintf($m->{'init'}, $src, $safepath)) == 0 or return 1; } else { system("cd '$safepath'; $m->{'update'}") == 0 or return 1; } return 0; } sub get_feed($) { my $feed = shift; my $file = "./feeds/$feed.index"; clear_packages(); -f $file or do { print "Ignoring feed '$feed' - index missing\n"; return; }; parse_package_metadata($file) or return; return { %package }; } sub get_installed() { system("$mk -s prepare-tmpinfo"); clear_packages(); parse_package_metadata("./tmp/.packageinfo"); %installed = %package; } sub search_feed { my $feed = shift; my @substr = @_; my $display; return unless @substr > 0; get_feed($feed); foreach my $name (sort { lc($a) cmp lc($b) } keys %package) { my $pkg = $package{$name}; my $substr; my $pkgmatch = 1; foreach my $substr (@substr) { my $match; foreach my $key (qw(name title description)) { $pkg->{$key} and $substr and $pkg->{$key} =~ m/$substr/i and $match = 1; } $match or undef $pkgmatch; }; $pkgmatch and do { $display or do { print "Search results in feed '$feed':\n"; $display = 1; }; printf "\%-25s\t\%s\n", $pkg->{name}, $pkg->{title}; }; } return 0; } sub search { my %opts; getopt('r:', \%opts); foreach my $feed (@feeds) { search_feed($feed->[1], @ARGV) if (!defined($opts{r}) or $opts{r} eq $feed->[1]); } } sub list_feed { my $feed = shift; get_feed($feed); foreach my $name (sort { lc($a) cmp lc($b) } keys %package) { my $pkg = $package{$name}; if($pkg->{name}) { printf "\%-32s\t\%s\n", $pkg->{name}, $pkg->{title}; } } return 0; } sub list { my %opts; getopts('r:sh', \%opts); if ($opts{h}) { usage(); return 0; } if ($opts{s}) { foreach my $feed (@feeds) { printf "\%-32s\tURL: %s\n", $feed->[1], join(", ", @{$feed->[2]}); } return 0; } foreach my $feed (@feeds) { list_feed($feed->[1], @ARGV) if (!defined($opts{r}) or $opts{r} eq $feed->[1]); } return 0; } sub install_generic() { my $feed = shift; my $pkg = shift; my $path = $pkg->{makefile}; if($path) { $path =~ s/\/Makefile$//; -d "./package/feeds" or mkdir "./package/feeds"; -d "./package/feeds/$feed->[1]" or mkdir "./package/feeds/$feed->[1]"; system("ln -sf ../../../$path ./package/feeds/$feed->[1]/"); } else { warn "Package is not valid\n"; return 1; } return 0; } my %install_method = ( 'src-svn' => \&install_generic, 'src-cpy' => \&install_generic, 'src-link' => \&install_generic, 'src-git' => \&install_generic, 'src-bzr' => \&install_generic, 'src-hg' => \&install_generic, ); my %feed; sub lookup_package($$) { my $feed = shift; my $package = shift; foreach my $feed ($feed, @feeds) { next unless $feed->[1]; next unless $feed{$feed->[1]}; $feed{$feed->[1]}->{$package} and return $feed; } return; } sub install_package { my $feed = shift; my $name = shift; my $ret = 0; $feed = lookup_package($feed, $name); $feed or do { $installed{$name} and return 0; # TODO: check if it's already installed within ./package directory $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"; return 0; }; my $pkg = $feed{$feed->[1]}->{$name} or return 1; $pkg->{name} or do { $installed{$name} and return 0; # TODO: check if this is an alias package, maybe it's known by another name warn "WARNING: Package '$name' is not available in feed $feed->[1].\n"; return 0; }; my $src = $pkg->{src}; my $type = $feed->[0]; $src or $src = $name; # previously installed packages set the runtime package # newly installed packages set the source package $installed{$src} and return 0; # check previously installed packages $installed{$name} and return 0; $installed{$src} = 1; warn "Installing package '$src'\n"; $install_method{$type} or do { warn "Unknown installation method: '$type'\n"; return 1; }; &{$install_method{$type}}($feed, $pkg) == 0 or do { warn "failed.\n"; return 1; }; # install all dependencies foreach my $vpkg (@{$srcpackage{$src}}, $pkg) { foreach my $dep (@{$vpkg->{depends}}, @{$vpkg->{builddepends}}) { next if $dep =~ /@/; $dep =~ s/^\+//; $dep =~ s/^.+://; next unless $dep; install_package($feed, $dep) == 0 or $ret = 1; } } return $ret; } sub refresh_config { my $default = shift; # workaround for timestamp check system("rm -f tmp/.packageinfo"); # refresh the config if ($default) { system("$mk oldconfig CONFDEFAULT=\"$default\" Config.in >/dev/null 2>/dev/null"); } else { system("$mk defconfig Config.in >/dev/null 2>/dev/null"); } } sub install { my $name; my %opts; my $feed; my $ret = 0; getopts('ap:d:h', \%opts); if ($opts{h}) { usage(); return 0; } get_installed(); foreach my $f (@feeds) { # index all feeds $feed{$f->[1]} = get_feed($f->[1]); # look up the preferred feed $opts{p} and $f->[1] eq $opts{p} and $feed = $f; } if($opts{a}) { foreach my $f (@feeds) { if (!defined($opts{p}) or $opts{p} eq $f->[1]) { printf "Installing all packages from feed %s.\n", $f->[1]; get_feed($f->[1]); foreach my $name (sort { lc($a) cmp lc($b) } keys %package) { my $p = $package{$name}; if( $p->{name} ) { install_package($feed, $p->{name}) == 0 or $ret = 1; } } } } } else { while ($name = shift @ARGV) { install_package($feed, $name) == 0 or $ret = 1; } } # workaround for timestamp check # set the defaults if ($opts{d} and $opts{d} =~ /^[ymn]$/) { refresh_config($opts{d}); } return $ret; } sub uninstall { my %opts; my $name; my $uninstall; getopts('ah', \%opts); if ($opts{h}) { usage(); return 0; } if ($opts{a}) { system("rm -rvf ./package/feeds"); $uninstall = 1; } else { if($#ARGV == -1) { warn "WARNING: no package to uninstall\n"; return 0; } get_installed(); while ($name = shift @ARGV) { my $pkg = $installed{$name}; $pkg or do { warn "WARNING: $name not installed\n"; next; }; $pkg->{src} and $name = $pkg->{src}; warn "Uninstalling package '$name'\n"; system("rm -f ./package/feeds/*/$name"); $uninstall = 1; } } $uninstall and refresh_config(); return 0; } sub update_feed($$$$) { my $type=shift; my $name=shift; my $src=shift; my $perform_update=shift; my $force_relocate=update_location( $name, "@$src" ); if( $force_relocate ) { warn "Source of feed $name has changed, replacing copy\n"; } $update_method{$type} or do { warn "Unknown type '$type' in feed $name\n"; return 1; }; $perform_update and do { my $failed = 1; foreach my $feedsrc (@$src) { warn "Updating feed '$name' from '$feedsrc' ...\n"; next unless update_feed_via($type, $name, $feedsrc, $force_relocate) == 0; $failed = 0; last; } $failed and do { warn "failed.\n"; return 1; }; }; warn "Create index file './feeds/$name.index' \n"; update_index($name) == 0 or do { warn "failed.\n"; return 1; }; return 0; } sub update { my %opts; my $feed_name; my $perform_update=1; $ENV{SCAN_COOKIE} = $$; $ENV{KBUILD_VERBOSE} = 99; getopts('ahi', \%opts); if ($opts{h}) { usage(); return 0; } if ($opts{i}) { # don't update from (remote) repository # only re-create index information $perform_update=0; } -d "feeds" or do { mkdir "feeds" or die "Unable to create the feeds directory"; }; if ( ($#ARGV == -1) or $opts{a}) { foreach my $feed (@feeds) { my ($type, $name, $src) = @$feed; update_feed($type, $name, $src, $perform_update); } } else { while ($feed_name = shift @ARGV) { foreach my $feed (@feeds) { my ($type, $name, $src) = @$feed; if($feed_name ne $name) { next; } update_feed($type, $name, $src, $perform_update); } } } refresh_config(); return 0; } sub usage() { print < [options] Commands: list [options]: List feeds and their content Options: -s : List of feed names and their URL. -r : List packages of specified feed. install [options] : Install a package Options: -a : Install all packages from all feeds or from the specified feed using the -p option. -p : Prefer this feed when installing packages. -d : Set default for newly installed packages. search [options] : Search for a package Options: -r : Only search in this feed uninstall -a|: Uninstall a package Options: -a : Uninstalls all packages. update -a|: Update packages and lists of feeds in feeds.conf . Options: -a : Update all feeds listed within feeds.conf. Otherwise the spezified feeds will be updated. -i : Recreate the index only. No feed update from repository is performed. clean: Remove downloaded/generated files. EOF exit(1); } my %commands = ( 'list' => \&list, 'update' => \&update, 'install' => \&install, 'search' => \&search, 'uninstall' => \&uninstall, 'clean' => sub { system("rm -rf feeds"); } ); my $arg = shift @ARGV; $arg or usage(); parse_config; foreach my $cmd (keys %commands) { $arg eq $cmd and do { exit(&{$commands{$cmd}}()); }; } usage(); href='#n289'>289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423