add initial version of a package feeds management script
authorFelix Fietkau <nbd@openwrt.org>
Sun, 23 Sep 2007 02:39:01 +0000 (02:39 +0000)
committerFelix Fietkau <nbd@openwrt.org>
Sun, 23 Sep 2007 02:39:01 +0000 (02:39 +0000)
SVN-Revision: 8973

feeds.conf [new file with mode: 0644]
include/scan.mk
include/toplevel.mk
scripts/config/conf.c
scripts/feeds [new file with mode: 0755]
scripts/metadata.pl
scripts/metadata.pm [new file with mode: 0644]

diff --git a/feeds.conf b/feeds.conf
new file mode 100644 (file)
index 0000000..31dbe5f
--- /dev/null
@@ -0,0 +1,2 @@
+src-svn packages https://svn.openwrt.org/openwrt/packages
+src-svn xwrt http://svn.berlios.de/svnroot/repos/xwrt/trunk/package
index dfe80fabaadca38a0c23ba097ac9437f78bd2ea5..28f526aa335ed94b5c18a21efb2640d98f2c030f 100644 (file)
@@ -1,15 +1,15 @@
 include $(TOPDIR)/include/verbose.mk
 TMP_DIR:=$(TOPDIR)/tmp
 
-all: tmp/.$(SCAN_TARGET)
+all: $(TMP_DIR)/.$(SCAN_TARGET)
 
 include $(TOPDIR)/include/host.mk
 
 SCAN_TARGET ?= packageinfo
 SCAN_NAME ?= package
 SCAN_DIR ?= package
-TARGET_STAMP:=tmp/info/.files-$(SCAN_TARGET).stamp
-FILELIST:=tmp/info/.files-$(SCAN_TARGET)-$(SCAN_COOKIE)
+TARGET_STAMP:=$(TMP_DIR)/info/.files-$(SCAN_TARGET).stamp
+FILELIST:=$(TMP_DIR)/info/.files-$(SCAN_TARGET)-$(SCAN_COOKIE)
 
 ifeq ($(IS_TTY),1)
   define progress
@@ -22,8 +22,8 @@ else
 endif
 
 define PackageDir
-  tmp/.$(SCAN_TARGET): tmp/info/.$(SCAN_TARGET)-$(1)
-  tmp/info/.$(SCAN_TARGET)-$(1): $(SCAN_DIR)/$(2)/Makefile $(SCAN_STAMP) $(foreach DEP,$(DEPS_$(SCAN_DIR)/$(1)/Makefile) $(SCAN_DEPS),$(wildcard $(if $(filter /%,$(DEP)),$(DEP),$(SCAN_DIR)/$(1)/$(DEP))))
+  $(TMP_DIR)/.$(SCAN_TARGET): $(TMP_DIR)/info/.$(SCAN_TARGET)-$(1)
+  $(TMP_DIR)/info/.$(SCAN_TARGET)-$(1): $(SCAN_DIR)/$(2)/Makefile $(SCAN_STAMP) $(foreach DEP,$(DEPS_$(SCAN_DIR)/$(1)/Makefile) $(SCAN_DEPS),$(wildcard $(if $(filter /%,$(DEP)),$(DEP),$(SCAN_DIR)/$(1)/$(DEP))))
        { \
                $$(call progress,Collecting $(SCAN_NAME) info: $(SCAN_DIR)/$(2)) \
                echo Source-Makefile: $(SCAN_DIR)/$(2)/Makefile; \
@@ -33,10 +33,10 @@ define PackageDir
 endef
 
 $(FILELIST):
-       rm -f tmp/info/.files-$(SCAN_TARGET)-*
+       rm -f $(TMP_DIR)/info/.files-$(SCAN_TARGET)-*
        $(call FIND_L, $(SCAN_DIR)) $(SCAN_EXTRA) -mindepth 1 $(if $(SCAN_DEPTH),-maxdepth $(SCAN_DEPTH)) -name Makefile | xargs grep -HE 'call (Build/DefaultTargets|Build(Package|Target)|.+Package)' | sed -e 's#^$(SCAN_DIR)/##' -e 's#/Makefile:.*##' | uniq > $@
 
-tmp/info/.files-$(SCAN_TARGET).mk: $(FILELIST)
+$(TMP_DIR)/info/.files-$(SCAN_TARGET).mk: $(FILELIST)
        ( \
                cat $< | awk '{print "$(SCAN_DIR)/" $$0 "/Makefile" }' | xargs grep -HE '^ *SCAN_DEPS *= *' | awk -F: '{ gsub(/^.*DEPS *= */, "", $$2); print "DEPS_" $$1 "=" $$2 }'; \
                awk -v deps="$$DEPS" '{ \
@@ -47,7 +47,7 @@ tmp/info/.files-$(SCAN_TARGET).mk: $(FILELIST)
                true; \
        ) > $@
 
--include tmp/info/.files-$(SCAN_TARGET).mk
+-include $(TMP_DIR)/info/.files-$(SCAN_TARGET).mk
 
 $(TARGET_STAMP):
        ( \
@@ -60,9 +60,9 @@ $(TARGET_STAMP):
                } \
        )
 
