#!/bin/sh
#
# cook-deps - SliTaz cook: analyze runtime dependencies of a cooked
# package by reading its files.list and resolving each binary /
# library / pkg-config / libtool reference back to the package that
# ships it. Output is one line "pkg: dep1 dep2 ..." per sub-package.
#
# Maintains a per-cache file database in $cache/tp.*.db so subsequent
# runs reuse the resolved index instead of rescanning files-list.lzma.
#
# Usage:
#   cook-deps <pkg>        # analyze deps of a cooked package
#   cook-deps <pkg> -q     # quiet: one line per sub-package
#   cook-deps <pkg> --la   # also follow *.la dependency_libs
#   cook-deps <pkg> --incl # include glibc-base / gcc-lib-base / gcc in output
#
# Copyright (C) SliTaz GNU/Linux - GNU GPL v3
#

. /usr/lib/slitaz/libcook.sh


usage() {
	cat <<EOT

$(boldify "$(_ 'Usage:')") cook-deps <pkg> [options]

$(boldify "$(_ 'Commands:')")
  usage|help|--help|-h $(_ 'Display this short usage.')
  <pkg>                $(_ 'Analyze runtime deps of <pkg> (and its splits).')

$(boldify "$(_ 'Options:')")
  -q | --quiet         $(_ 'One line per sub-package, deps separated by spaces.')
  --la                 $(_ 'Also follow *.la dependency_libs.')
  --incl               $(_ 'Include glibc-base / gcc-lib-base / gcc in output.')

$(boldify "$(_ 'Examples:')")
  cook-deps libogg             $(_ 'show what libogg pulls in at runtime')
  cook-deps libogg -q          $(_ 'one-line-per-pkg output, scripting-friendly')
  cook-deps libogg --incl      $(_ 'also show implicit core deps')

$(boldify "$(_ 'About the databases:')")
  cook-deps maintains 4 indexes in \$cache (tp.so.db, tp.a.db,
  tp.la.db, tp.pc.db) built from \$cache/tp.files.db, itself merged
  from \$cache/files.list and the system mirror's files.list.lzma
  (on the official tank cooker the mirror list is skipped — too big).
  The DB files are rebuilt automatically when stale.

EOT
	exit 0
}


pkg=''
quiet=''
la=''
incl=''

for arg in "$@"; do
	case "$arg" in
		usage|help|--help|-h) usage ;;
		-q|--quiet) quiet='yes' ;;
		--la)       la='yes' ;;
		--incl)     incl='yes' ;;
		-*) _ 'Unknown option: %s' "$arg" >&2; usage ;;
		*) [ -z "$pkg" ] && pkg="$arg" ;;
	esac
done

[ -z "$pkg" ] && usage


#
# Maintain databases
#

fl="$cache/tp.files.db"		# combined list of all files
db_so="$cache/tp.so.db"		# database with *.so files
db_a="$cache/tp.a.db"		# database with *.a files
db_la="$cache/tp.la.db"		# database with *.la files
db_pc="$cache/tp.pc.db"		# database with *.pc files

fl_mirrorz='/var/lib/tazpkg/files.list.lzma'	# mirror files list
fl_local='/home/slitaz/cache/files.list'		# local files list


# Drop entries for packages no longer in the wok split.db.

action 'Updating %s...' "$(basename $fl_local)"

splitdb="$CACHE/split.db"
sed 's|:.*||' $fl_local \
| sort -u \
| while read pkg_in_list; do
	mainpkg=$(awk -F$'\t' -vpkg=" $pkg_in_list " '{
		if (index(" " $2 " ", pkg)) {print 1; exit}
	}' $splitdb)
	[ -n "$mainpkg" ] || sed -i "/^$pkg_in_list: /d" $fl_local
done
status

# Combine local + mirror file lists into tp.files.db when stale.
case $(hostname) in
	# On the tank cooker the mirror list is too big — local only.
	tank)
		[ ! -s $fl   -o   $fl_local -nt $fl ] && cp $fl_local $fl
		;;
	*)
		if [ ! -s $fl   -o   $fl_mirrorz -nt $fl   -o   $fl_local -nt $fl ]; then
			action 'Updating %s...' "$(basename $fl)"
			fl_mirror="$(mktemp)"
			lzcat $fl_mirrorz | sed '/\.\(so.*\|a\|la\|pc\)$/!d' > $fl_mirror
			# drop packages already in the local list
			cut -d: -f1 $fl_local | uniq | \
			while read package; do
				sed -i "/^$package: /d" $fl_mirror
			done
			cat $fl_mirror $fl_local > $fl
			rm $fl_mirror
			status
		fi
		;;
