| #!/usr/bin/env 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(); | 
 | chdir $ENV{TOPDIR}; | 
 | $ENV{GIT_CONFIG_PARAMETERS}="'core.autocrlf=false'"; | 
 | $ENV{GREP_OPTIONS}=""; | 
 |  | 
 | my $mk=`command -v gmake 2>/dev/null`;	# select the right 'make' program | 
 | chomp($mk);		# trim trailing newline | 
 | $mk or $mk = "make";	# default to 'make' | 
 |  | 
 | # check version of make | 
 | my @mkver = split /\s+/, `$mk -v`, 4; | 
 | my $valid_mk = 1; | 
 | $mkver[0] =~ /^GNU/ or $valid_mk = 0; | 
 | $mkver[1] =~ /^Make/ or $valid_mk = 0; | 
 |  | 
 | my ($mkv1, $mkv2) = split /\./, $mkver[2]; | 
 | ($mkv1 >= 4 || ($mkv1 == 3 && $mkv2 >= 81)) or $valid_mk = 0; | 
 |  | 
 | $valid_mk or die "Unsupported version of make found: $mk\n"; | 
 |  | 
 | my @feeds; | 
 | my %build_packages; | 
 | my %installed; | 
 | my %installed_pkg; | 
 | my %installed_targets; | 
 | my %feed_cache; | 
 |  | 
 | my $feed_package = {}; | 
 | my $feed_src = {}; | 
 | my $feed_target = {}; | 
 | my $feed_vpackage = {}; | 
 |  | 
 | sub parse_file($$); | 
 |  | 
 | sub parse_file($$) { | 
 | 	my ($fname, $existing) = @_; | 
 | 	my $line = 0; | 
 | 	my $fh; | 
 |  | 
 | 	open $fh, $fname or return undef; | 
 | 	while (<$fh>) { | 
 | 		chomp; | 
 | 		s/#.+$//; | 
 | 		$line++; | 
 | 		next unless /\S/; | 
 |  | 
 | 		my ($type, $flags, $name, $urls) = m!^src-([\w\-]+)((?:\s+--\w+(?:=\S+)?)*)\s+(\w+)(?:\s+(\S.*))?$!; | 
 | 		unless ($type && $name) { | 
 | 			die "Syntax error in $fname, line $line\n"; | 
 | 		} | 
 |  | 
 | 		if ($existing->{$name}++) { | 
 | 			die "Duplicate feed name '$name' in '$fname' line: $line\n"; | 
 | 		} | 
 |  | 
 | 		my @src = defined($urls) ? split /\s+/, $urls : (); | 
 | 		push @src, '' if @src == 0; | 
 |  | 
 | 		my %flags; | 
 | 		if (defined $flags) { | 
 | 			while ($flags =~ m!\s+--(\w+)(?:=(\S+))?!g) { | 
 | 				$flags{$1} = defined($2) ? $2 : 1; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if ($type eq "include") { | 
 | 			parse_file($urls, $existing) or | 
 | 			    die "Unable to open included file '$urls'"; | 
 | 			next; | 
 | 		} | 
 |  | 
 | 		push @feeds, ["src-$type", $name, \@src, \%flags]; | 
 | 	} | 
 | 	close $fh; | 
 | 	return 1; | 
 | } | 
 |  | 
 | sub parse_config() { | 
 | 	my %name; | 
 | 	parse_file("feeds.conf", \%name) or | 
 | 	    parse_file("feeds.conf.default", \%name)  or | 
 | 	    die "Unable to open feeds configuration"; | 
 | } | 
 |  | 
 | 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 OPENWRT_BUILD= 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_DEPTH=5 SCAN_EXTRA=\"\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\""); | 
 | 	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"targetinfo\" SCAN_DIR=\"feeds/$name\" SCAN_NAME=\"target\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" SCAN_MAKEOPTS=\"TARGET_BUILD=1\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\""); | 
 | 	system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index"); | 
 | 	system("ln -sf $name.tmp/.targetinfo ./feeds/$name.targetindex"); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | my %update_method = ( | 
 | 	'src-svn' => { | 
 | 		'init'		=> "svn checkout '%s' '%s'", | 
 | 		'update'	=> "svn update", | 
 | 		'controldir'	=> ".svn", | 
 | 		'revision'	=> "svn info | grep 'Revision' | cut -d ' ' -f 2 | tr -d '\n'"}, | 
 | 	'src-cpy' => { | 
 | 		'init'		=> "cp -Rf '%s' '%s'", | 
 | 		'update'	=> "", | 
 | 		'revision'	=> "echo -n 'local'"}, | 
 | 	'src-link' => { | 
 | 		'init'		=> "ln -s '%s' '%s'", | 
 | 		'update'	=> "", | 
 | 		'revision'	=> "echo -n 'local'"}, | 
 | 	'src-dummy' => { | 
 | 		'init'		=> "true '%s' && mkdir '%s'", | 
 | 		'update'	=> "", | 
 | 		'revision'	=> "echo -n 'dummy'"}, | 
 | 	'src-git' => { | 
 | 		'init'          => "git clone --depth 1 '%s' '%s'", | 
 | 		'init_branch'   => "git clone --depth 1 --branch '%s' '%s' '%s'", | 
 | 		'init_commit'   => "git clone --depth 1 '%s' '%s' && cd '%s' && git fetch --depth=1 origin '%s' && git -c advice.detachedHead=false checkout '%s' && cd -", | 
 | 		'update'	=> "git pull --ff-only", | 
 | 		'update_rebase'	=> "git pull --rebase=merges", | 
 | 		'update_stash'	=> "git pull --rebase=merges --autostash", | 
 | 		'update_force'	=> "git pull --ff-only || (git reset --hard HEAD; git pull --ff-only; exit 1)", | 
 | 		'post_update'	=> "git submodule update --init --recursive --depth 1", | 
 | 		'controldir'	=> ".git", | 
 | 		'revision'	=> "git rev-parse HEAD | tr -d '\n'"}, | 
 | 	'src-git-full' => { | 
 | 		'init'          => "git clone '%s' '%s'", | 
 | 		'init_branch'   => "git clone --branch '%s' '%s' '%s'", | 
 | 		'init_commit'   => "git clone '%s' '%s' && cd '%s' && git checkout -b '%s' '%s' && cd -", | 
 | 		'update'	=> "git pull --ff-only", | 
 | 		'update_rebase'	=> "git pull --rebase=merges", | 
 | 		'update_stash'	=> "git pull --rebase=merges --autostash", | 
 | 		'update_force'	=> "git pull --ff-only || (git reset --hard HEAD; git pull --ff-only; exit 1)", | 
 | 		'post_update'	=> "git submodule update --init --recursive", | 
 | 		'controldir'	=> ".git", | 
 | 		'revision'	=> "git rev-parse HEAD | tr -d '\n'"}, | 
 | 	'src-gitsvn' => { | 
 | 		'init'	=> "git svn clone -r HEAD '%s' '%s'", | 
 | 		'update'	=> "git svn rebase", | 
 | 		'controldir'	=> ".git", | 
 | 		'revision'	=> "git rev-parse HEAD | tr -d '\n'"}, | 
 | 	'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-darcs' => { | 
 | 		'init'    => "darcs get '%s' '%s'", | 
 | 		'update'  => "darcs pull -a", | 
 | 		'controldir' => "_darcs"}, | 
 | ); | 
 |  | 
 | # 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 $force = shift; | 
 | 	my $rebase = shift; | 
 | 	my $stash = shift; | 
 |  | 
 | 	my $m = $update_method{$type}; | 
 | 	my $localpath = "./feeds/$name"; | 
 | 	my $safepath = $localpath; | 
 | 	$safepath =~ s/'/'\\''/; | 
 | 	my ($base_branch, $branch) = split(/;/, $src, 2); | 
 | 	my ($base_commit, $commit) = split(/\^/, $src, 2); | 
 |  | 
 | 	if( $relocate || !$m->{'update'} || !-d "$localpath/$m->{'controldir'}" ) { | 
 | 		system("rm -rf '$safepath'"); | 
 | 		if ($m->{'init_branch'} and $branch) { | 
 | 			system(sprintf($m->{'init_branch'}, $branch, $base_branch, $safepath)) == 0 or return 1; | 
 | 		} elsif ($m->{'init_commit'} and $commit) { | 
 | 			system(sprintf($m->{'init_commit'}, $base_commit, $safepath, $safepath, $commit, $commit)) == 0 or return 1; | 
 | 		} else { | 
 | 			system(sprintf($m->{'init'}, $src, $safepath)) == 0 or return 1; | 
 | 		} | 
 | 	} elsif ($m->{'init_commit'} and $commit) { | 
 | 		# in case git hash has been provided don't update the feed | 
 | 	} else { | 
 | 		my $update_cmd = $m->{'update'}; | 
 | 		if ($force && exists $m->{'update_force'}) { | 
 | 			$update_cmd = $m->{'update_force'}; | 
 | 		} | 
 | 		if ($rebase && exists $m->{'update_rebase'}) { | 
 | 			$update_cmd = $m->{'update_rebase'}; | 
 | 		} | 
 | 		if ($stash && exists $m->{'update_stash'}) { | 
 | 			$update_cmd = $m->{'update_stash'}; | 
 | 		} | 
 | 		system("cd '$safepath'; $update_cmd") == 0 or return 1; | 
 | 	} | 
 | 	if ($m->{'post_update'}) { | 
 | 		my $cmd = $m->{'post_update'}; | 
 | 		system("cd '$safepath'; $cmd") == 0 or return 1; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | sub get_targets($) { | 
 | 	my $file = shift; | 
 | 	my @target = parse_target_metadata($file); | 
 | 	my %target; | 
 | 	foreach my $target (@target) { | 
 | 		$target{$target->{id}} = $target; | 
 | 	} | 
 | 	return %target | 
 | } | 
 |  | 
 | sub get_feed($) { | 
 | 	my $feed = shift; | 
 |  | 
 | 	if (!defined($feed_cache{$feed})) { | 
 | 		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; | 
 | 		my %target = get_targets("./feeds/$feed.targetindex"); | 
 |  | 
 | 		$feed_cache{$feed} = [ { %package }, { %srcpackage }, { %target }, { %vpackage } ]; | 
 | 	} | 
 |  | 
 | 	$feed_package = $feed_cache{$feed}->[0]; | 
 | 	$feed_src = $feed_cache{$feed}->[1]; | 
 | 	$feed_target = $feed_cache{$feed}->[2]; | 
 | 	$feed_vpackage = $feed_cache{$feed}->[3]; | 
 | } | 
 |  | 
 | sub get_installed() { | 
 | 	system("$mk -s prepare-tmpinfo OPENWRT_BUILD="); | 
 | 	clear_packages(); | 
 | 	parse_package_metadata("./tmp/.packageinfo"); | 
 | 	%installed_pkg = %vpackage; | 
 | 	%installed = %srcpackage; | 
 | 	%installed_targets = get_targets("./tmp/.targetinfo"); | 
 | } | 
 |  | 
 | 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 %$feed_package) { | 
 | 		my $pkg = $feed_package->{$name}; | 
 | 		my $substr; | 
 | 		my $pkgmatch = 1; | 
 |  | 
 | 		foreach my $substr (@substr) { | 
 | 			my $match; | 
 | 			foreach my $key (qw(name title description src)) { | 
 | 				$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}; | 
 | 		}; | 
 | 	} | 
 |  | 
 | 	foreach my $name (sort { lc($a) cmp lc($b) } keys %$feed_target) { | 
 | 		my $target = $feed_target->{$name}; | 
 | 		my $targetmatch = 1; | 
 |  | 
 | 		foreach my $substr (@substr) { | 
 | 			my $match; | 
 | 			foreach my $key (qw(id name description)) { | 
 | 				$target->{$key} and $substr and $target->{$key} =~ m/$substr/i and $match = 1; | 
 | 			} | 
 | 			$match or undef $targetmatch; | 
 | 		}; | 
 | 		$targetmatch and do { | 
 | 			$display or do { | 
 | 				print "Search results in feed '$feed':\n"; | 
 | 				$display = 1; | 
 | 			}; | 
 | 			printf "TARGET: \%-17s\t\%s\n", $target->{id}, $target->{name}; | 
 | 		}; | 
 | 	} | 
 | 	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 %$feed_package) { | 
 | 		my $pkg = $feed_package->{$name}; | 
 | 		if($pkg->{name}) { | 
 | 			printf "\%-32s\t\%s\n", $pkg->{name}, $pkg->{title}; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	foreach my $name (sort { lc($a) cmp lc($b) } keys %$feed_target) { | 
 | 		my $target = $feed_target->{$name}; | 
 | 		if($target->{name}) { | 
 | 			printf "TARGET: \%-24s\t\%s\n", $target->{id}, $target->{name}; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | sub list { | 
 | 	my %opts; | 
 |  | 
 | 	getopts('r:d:nshf', \%opts); | 
 | 	if ($opts{h}) { | 
 | 		usage(); | 
 | 		return 0; | 
 | 	} | 
 | 	if ($opts{n}) { | 
 | 		foreach my $feed (@feeds) { | 
 | 			printf "%s\n", $feed->[1]; | 
 | 		} | 
 | 		return 0; | 
 | 	} | 
 | 	if ($opts{s}) { | 
 | 		foreach my $feed (@feeds) { | 
 | 			my $localpath = "./feeds/$feed->[1]"; | 
 | 			my $m = $update_method{$feed->[0]}; | 
 | 			my $revision; | 
 | 			if (!-d "$localpath" || !$m->{'revision'}) { | 
 | 				$revision = "X"; | 
 | 			} | 
 | 			elsif( $m->{'controldir'} && -d "$localpath/$m->{'controldir'}" ) { | 
 | 				$revision = `cd '$localpath'; $m->{'revision'}`; | 
 | 			} | 
 | 			else { | 
 | 				$revision = "local"; | 
 | 			} | 
 | 			if ($opts{d}) { | 
 | 				printf "%s%s%s%s%s%s%s\n", $feed->[1], $opts{d}, $feed->[0], $opts{d}, $revision, $opts{d}, join(", ", @{$feed->[2]}); | 
 | 			} | 
 | 			elsif ($opts{f}) { | 
 | 				my $uri = join(", ", @{$feed->[2]}); | 
 | 				if ($revision ne "local" && $revision ne "X") { | 
 | 					$uri =~ s/[;^].*//; | 
 | 					$uri .= "^" . $revision; | 
 | 				} | 
 | 				printf "%s %s %s\n", $feed->[0], $feed->[1], $uri; | 
 | 			} | 
 | 			else { | 
 | 				printf "\%-10s \%-8s \%-8s \%s\n", $feed->[1], $feed->[0], $revision, 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 do_install_src($$) { | 
 | 	my $feed = shift; | 
 | 	my $src = shift; | 
 |  | 
 | 	my $path = $src->{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; | 
 | } | 
 |  | 
 | sub do_install_target($) { | 
 | 	my $target = shift; | 
 | 	my $path = $target->{makefile}; | 
 |  | 
 | 	if ($path) { | 
 | 		$path =~ s/\/Makefile$//; | 
 | 		my $name = $path; | 
 | 		$name =~ s/.*\///; | 
 | 		my $dest = "./target/linux/feeds/$name"; | 
 |  | 
 | 		-d "./target/linux/feeds" or mkdir "./target/linux/feeds"; | 
 |  | 
 | 		-e $dest and do { | 
 | 			warn "Path $dest already exists"; | 
 | 			return 1; | 
 | 		}; | 
 |  | 
 | 		system("ln -sf ../../../$path ./target/linux/feeds/"); | 
 | 	} else { | 
 | 		warn "Target is not valid\n"; | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	# Clean packageinfo of linux kernel to force the scan. | 
 | 	# Otherwise kernel modules defined at target level are not scanned, as the | 
 | 	# linux kernel package was scanned before the installation of the target. | 
 | 	unlink "tmp/info/.packageinfo-kernel_linux"; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | sub lookup_src($$) { | 
 | 	my $feed = shift; | 
 | 	my $src = shift; | 
 |  | 
 | 	foreach my $feed ($feed, @feeds) { | 
 | 		next unless $feed->[1]; | 
 | 		next unless $feed_cache{$feed->[1]}; | 
 | 		$feed_cache{$feed->[1]}->[1]->{$src} and return $feed; | 
 | 	} | 
 | 	return; | 
 | } | 
 |  | 
 | sub lookup_package($$) { | 
 | 	my $feed = shift; | 
 | 	my $package = shift; | 
 |  | 
 | 	foreach my $feed ($feed, @feeds) { | 
 | 		next unless $feed->[1]; | 
 | 		next unless $feed_cache{$feed->[1]}; | 
 | 		$feed_cache{$feed->[1]}->[3]->{$package} and return $feed; | 
 | 	} | 
 | 	return; | 
 | } | 
 |  | 
 | sub lookup_target($$) { | 
 | 	my $feed = shift; | 
 | 	my $target = shift; | 
 |  | 
 | 	foreach my $feed ($feed, @feeds) { | 
 | 		next unless $feed->[1]; | 
 | 		next unless $feed_cache{$feed->[1]}; | 
 | 		$feed_cache{$feed->[1]}->[2]->{$target} and return $feed; | 
 | 	} | 
 | 	return; | 
 | } | 
 |  | 
 | sub is_core_src($) { | 
 | 	my $src = shift; | 
 | 	foreach my $file ("tmp/info/.packageinfo-$src", glob("tmp/info/.packageinfo-*_$src")) { | 
 | 		next unless index($file, "tmp/info/.packageinfo-feeds_"); | 
 | 		return 1 if -s $file; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | sub install_target { | 
 | 	my $feed = shift; | 
 | 	my $name = shift; | 
 | 	my $force = shift; | 
 |  | 
 | 	$feed = lookup_target($feed, $name); | 
 | 	my $feed_name = $feed->[1]; | 
 |  | 
 | 	-e "target/linux/feeds/$name" and return 0; | 
 |  | 
 | 	# enable force flag if feed src line was declared with --force | 
 | 	if (exists($feed->[3]{force})) { | 
 | 		$force = 1; | 
 | 	} | 
 |  | 
 | 	$feed = $feed_cache{$feed_name}->[2]; | 
 | 	$feed or return 0; | 
 |  | 
 | 	my $target = $feed->{$name}; | 
 | 	$target or return 0; | 
 |  | 
 | 	if (-e "target/linux/$name") { | 
 | 		if ($force) { | 
 | 			warn "Overriding target '$name' with version from '$feed_name'\n"; | 
 | 		} else { | 
 | 			warn "WARNING: Not overriding core target '$name'; use -f to force\n"; | 
 | 			return 0; | 
 | 		} | 
 | 	} else { | 
 | 		warn "Installing target '$name'\n"; | 
 | 	} | 
 | 	return do_install_target($target); | 
 | } | 
 |  | 
 | sub install_src { | 
 | 	my $feed = shift; | 
 | 	my $name = shift; | 
 | 	my $force = shift; | 
 | 	my $ret = 0; | 
 |  | 
 | 	my $select_feed = lookup_src($feed, $name); | 
 | 	unless ($select_feed) { | 
 | 		$installed{$name} and return 0; | 
 | 		$feed_src->{$name} or warn "WARNING: No feed for source package '$name' found\n"; | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	# switch to the metadata for the selected feed | 
 | 	get_feed($select_feed->[1]); | 
 | 	my $src = $feed_src->{$name} or return 1; | 
 |  | 
 | 	# enable force flag if feed src line was declared with --force | 
 | 	if (exists($select_feed->[3]{force})) { | 
 | 		$force = 1; | 
 | 	} | 
 |  | 
 | 	# If it's a core package and we don't want to override, just return | 
 | 	my $override = 0; | 
 | 	if (is_core_src($name)) { | 
 | 		if (!$force) { | 
 | 			if ($name ne "toolchain" && $name ne "linux") { | 
 | 				warn "WARNING: Not overriding core package '$name'; use -f to force\n"; | 
 | 			} | 
 | 			return 0; | 
 | 		} | 
 | 		$override = 1; | 
 | 	} | 
 |  | 
 | 	if ($installed{$name}) { | 
 | 		# newly installed packages set the source package to 1 | 
 | 		return 0 if ($installed{$name} == 1); | 
 | 		return 0 unless ($override); | 
 | 	} | 
 |  | 
 | 	$installed{$name} = 1; | 
 | 	foreach my $pkg (@{$src->{packages}}) { | 
 | 		foreach my $vpkg (@{$pkg->{provides}}) { | 
 | 			$installed_pkg{$vpkg} = 1; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if ($override) { | 
 | 		warn "Overriding core package '$name' with version from $select_feed->[1]\n"; | 
 | 	} else { | 
 | 		warn "Installing package '$name' from $select_feed->[1]\n"; | 
 | 	} | 
 |  | 
 | 	do_install_src($select_feed, $src) == 0 or do { | 
 | 		warn "failed.\n"; | 
 | 		return 1; | 
 | 	}; | 
 |  | 
 | 	# install all dependencies referenced from the source package | 
 | 	foreach my $dep ( | 
 | 		@{$src->{builddepends}}, | 
 | 		@{$src->{'builddepends/host'}}, | 
 | 	) { | 
 | 		next if $dep =~ /@/; | 
 | 		$dep =~ s/^.+://; | 
 | 		$dep =~ s/\/.+$//; | 
 | 		next unless $dep; | 
 | 		install_src($feed, $dep, 0) == 0 or $ret = 1; | 
 | 	} | 
 |  | 
 | 	foreach my $pkg (@{$src->{packages}}) { | 
 | 		foreach my $dep (@{$pkg->{depends}}) { | 
 | 			next if $dep =~ /@/; | 
 | 			$dep =~ s/^\+//; | 
 | 			$dep =~ s/^.+://; | 
 | 			next unless $dep; | 
 | 			install_package($feed, $dep, 0) == 0 or $ret = 1; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return $ret; | 
 | } | 
 |  | 
 | sub install_package { | 
 | 	my $feed = shift; | 
 | 	my $name = shift; | 
 | 	my $force = shift; | 
 |  | 
 | 	my $select_feed = lookup_package($feed, $name); | 
 | 	unless ($select_feed) { | 
 | 		$installed_pkg{$name} and return 0; | 
 | 		$feed_vpackage->{$name} or warn "WARNING: No feed for package '$name' found\n"; | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	# switch to the metadata for the selected feed | 
 | 	get_feed($select_feed->[1]); | 
 | 	my $pkg = $feed_vpackage->{$name} or return 1; | 
 | 	return install_src($feed, $pkg->[0]{src}{name}, $force); | 
 | } | 
 |  | 
 | sub install_target_or_package { | 
 | 	my $feed = shift; | 
 | 	my $name = shift; | 
 | 	my $force = shift; | 
 |  | 
 | 	lookup_target($feed, $name) and do { | 
 | 		return install_target($feed, $name, $force); | 
 | 	}; | 
 |  | 
 | 	lookup_src($feed, $name) and do { | 
 | 		return install_src($feed, $name, $force); | 
 | 	}; | 
 |  | 
 | 	return install_package($feed, $name, $force); | 
 | } | 
 |  | 
 | sub refresh_config { | 
 | 	my $default = shift; | 
 |  | 
 | 	# Don't create .config if it doesn't already exist so that making a | 
 | 	# config only occurs when the user intends it do (however we do | 
 | 	# want to refresh an existing config). | 
 | 	return if not (-e '.config'); | 
 |  | 
 | 	# 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:fh', \%opts); | 
 |  | 
 | 	if ($opts{h}) { | 
 | 		usage(); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	get_installed(); | 
 |  | 
 | 	foreach my $f (@feeds) { | 
 | 		# fetch all feeds | 
 | 		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 %$feed_src) { | 
 | 					install_src($feed, $name, exists($opts{f})) == 0 or $ret = 1; | 
 | 					get_feed($f->[1]); | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 	} else { | 
 | 		while ($name = shift @ARGV) { | 
 | 			install_target_or_package($feed, $name, exists($opts{f})) == 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_target($) { | 
 | 	my $dir = shift; | 
 | 	my $name = $dir; | 
 | 	$name =~ s/.*\///g; | 
 |  | 
 | 	my $dest = readlink $dir; | 
 | 	return unless $dest =~ /..\/..\/feeds/; | 
 | 	warn "Uninstalling target '$name'\n"; | 
 | 	unlink "$dir"; | 
 | } | 
 |  | 
 | 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"); | 
 | 		foreach my $dir (glob "target/linux/*") { | 
 | 			next unless -l $dir; | 
 | 			uninstall_target($dir); | 
 | 		} | 
 | 		$uninstall = 1; | 
 | 	} else { | 
 | 		if($#ARGV == -1) { | 
 | 			warn "WARNING: no package to uninstall\n"; | 
 | 			return 0; | 
 | 		} | 
 | 		get_installed(); | 
 | 		while ($name = shift @ARGV) { | 
 | 			my $target = "target/linux/feeds/$name"; | 
 | 			-l "$target" and do { | 
 | 				uninstall_target($target); | 
 | 				$uninstall = 1; | 
 | 				next; | 
 | 			}; | 
 |  | 
 | 			my $pkg = $installed{$name}; | 
 | 			$pkg or do { | 
 | 				warn "WARNING: $name not installed\n"; | 
 | 				next; | 
 | 			}; | 
 | 			$pkg->{src} and $name = $pkg->{src}{name}; | 
 | 			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 $force_update=shift; | 
 | 	my $rebase_update=shift; | 
 | 	my $stash_update=shift; | 
 | 	my $force_relocate=update_location( $name, "@$src" ); | 
 | 	my $rv=0; | 
 |  | 
 | 	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; | 
 | 	}; | 
 |  | 
 | 	my $failed = 1; | 
 | 	foreach my $feedsrc (@$src) { | 
 | 		warn "Updating feed '$name' from '$feedsrc' ...\n"; | 
 | 		if (update_feed_via($type, $name, $feedsrc, $force_relocate, $force_update, $rebase_update, $stash_update) != 0) { | 
 | 			if ($force_update) { | 
 | 				$rv=1; | 
 | 				$failed=0; | 
 | 				warn "failed, ignore.\n"; | 
 | 				next; | 
 | 			} | 
 | 			last; | 
 | 		} | 
 | 		$failed = 0; | 
 | 	} | 
 | 	$failed and do { | 
 | 		warn "failed.\n"; | 
 | 		return 1; | 
 | 	}; | 
 | 	return $rv; | 
 | } | 
 |  | 
 | sub update { | 
 | 	my %opts; | 
 | 	my %argv_feeds; | 
 | 	my $failed=0; | 
 |  | 
 | 	$ENV{SCAN_COOKIE} = $$; | 
 | 	$ENV{OPENWRT_VERBOSE} = 's'; | 
 |  | 
 | 	getopts('ahifrs', \%opts); | 
 | 	%argv_feeds = map { $_ => 1 } @ARGV; | 
 |  | 
 | 	if ($opts{h}) { | 
 | 		usage(); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if ($opts{f} && ($opts{r} || $opts{s})) { | 
 | 		warn "Force and rebase/stash are incompatible update options.\n";; | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	-d "feeds" or do { | 
 | 			mkdir "feeds" or die "Unable to create the feeds directory"; | 
 | 		}; | 
 |  | 
 | 	my @index_feeds; | 
 | 	foreach my $feed (@feeds) { | 
 | 		my ($type, $name, $src) = @$feed; | 
 | 		next unless $#ARGV == -1 or $opts{a} or $argv_feeds{$name}; | 
 | 		if (not $opts{i}) { | 
 | 			update_feed($type, $name, $src, $opts{f}, $opts{r}, $opts{s}) == 0 or $failed=1; | 
 | 		} | 
 | 		push @index_feeds, $name; | 
 | 	} | 
 | 	foreach my $name (@index_feeds) { | 
 | 		warn "Create index file './feeds/$name.index' \n"; | 
 | 		update_index($name) == 0 or do { | 
 | 			warn "failed.\n"; | 
 | 			$failed=1; | 
 | 		}; | 
 | 	} | 
 |  | 
 | 	refresh_config(); | 
 |  | 
 | 	return $failed; | 
 | } | 
 |  | 
 | sub feed_config() { | 
 | 	foreach my $feed (@feeds) { | 
 | 		my $installed = (-f "feeds/$feed->[1].index"); | 
 |  | 
 | 		printf "\tconfig FEED_%s\n", $feed->[1]; | 
 | 		printf "\t\ttristate \"Enable feed %s\"\n", $feed->[1]; | 
 | 		printf "\t\tdepends on PER_FEED_REPO\n"; | 
 | 		printf "\t\tdefault y\n" if $installed; | 
 | 		printf "\t\thelp\n"; | 
 | 		printf "\t\t Enable the \\\"%s\\\" feed in opkg distfeeds.conf and apk repositories.\n", $feed->[1]; | 
 | 		printf "\t\t Say M to add the feed commented out.\n"; | 
 | 		printf "\n"; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | sub usage() { | 
 | 	print <<EOF; | 
 | Usage: $0 <command> [options] | 
 |  | 
 | Commands: | 
 | 	list [options]: List feeds, their content and revisions (if installed) | 
 | 	Options: | 
 | 	    -n :            List of feed names. | 
 | 	    -s :            List of feed names and their URL. | 
 | 	    -r <feedname>:  List packages of specified feed. | 
 | 	    -d <delimiter>: Use specified delimiter to distinguish rows (default: spaces) | 
 | 	    -f :            List feeds in opkg feeds.conf compatible format (when using -s). | 
 |  | 
 | 	install [options] <package>: Install a package | 
 | 	Options: | 
 | 	    -a :           Install all packages from all feeds or from the specified feed using the -p option. | 
 | 	    -p <feedname>: Prefer this feed when installing packages. | 
 | 	    -d <y|m|n>:    Set default for newly installed packages. | 
 | 	    -f :           Install will be forced even if the package exists in core OpenWrt (override) | 
 |  | 
 | 	search [options] <substring>: Search for a package | 
 | 	Options: | 
 | 	    -r <feedname>: Only search in this feed | 
 |  | 
 | 	uninstall -a|<package>: Uninstall a package | 
 | 	Options: | 
 | 	    -a :           Uninstalls all packages. | 
 |  | 
 | 	update -a|<feedname(s)>: Update packages and lists of feeds in feeds.conf . | 
 | 	Options: | 
 | 	    -a :           Update all feeds listed within feeds.conf. Otherwise the specified feeds will be updated. | 
 | 	    -r :           Update by rebase. (git only. Useful if local commits exist) | 
 | 	    -s :           Update by rebase and autostash. (git only. Useful if local commits and uncommited changes exist) | 
 | 	    -i :           Recreate the index only. No feed update from repository is performed. | 
 | 	    -f :           Force updating feeds even if there are changed, uncommitted files. | 
 |  | 
 | 	clean:             Remove downloaded/generated files. | 
 |  | 
 | EOF | 
 | 	exit(1); | 
 | } | 
 |  | 
 | my %commands = ( | 
 | 	'list' => \&list, | 
 | 	'update' => \&update, | 
 | 	'install' => \&install, | 
 | 	'search' => \&search, | 
 | 	'uninstall' => \&uninstall, | 
 | 	'feed_config' => \&feed_config, | 
 | 	'clean' => sub { | 
 | 		system("rm -rf ./feeds ./package/feeds ./target/linux/feeds"); | 
 | 	} | 
 | ); | 
 |  | 
 | my $arg = shift @ARGV; | 
 | $arg or usage(); | 
 | parse_config; | 
 | foreach my $cmd (keys %commands) { | 
 | 	$arg eq $cmd and do { | 
 | 		exit(&{$commands{$cmd}}()); | 
 | 	}; | 
 | } | 
 | usage(); |