-tmp/.$(SCAN_TARGET): $(TARGET_STAMP) $(SCAN_STAMP)
+$(TMP_DIR)/.$(SCAN_TARGET): $(TARGET_STAMP) $(SCAN_STAMP)
        $(call progress,Collecting $(SCAN_NAME) info: merging...)
-       cat $(FILELIST) | awk '{gsub(/\//, "_", $$0);print "tmp/info/.$(SCAN_TARGET)-" $$0}' | xargs cat > $@
+       cat $(FILELIST) | awk '{gsub(/\//, "_", $$0);print "$(TMP_DIR)/info/.$(SCAN_TARGET)-" $$0}' | xargs cat > $@
        $(call progress,Collecting $(SCAN_NAME) info: done)
        echo
 
index 0a8bdedc3e9294a20206a05b9eb7a8b76ea895ec..a9ca3a2806ff9955219d3797794547231211c485 100644 (file)
@@ -30,15 +30,17 @@ endif
 SCAN_COOKIE?=$(shell echo $$$$)
 export SCAN_COOKIE
 
+prepare-mk: FORCE ;
+
 prepare-tmpinfo: FORCE
        mkdir -p tmp/info
        +$(NO_TRACE_MAKE) -s -f include/scan.mk SCAN_TARGET="packageinfo" SCAN_DIR="package" SCAN_NAME="package" SCAN_DEPS="$(TOPDIR)/include/package*.mk" SCAN_DEPTH=4 SCAN_EXTRA=""
        +$(NO_TRACE_MAKE) -s -f include/scan.mk SCAN_TARGET="targetinfo" SCAN_DIR="target/linux" SCAN_NAME="target" SCAN_DEPS="profiles/*.mk $(TOPDIR)/include/kernel*.mk" SCAN_DEPTH=2 SCAN_EXTRA="" SCAN_MAKEOPTS="TARGET_BUILD=1"
        for type in package target; do \
                f=tmp/.$${type}info; t=tmp/.config-$${type}.in; \
-               [ "$$t" -nt "$$f" ] || ./scripts/metadata.pl $${type}_config "$$f" > "$$t" || { rm -f "$$t"; echo "Failed to build $$t"; false; break; }; \
+               [ "$$t" -nt "$$f" ] || ./scripts/metadata.pl $${type}_config "$$f" > "$$t" || { rm -f "$$t"; echo "Failed to build $$t"; false; break; }; \
        done
-       ./scripts/metadata.pl package_mk tmp/.packageinfo > tmp/.packagedeps || { rm -f tmp/.packagedeps; false; }
+       ./scripts/metadata.pl package_mk tmp/.packageinfo > tmp/.packagedeps || { rm -f tmp/.packagedeps; false; }
        touch $(TOPDIR)/tmp/.build
 
 .config: ./scripts/config/conf prepare-tmpinfo