esac

# Per-extension indexes — rebuilt when tp.files.db is newer.

if [ ! -s $db_so   -o   $fl -nt $db_so ]; then
	action 'Updating %s...' "$(basename $db_so)"
	fgrep '/lib/' $fl | fgrep '.so' | \
	sed 's|^\([^:]*\):.*/\([^/]*\)$|\2\t\1|' | \
	awk -F$'\t' '{if ($2 !~ "uclibc") print}' | \
	sort > $db_so
	status
fi

if [ ! -s $db_a -o $fl -nt $db_a ]; then
	action 'Updating %s...' "$(basename $db_a)"
	fgrep '/usr/lib/lib' $fl | fgrep '.a' | \
	sed 's|^\([^:]*\):.*/\([^/]*\)$|\2\t\1|' | \
	sort > $db_a
	status
fi

if [ ! -s $db_la -o $fl -nt $db_la ]; then
	action 'Updating %s...' "$(basename $db_la)"
	fgrep '/usr/lib/' $fl | fgrep '.la' | \
	sed 's|^\([^:]*\): \(.*\)$|\2\t\1|' | \
	sort > $db_la
	status
fi

if [ ! -s $db_pc -o $fl -nt $db_pc ]; then
	action 'Updating %s...' "$(basename $db_pc)"
	grep '\.pc$' $fl | \
	sed -e 's|^\([^:]*\):.*/\([^/]*\)$|\2\t\1|' -e '/\tbuildroot$/d' | \
	sort > $db_pc
	status
fi


#
# Helpers
#

# Filter out the noise (ld-linux, linux-gate) and apply hand-curated
# package-renaming rewrites accumulated over the years.

outpkg() {
	pkgs="$1"
	case $pkgs in
		*ld-linux.so*);;
		*linux-gate.so*);;
		*)
			echo "$pkgs" | awk -vincl="$incl" '
			# if both packages exist in list, return the first one only
			function s(pkg1, pkg2) {
				if (index(" "$0" ", " "pkg1" ") && index(" "$0" ", " " pkg2 " "))
					$0 = pkg1;
			}
			{
				s("wine",         "wine-rt");
				s("samba",        "samba-pam");
				s("mesa",         "mesa-wayland");
				s("mysql",        "mariadb");
				s("perl",         "perl-thread");
				s("xorg-server",  "xorg-server-light");
				s("cairo",        "cairo-gl");
				s("freetype",     "freetype-infinality");
				s("freetype",     "freetype-without-harfbuzz");
				s("harfbuzz",     "harfbuzz-icu");
				s("openbox",      "openbox-imlib2");
				s("gcc-lib-base", "gcc49-lib-base"); # also gcc54-lib-base and gcc61-lib-base
				s("polkit",       "polkit-pam");
				s("libgudev",     "eudev"); # also systemd
				s("xz-dev",       "liblzma-dev");
				s("xorg-libxcb",  "libxcb");
				s("xorg-xcb-util-image",      "xcb-util-image");
				s("xorg-xcb-util-keysyms",    "xcb-util-keysyms");
				s("xorg-xcb-util-renderutil", "xcb-util-renderutil");
				s("xorg-xcb-util-wm",         "xcb-util-wm");
				s("xorg-xcb-util",            "xcb-util");
				s("xorg-libxcb",              "libxcb");
				s("xorg-libxcb-dev",          "libxcb-dev");
				s("xorg-pixman",              "pixman");
				s("xorg-pixman-dev",          "pixman-dev");
				s("xorg-xcb-util-cursor",     "xcb-util-cursor");
				s("xorg-xcb-util-dev",        "xcb-util-dev");
				s("xorg-xcb-util-image-dev",  "xcb-util-image-dev");
				s("xorg-xcb-util-renderutil-dev", "xcb-util-renderutil-dev");
				s("eudev-dev", "udev-dev");
				s("util-linux-uuid",     "ossp-uuid");
				s("util-linux-uuid-dev", "ossp-uuid-dev");
				s("polkit-pam-dev", "polkit-dev");
				s("nspr", "palemoon"); # I doubt app may depend on Palemoon
				s("nss", "palemoon");  #
				s("xfconf", "libxfconf"); s("xfconf-dev", "libxfconf-dev");
				s("exo",    "libexo");    s("exo-dev",    "libexo-dev");
				s("gconf",  "GConf");     s("gconf-dev",  "GConf-dev");
				s("pulseaudio", "apulse");
				s("cairo-dev", "cairo-gl-dev");
				s("freetype-dev", "freetype-infinality-dev");
				s("nspr", "webian-shell");
				s("nss", "webian-shell");
				s("libxml2", "libxml2-min");

				if (incl == "yes" ||
					! index($0, "glibc-base") &&
					! index($0, "gcc-lib-base") &&
					! index($0, "glibc-dev") &&
					$0 != "gcc")
						print gensub(" ", "|", "g");
			}';;
	esac
}


