diff --git a/feeds.conf b/feeds.conf
new file mode 100644
index 0000000000000000000000000000000000000000..31dbe5f0f35e6a0b394ce6eaa3bb93f761866efe
--- /dev/null
+++ b/feeds.conf
@@ -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
diff --git a/include/scan.mk b/include/scan.mk
index dfe80fabaadca38a0c23ba097ac9437f78bd2ea5..28f526aa335ed94b5c18a21efb2640d98f2c030f 100644
--- a/include/scan.mk
+++ b/include/scan.mk
@@ -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
 
diff --git a/include/toplevel.mk b/include/toplevel.mk
index 0a8bdedc3e9294a20206a05b9eb7a8b76ea895ec..a9ca3a2806ff9955219d3797794547231211c485 100644
--- a/include/toplevel.mk
+++ b/include/toplevel.mk
@@ -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 \
diff --git a/scripts/config/conf.c b/scripts/config/conf.c
index cb2093691e341db2be6149650a281d419cba8d9f..6589aee4515d86cfba4e339b0942979a174b61fc 100644
--- a/scripts/config/conf.c
+++ b/scripts/config/conf.c
@@ -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
index 0000000000000000000000000000000000000000..d62e8e79dd487d8733329a7c5689510e6ff56e6b
--- /dev/null
+++ b/scripts/feeds
@@ -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();
diff --git a/scripts/metadata.pl b/scripts/metadata.pl
index 03041f97c4c3ccacfc7aea48bf5afddb9c1969c3..f3d6e9df2c7e79cac13e44c7f4e173c97f6bafd0 100755
--- a/scripts/metadata.pl
+++ b/scripts/metadata.pl
@@ -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
index 0000000000000000000000000000000000000000..f90446298af1841f741ed14caeacaa0078b45ded
--- /dev/null
+++ b/scripts/metadata.pm
@@ -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;