@@ -64,7 +66,7 @@ defconfig: scripts/config/conf prepare-tmpinfo FORCE
        $< -D .config Config.in
 
 oldconfig: scripts/config/conf prepare-tmpinfo FORCE
-       $< -o Config.in
+       $< -$(if $(CONFDEFAULT),$(CONFDEFAULT),o) Config.in
 
 menuconfig: scripts/config/mconf prepare-tmpinfo FORCE
        if [ \! -f .config -a -e $(HOME)/.openwrt/defconfig ]; then \
index cb2093691e341db2be6149650a281d419cba8d9f..6589aee4515d86cfba4e339b0942979a174b61fc 100644 (file)
@@ -579,28 +579,11 @@ int main(int ac, char **av)
                }
        case ask_all:
        case ask_new:
-               conf_read(NULL);
-               break;
        case set_no:
        case set_mod:
        case set_yes:
        case set_random:
-               name = getenv("KCONFIG_ALLCONFIG");
-               if (name && !stat(name, &tmpstat)) {
-                       conf_read_simple(name);
-                       break;
-               }
-               switch (input_mode) {
-               case set_no:     name = "allno.config"; break;
-               case set_mod:    name = "allmod.config"; break;
-               case set_yes:    name = "allyes.config"; break;
-               case set_random: name = "allrandom.config"; break;
-               default: break;
-               }
-               if (!stat(name, &tmpstat))
-                       conf_read_simple(name);
-               else if (!stat("all.config", &tmpstat))
-                       conf_read_simple("all.config");
+               conf_read(NULL);
                break;
        default:
                break;