# Look up item $1 in db $2 (which is "<key>\t<pkg>" sorted).

indb() {
	local res="$(awk -vi="$1" '
		{
			if ($1 == i)
				{ print $2; found = 1; }
		}
		END {
			if (found != 1) {
				if (index(i, "statically linked"))
					print gensub(" ", "_", "g", i);
				else
					printf("[%s]\n", i);
			}
		}
	' $2 | tr '\n' ' ' | sed 's| $||')"

	outpkg "$res"
}


# Like `ldd` but returns package names. Also handles .la / .pc /
# kernel modules / perl / python scripts.

tp_ldd() {
	local tmptmp libs variables pcs pkg pkgs out
	unset IFS
	tmptmp=$(mktemp)

	case $1 in
		*.la)
			if [ -n "$la" ]; then
				libs=$(. $1; echo $dependency_libs)
				for i in $libs; do
					case $i in
						-l*)  indb "${i/-l/lib}.so" $db_so >>$tmptmp;;
						*.la) indb "$i" $db_la >>$tmptmp;;
					esac
				done
			fi
			;;
		*.pc)
			variables=$(mktemp)
			for i in $(grep '^[a-zA-Z_][a-zA-Z_]*=' $1 | cut -d= -f1); do
				echo "$i=\"$(pkg-config --variable=$i $1)\""
			done > $variables
			. $variables
			rm $variables
			pcs=$(grep '^Requires' $1 | cut -d: -f2 | tr ',' ' ' | tr '\n' ' ')
			pcs=$(eval echo "${pcs//>/}")
			for i in $pcs; do
				isitlib=$(echo $i | tr -d '<=>0-9.')
				[ -n "$isitlib" ] || continue
				indb "$i.pc" $db_pc >>$tmptmp
			done
			libs=$(grep '^Libs' $1 | cut -d: -f2 | tr '\n' ' ')
			for i in $libs; do
				case $i in
					-L*) eval LIBDIR="${i#-L}";;
					-l*)
						eval i="$i"
						pkg=$(indb "lib${i#-l}.so" $db_so)
						pkgs=$(awk -F$'\t' -vpkg="$pkg" '{if (index(" " $2 " ", " " pkg " ")) print $2;}' /home/slitaz/cache/split.db)
						unset out
						for j in $pkgs; do
							case $j in $pkg-dev) out=$j; break;;
							esac
						done
						[ -z "$out" ] &&
						for j in $pkgs; do
							case $j in *-dev) out=$j; break;;
							esac
						done
						echo ${out:-$pkg}
						;;
				esac
			done
			;;
		*/lib/modules/*) echo 'linux' ;;
		*.pl|*.pm)       echo 'perl' ;;
		*.py)            echo 'python' ;;
		*)
			readelf -dW "$1" 2>/dev/null \
			| sed -n '/NEEDED/ s|.*\[\(.*\)\]|\1|p' \
			| while read i; do
				indb "$i" $db_so >>$tmptmp
			done
			;;
	esac

	sort -u $tmptmp
	rm $tmptmp
}


# Names of all packages bundled in this receipt (main + SPLIT).

all_names() {
	local split=$(echo $SPLIT \
		| awk '
			BEGIN { RS = " "; FS = ":"; }
			{ print $1; }' \
		| tr '\n' ' ')
	local split_space=" $split "
	if [ "${split_space/ $PACKAGE /}" != "$split_space" ]; then
		echo $split
	else
		echo $PACKAGE $split
	fi
}


#
# Run
#

unset IFS
. $WOK/$pkg/receipt

if [ -n "$quiet" ]; then
	sub='\n'
else
	sub=' '
fi

for p in $(all_names); do
	title 'Dependencies for "%s"' "$p"
	[ -n "$quiet" ] && echo -n "$p: "
	IFS=$'\n'
	while read file; do
		# subprocess: VERSION may be redefined by sourced .pc files
		( tp_ldd "$WOK/$pkg/taz/$p-$VERSION/fs$file" )
	done < $WOK/$pkg/taz/$p-$VERSION/files.list \
	| sort -u | grep -v "^$p$" | sed '/^$/d' \
	| tr "$sub" ' '
	[ -n "$quiet" ] && echo
done

newline