diff --git a/scripts/feeds b/scripts/feeds
new file mode 100755 (executable)
index 0000000..d62e8e7
--- /dev/null
@@ -0,0 +1,322 @@
+#!/usr/bin/perl
+use Getopt::Std;
+use FindBin;
+use Cwd;
+use lib "$FindBin::Bin";
+use metadata;
+use warnings;
+use strict;
+
+chdir "$FindBin::Bin/..";
+$ENV{TOPDIR}=getcwd();
+
+my @feeds;
+my %build_packages;
+my %installed;
+
+sub parse_config() {
+       my $line = 0;
+       my %name;
+
+       open FEEDS, "feeds.conf";
+       while (<FEEDS>) {
+               chomp;
+               s/#.+$//;
+               next unless /\S/;
+               my @line = split /\s+/, $_, 3;
+               $line++;
+
+               my $valid = 1;
+               $line[0] =~ /^src-\w+$/ or $valid = 0;
+               $line[1] =~ /^\w+$/ or $valid = 0;
+               $line[2] =~ /\s/ and $valid = 0;
+               $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];
+       }
+       close FEEDS;
+}
+
+sub update_svn($$) {
+       my $name = shift;
+       my $src = shift;
+
+       system("svn co $src ./feeds/$name") == 0 or return 1;
+       -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("make -s prepare-mk TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+       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\"");
+       system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
+
+       return 0;
+}
+
+sub get_feed($) {
+       my $feed = shift;
+
+       clear_packages();
+       parse_package_metadata("./feeds/$feed.index") or return;
+       return { %package };
+}
+
+sub get_installed() {
+       system("make -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)) {
+                               $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 install_svn() {
+       my $feed = shift;
+       my $pkg = shift;
+       my $path = $pkg->{makefile};
+       $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]/");
+
+       return 0;
+}
+
+my %install_method = (
+       'src-svn' => \&install_svn
+);
+
+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;
+               warn "WARNING: Package '$name' is not available.\n";
+               return 1;
+       };
+
+       my $pkg = $feed{$feed->[1]}->{$name} or return 1;
+       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;
+
+       # install all dependencies
+       foreach my $dep (@{$pkg->{depends}}) {
+               next if $dep =~ /@/;
+               $dep =~ s/^\+//;
+               install_package($feed, $dep) == 0 or $ret = 1;
+       }
+
+       # 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;
+       };
+
+       return $ret;
+}
+
+sub refresh_config {
+       my $default = shift;
+       $default or $default = "o";
+
+       # workaround for timestamp check
+       system("rm -f tmp/.packageinfo");
+
+       # refresh the config 
+       system("make oldconfig CONFDEFAULT=\"$default\" Config.in >/dev/null 2>/dev/null");
+}
+
+sub install {
+       my $name;
+       my %opts;
+       my $feed;
+       my $ret = 0;
+       
+       getopt('p:d:', \%opts);
+       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;
+       }
+
+       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 $name;
+       my $uninstall;
+
+       if ($ARGV[0] eq '-a') {
+               system("rm -rf ./package/feeds");
+               $uninstall = 1;
+       } else {
+               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 usage() {
+       print <<EOF;
+Usage: $0 <command> [options]
+
+Commands:
+       install [options] <package>: Install a package
+       Options:
+           -p <feedname>: Prefer this feed when installing packages
+           -d <y|m|n>:    Set default for newly installed packages
+
+       search [options] <substring>: Search for a package
+       Options:
+           -r <feedname>: Only search in this feed
+
+       uninstall -a|<package>: Uninstall a package
+           -a uninstalls all packages
+
+       update: Update packages and lists of feeds in feeds.list
+       clean: Remove downloaded/generated files
+
+EOF
+       exit(1);
+}
+
+my %update_method = (
+       'src-svn' => \&update_svn
+);
+
+my %commands = (
+       'update' => sub {
+               -d "feeds" or do {
+                       mkdir "feeds" or die "Unable to create the feeds directory";
+               };
+               $ENV{SCAN_COOKIE} = $$;
+               $ENV{KBUILD_VERBOSE} = 99;
+               foreach my $feed (@feeds) {
+                       my ($type, $name, $src) = @$feed;
+                       $update_method{$type} or do {
+                               warn "Unknown type '$type' in feed $name\n";
+                               next;
+                       };
+                       warn "Updating feed '$name'...\n";
+                       &{$update_method{$type}}($name, $src) == 0 or do {
+                               warn "failed.\n";
+                               return 1;
+                       };
+               }
+               return 0;
+       },
+       '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();
index 03041f97c4c3ccacfc7aea48bf5afddb9c1969c3..f3d6e9df2c7e79cac13e44c7f4e173c97f6bafd0 100755 (executable)
@@ -1,23 +1,10 @@
 #!/usr/bin/perl
+use FindBin;
+use lib "$FindBin::Bin";
 use strict;
-my %preconfig;
-my %package;
-my %srcpackage;
-my %category;
-my %subdir;
-my %board;
-
-sub get_multiline {
-       my $prefix = shift;
-       my $str;
-       while (<>) {
-               last if /^@@/;
-               s/^\s*//g;
-               $str .= (($_ and $prefix) ? $prefix . $_ : $_);
-       }
+use metadata;
 
-       return $str;
-}
+my %board;
 
 sub confstr($) {
        my $conf = shift;
@@ -26,8 +13,13 @@ sub confstr($) {
 }
 
 sub parse_target_metadata() {
-       my ($target, @target, $profile);        
-       while (<>) {
+       my $file = shift @ARGV;
+       my ($target, @target, $profile);
+       open FILE, "<$file" or do {
+               warn "Can't open file '$file': $!\n";
+               return;
+       };
+       while (<FILE>) {
                chomp;
                /^Target:\s*(.+)\s*$/ and do {
                        $target = {
@@ -46,7 +38,7 @@ sub parse_target_metadata() {
                /^Target-Path:\s*(.+)\s*$/ and $target->{path} = $1;
                /^Target-Arch:\s*(.+)\s*$/ and $target->{arch} = $1;
                /^Target-Features:\s*(.+)\s*$/ and $target->{features} = [ split(/\s+/, $1) ];
-               /^Target-Description:/ and $target->{desc} = get_multiline();
+               /^Target-Description:/ and $target->{desc} = get_multiline(*FILE);
                /^Linux-Version:\s*(.+)\s*$/ and $target->{version} = $1;
                /^Linux-Release:\s*(.+)\s*$/ and $target->{release} = $1;
                /^Linux-Kernel-Arch:\s*(.+)\s*$/ and $target->{karch} = $1;
@@ -61,10 +53,11 @@ sub parse_target_metadata() {
                };
                /^Target-Profile-Name:\s*(.+)\s*$/ and $profile->{name} = $1;
                /^Target-Profile-Packages:\s*(.*)\s*$/ and $profile->{packages} = [ split(/\s+/, $1) ];
-               /^Target-Profile-Description:\s*(.*)\s*/ and $profile->{desc} = get_multiline();
-               /^Target-Profile-Config:/ and $profile->{config} = get_multiline("\t");
+               /^Target-Profile-Description:\s*(.*)\s*/ and $profile->{desc} = get_multiline(*FILE);
+               /^Target-Profile-Config:/ and $profile->{config} = get_multiline(*FILE, "\t");
                /^Target-Profile-Kconfig:/ and $profile->{kconfig} = 1;
        }
+       close FILE;
        foreach my $target (@target) {
                @{$target->{profiles}} > 0 or $target->{profiles} = [
                        {
@@ -77,78 +70,6 @@ sub parse_target_metadata() {
        return @target;
 }
 
-sub parse_package_metadata() {
-       my $pkg;
-       my $makefile;
-       my $preconfig;
-       my $subdir;
-       my $src;
-       while (<>) {
-               chomp;
-               /^Source-Makefile: \s*((.+\/)([^\/]+)\/Makefile)\s*$/ and do {
-                       $makefile = $1;
-                       $subdir = $2;
-                       $src = $3;
-                       $subdir =~ s/^package\///;
-                       $subdir{$src} = $subdir;
-                       $srcpackage{$src} = [];
-                       undef $pkg;
-               };
-               /^Package:\s*(.+?)\s*$/ and do {
-                       $pkg = {};
-                       $pkg->{src} = $src;
-                       $pkg->{makefile} = $makefile;
-                       $pkg->{name} = $1;
-                       $pkg->{default} = "m if ALL";
-                       $pkg->{depends} = [];
-                       $pkg->{builddepends} = [];
-                       $pkg->{subdir} = $subdir;
-                       $package{$1} = $pkg;
-                       push @{$srcpackage{$src}}, $pkg;
-               };
-               /^Version: \s*(.+)\s*$/ and $pkg->{version} = $1;
-               /^Title: \s*(.+)\s*$/ and $pkg->{title} = $1;
-               /^Menu: \s*(.+)\s*$/ and $pkg->{menu} = $1;
-               /^Submenu: \s*(.+)\s*$/ and $pkg->{submenu} = $1;
-               /^Submenu-Depends: \s*(.+)\s*$/ and $pkg->{submenudep} = $1;
-               /^Default: \s*(.+)\s*$/ and $pkg->{default} = $1;
-               /^Provides: \s*(.+)\s*$/ and do {
-                       my @vpkg = split /\s+/, $1;
-                       foreach my $vpkg (@vpkg) {
-                               $package{$vpkg} or $package{$vpkg} = { vdepends => [] };
-                               push @{$package{$vpkg}->{vdepends}}, $pkg->{name};
-                       }
-               };
-               /^Depends: \s*(.+)\s*$/ and $pkg->{depends} = [ split /\s+/, $1 ];
-               /^Build-Depends: \s*(.+)\s*$/ and $pkg->{builddepends} = [ split /\s+/, $1 ];
-               /^Category: \s*(.+)\s*$/ and do {
-                       $pkg->{category} = $1;
-                       defined $category{$1} or $category{$1} = {};
-                       defined $category{$1}->{$src} or $category{$1}->{$src} = [];
-                       push @{$category{$1}->{$src}}, $pkg;
-               };
-               /^Description: \s*(.*)\s*$/ and $pkg->{description} = "\t\t $1\n". get_multiline("\t\t ");
-               /^Config: \s*(.*)\s*$/ and $pkg->{config} = "$1\n".get_multiline();
-               /^Prereq-Check:/ and $pkg->{prereq} = 1;
-               /^Preconfig:\s*(.+)\s*$/ and do {
-                       my $pkgname = $pkg->{name};
-                       $preconfig{$pkgname} or $preconfig{$pkgname} = {};
-                       if (exists $preconfig{$pkgname}->{$1}) {
-                               $preconfig = $preconfig{$pkgname}->{$1};
-                       } else {
-                               $preconfig = {
-                                       id => $1
-                               };
-                               $preconfig{$pkgname}->{$1} = $preconfig;
-                       }
-               };
-               /^Preconfig-Type:\s*(.*?)\s*$/ and $preconfig->{type} = $1;
-               /^Preconfig-Label:\s*(.*?)\s*$/ and $preconfig->{label} = $1;
-               /^Preconfig-Default:\s*(.*?)\s*$/ and $preconfig->{default} = $1;
-       }
-       return %category;
-}
-
 sub gen_kconfig_overrides() {
        my %config;
        my %kconfig;
@@ -318,20 +239,31 @@ EOF
        print "endchoice\n";
 }
 
-
-sub find_package_dep($$) {
+my %dep_check;
+sub __find_package_dep($$) {
        my $pkg = shift;
        my $name = shift;
        my $deps = ($pkg->{vdepends} or $pkg->{depends});
 
        return 0 unless defined $deps;
        foreach my $dep (@{$deps}) {
+               next if $dep_check{$dep};
+               $dep_check{$dep} = 1;
                return 1 if $dep eq $name;
-               return 1 if ($package{$dep} and (find_package_dep($package{$dep},$name) == 1));
+               return 1 if ($package{$dep} and (__find_package_dep($package{$dep},$name) == 1));
        }
        return 0;
 }
 
+# wrapper to avoid infinite recursion
+sub find_package_dep($$) {
+       my $pkg = shift;
+       my $name = shift;
+
+       %dep_check = ();
+       return __find_package_dep($pkg, $name);
+}
+
 sub package_depends($$) {
        my $a = shift;
        my $b = shift;
@@ -452,7 +384,7 @@ sub print_package_config_category($) {
 }
 
 sub gen_package_config() {
-       parse_package_metadata();
+       parse_package_metadata($ARGV[0]) or exit 1;
        print "menuconfig UCI_PRECONFIG\n\tbool \"Image configuration\"\n";
        foreach my $preconfig (keys %preconfig) {
                foreach my $cfg (keys %{$preconfig{$preconfig}}) {
@@ -478,7 +410,7 @@ sub gen_package_mk() {
        my %dep;
        my $line;
 
-       parse_package_metadata();
+       parse_package_metadata($ARGV[0]) or exit 1;
        foreach my $name (sort {uc($a) cmp uc($b)} keys %package) {
                my $config;
                my $pkg = $package{$name};
diff --git a/scripts/metadata.pm b/scripts/metadata.pm
new file mode 100644 (file)
index 0000000..f904462
--- /dev/null
@@ -0,0 +1,113 @@
+package metadata;
+use base 'Exporter';
+use strict;
+use warnings;
+our @EXPORT = qw(%package %srcpackage %category %subdir %preconfig clear_packages parse_package_metadata get_multiline);
+
+our %package;
+our %preconfig;
+our %srcpackage;
+our %category;
+our %subdir;
+
+sub get_multiline {
+       my $fh = shift;
+       my $prefix = shift;
+       my $str;
+       while (<$fh>) {
+               last if /^@@/;
+               s/^\s*//g;
+               $str .= (($_ and $prefix) ? $prefix . $_ : $_);
+       }
+
+       return $str ? $str : "";
+}
+
+sub clear_packages() {
+       %subdir = ();
+       %preconfig = ();
+       %package = ();
+       %srcpackage = ();
+       %category = ();
+}
+
+sub parse_package_metadata($) {
+       my $file = shift;
+       my $pkg;
+       my $makefile;
+       my $preconfig;
+       my $subdir;
+       my $src;
+
+       open FILE, "<$file" or do {
+               warn "Cannot open '$file': $!\n";
+               return undef;
+       };
+       while (<FILE>) {
+               chomp;
+               /^Source-Makefile: \s*((.+\/)([^\/]+)\/Makefile)\s*$/ and do {
+                       $makefile = $1;
+                       $subdir = $2;
+                       $src = $3;
+                       $subdir =~ s/^package\///;
+                       $subdir{$src} = $subdir;
+                       $srcpackage{$src} = [];
+                       undef $pkg;
+               };
+               /^Package:\s*(.+?)\s*$/ and do {
+                       $pkg = {};
+                       $pkg->{src} = $src;
+                       $pkg->{makefile} = $makefile;
+                       $pkg->{name} = $1;
+                       $pkg->{default} = "m if ALL";
+                       $pkg->{depends} = [];
+                       $pkg->{builddepends} = [];
+                       $pkg->{subdir} = $subdir;
+                       $package{$1} = $pkg;
+                       push @{$srcpackage{$src}}, $pkg;
+               };
+               /^Version: \s*(.+)\s*$/ and $pkg->{version} = $1;
+               /^Title: \s*(.+)\s*$/ and $pkg->{title} = $1;
+               /^Menu: \s*(.+)\s*$/ and $pkg->{menu} = $1;
+               /^Submenu: \s*(.+)\s*$/ and $pkg->{submenu} = $1;
+               /^Submenu-Depends: \s*(.+)\s*$/ and $pkg->{submenudep} = $1;
+               /^Default: \s*(.+)\s*$/ and $pkg->{default} = $1;
+               /^Provides: \s*(.+)\s*$/ and do {
+                       my @vpkg = split /\s+/, $1;
+                       foreach my $vpkg (@vpkg) {
+                               $package{$vpkg} or $package{$vpkg} = { vdepends => [] };
+                               push @{$package{$vpkg}->{vdepends}}, $pkg->{name};
+                       }
+               };
+               /^Depends: \s*(.+)\s*$/ and $pkg->{depends} = [ split /\s+/, $1 ];
+               /^Build-Depends: \s*(.+)\s*$/ and $pkg->{builddepends} = [ split /\s+/, $1 ];
+               /^Category: \s*(.+)\s*$/ and do {
+                       $pkg->{category} = $1;
+                       defined $category{$1} or $category{$1} = {};
+                       defined $category{$1}->{$src} or $category{$1}->{$src} = [];
+                       push @{$category{$1}->{$src}}, $pkg;
+               };
+               /^Description: \s*(.*)\s*$/ and $pkg->{description} = "\t\t $1\n". get_multiline(*FILE, "\t\t ");
+               /^Config: \s*(.*)\s*$/ and $pkg->{config} = "$1\n".get_multiline(*FILE);
+               /^Prereq-Check:/ and $pkg->{prereq} = 1;
+               /^Preconfig:\s*(.+)\s*$/ and do {
+                       my $pkgname = $pkg->{name};
+                       $preconfig{$pkgname} or $preconfig{$pkgname} = {};
+                       if (exists $preconfig{$pkgname}->{$1}) {
+                               $preconfig = $preconfig{$pkgname}->{$1};
+                       } else {
+                               $preconfig = {
+                                       id => $1
+                               };
+                               $preconfig{$pkgname}->{$1} = $preconfig;
+                       }
+               };
+               /^Preconfig-Type:\s*(.*?)\s*$/ and $preconfig->{type} = $1;
+               /^Preconfig-Label:\s*(.*?)\s*$/ and $preconfig->{label} = $1;
+               /^Preconfig-Default:\s*(.*?)\s*$/ and $preconfig->{default} = $1;
+       }
+       close FILE;
+       return %category;
+}
+
+1;