#!/bin/sh
#-*-mode:  sh -*-

# Copyright (c) 2007-2015, Aleksey Cheusov <vle@gmx.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

set -e

export PATH=$PSS_PRE_PATH:$PATH:$PSS_POST_PATH

. pipestatus

export LC_ALL=C

############################################################
# user settable variables
: ${PKGSRCDIR:=/usr/pkgsrc}
: ${BMAKE:=/usr/pkg/bin/bmake}
: ${PKG_INFO_CMD:=/usr/sbin/pkg_info -K /usr/pkg/pkgdb}

############################################################
: ${PSS_MKSCRIPTSDIR:=/usr/pkg/share/psu_mk}
: ${LIBEXECDIR:=/usr/pkg/libexec/psu}

export PKGSRCDIR

usage (){
    cat 1>&2 <<EOF
pkg_src_summary - builds summary information
about source packages

usage:
  pkg_src_summary -h
  pkg_src_summary [OPTIONS] [pkgpath1 pkgpath2 ...]
  pkg_src_summary -s [OPTIONS]
  pkg_src_summary -i [OPTIONS]
  pkg_src_summary -F
OPTIONS:
  -h              display this help message
  -p              generate PLIST using 'bmake plist'
  -i              generate src_summary for the installed packages only,
                  do not read list of packages from stdin and
                  from pkgpathN arguments
  -f <fields>     list of fields (separated by space or comma)
                  to be included to summary,
                  by default FULL summary is generated
  -s              ready for use as paexec slave/remote program
                  (used internally for generating summary in parallel
                  when PSS_SLAVES is set)
  -a <fields>     add the specified fields to the list
                             of default ones
  -r <fields>     remove the specified fields from the list
                             of default ones
  -b              add BOOTSTRAP_DEPENDS to BUILD_DEPENDS
  -t              add TOOL_DEPENDS to BUILD_DEPENDS
  -l              add library dependencies (bl3.mk) to BUILD_DEPENDS
  -d              also generates summary for dependancies (DEPENDS)
  -D              also generates summary for dependancies (BUILD_DEPENDS)
  -B              also generates summary for dependancies (BOOTSTRAP_DEPENDS)
  -T              also generates summary for dependancies (TOOL_DEPENDS)
  -A              implies -d, -B, -D and -T
  -m              generate ASSIGNMENTS field for multi-variant packages
  -u              leave only one summary per PKGNAME
  -M              implies -m and move/add ASSIGNMENTS to PKGPATH field
  -F              output fields (one per line) to generate and exit
  -G              for debugging
  -v              verbose output
EOF
}

set_fields (){
    # $@ - fields
    PSS_FIELDS=$(echo $* | tr , ' ')
}

add_fields (){
    # $@ - fields to add
    __fields=$(echo $* | tr , ' ')
    rem_fields $__fields # avoid duplications
    for f in $__fields; do
	PSS_FIELDS="$PSS_FIELDS $f"
    done
}

rem_fields (){
    # $@ - fields to remove
    __fields=$(echo $* | tr , ' ')
    PSS_FIELDS=`awk '
BEGIN {
   for (i=1; i < ARGC; ++i){
      if (ARGV [i] == "-") break
      excl [ARGV [i]] = 1
   }
   for (++i; i < ARGC; ++i){
      if (! (ARGV [i] in excl)){
         printf "%s ", ARGV [i]
      }
   }
}
' $__fields - $PSS_FIELDS`
}

# list of fields for default pkg_src_summary
if test -n "$PSS_FIELDS"; then
    set_fields $PSS_FIELDS
else
    PSS_FIELDS='PKGNAME PKGPATH DEPENDS BUILD_DEPENDS TOOL_DEPENDS CONFLICTS HOMEPAGE COMMENT ONLYFOR NOTFOR MAINTAINER CATEGORIES NO_BIN_ON_FTP NO_SRC_ON_FTP NO_BIN_ON_CDROM NO_SRC_ON_CDROM LICENSE DESCRIPTION PLIST' # CVS_CHECKSUM ALLDISTFILES
fi

process_options (){
    alt_opts="$1"
    shift
    alt_getopt $alt_opts \
	'h help'        'usage; exit 0' \
	'=f fields'      'set_fields ' \
	'=a add-fields'  'add_fields ' \
	'=r rem-fields'  'rem_fields ' \
	's slave-mode'   slave=1       \
	'p make-plist'   make_plist='-p' \
	b        'add_fields BOOTSTRAP_DEPENDS BUILD_DEPENDS; with_bootstrap=-b' \
	t        'add_fields TOOL_DEPENDS BUILD_DEPENDS; with_tool_depends=-t' \
	l        'with_libdeps=-l'     \
	'd with-deps'    with_dep=-d   \
	'D with-bdeps'   with_bdep=-D  \
	'T with-tdeps'   with_tdep=-t  \
	'B with-Bdeps'   with_Bdep=-b  \
	'A with-alldeps' 'add_fields DEPENDS BUILD_DEPENDS TOOL_DEPENDS BOOTSTRAP_DEPENDS;
	                 with_dep=-d; with_bdep=-D with_tdep=-t with_Bdep=-b' \
	m                multi_var=1   \
	M                multi_var=2   \
	u                uniq_pkgname=1   \
	F                output_fields=1   \
	'i installed'    installed_pkgs=1 \
	G                debug=1 \
	v                verbose=1 \
	-- "$@"
}
cmds=`process_options -c $PSS_OPTIONS`
eval "$cmds"
cmds=`process_options '' "$@"`
eval "$cmds"

pkgpaths="$*"

with_xxx="$with_bootstrap $with_tool_depends $with_libdeps $make_plist"

if test "$multi_var"; then
    # _VARIANTS - artificial field keeping all variable assignments
    # for multi-variant packages.
    add_fields '_VARIANTS'
fi

if test -n "$with_dep"; then
    add_fields DEPENDS
fi
if test -n "$with_bdep"; then
    add_fields BUILD_DEPENDS
fi
if test -n "$with_tdep"; then
    add_fields TOOL_DEPENDS
fi
if test -n "$with_Bdep"; then
    add_fields BOOTSTRAP_DEPENDS
fi

if echo "$PSS_FIELDS" | grep -q DESCRIPTION; then
    field_descr=1
fi
if echo "$PSS_FIELDS" | grep -q PLIST; then
    field_plist=1
fi
if echo "$PSS_FIELDS" | grep -q ALLDISTFILES; then
    field_alldistfiles=1
fi
if echo "$PSS_FIELDS" | grep -q CVS_CHECKSUM; then
    field_cvs_checksum=1
fi
if test -n "$output_fields"; then
    for f in $PSS_FIELDS; do
	echo $f
    done
    exit 0
fi
varnames=`echo $PSS_FIELDS |
   awk -v make_plist="$make_plist" '{
         gsub(/PLIST/, (make_plist ? "" : "PLIST_SRC"))
         gsub(/CVS_CHECKSUM/, "")
         gsub(/ONLYFOR/, "ONLY_FOR_PLATFORM")
         gsub(/NOTFOR/,  "NOT_FOR_PLATFORM")
         gsub(/DESCRIPTION/, "DESCR_SRC")
         gsub(/ALLDISTFILES/, "DISTINFO_FILE DISTFILES")
         print }'`

if test -n "$debug"; then
    echo '===== PSS_FIELDS: =========' 1>&2
    echo "$PSS_FIELDS" 1>&2
fi

############################################################
on_exit () {
	# Stupid test for stupid Solaris
    if test -n "$tmp_dir"; then
	rm -rf $tmp_dir || true
    fi
}
. "${LIBEXECDIR}/sig_handler.sh"

tmp_dir=`mktemp -d ${TMPDIR-/tmp}/pkg_src_summary.XXXXXX`
test -n "$tmp_dir" || exit 1

tmpfn="$tmp_dir/pkgdirs2info.txt"
errsfn="$tmp_dir/errors.txt"
summaryfn="$tmp_dir/summary.txt"
real_plistfn="$tmp_dir/real_plist.txt"
multi_pkgs_fn="$tmp_dir/multi_pkgs.txt"
multi_vars_fn="$tmp_dir/multi_vars.txt"
normal_pkgs_fn="$tmp_dir/normal_pkgs.txt"

############################################################
pkgpath2multivar_opts (){
    # textproc/dictem:EMACS_TYPE=xemacs215 -> EMACS_TYPE=xemacs215
    # sysutils/mc:PKG_OPTIONS.mc=-x11~-slang -> PKG_OPTIONS.mc='-x11 -slang'
    # sysutils/mc:A=a,B=b -> A=a B=b
    sed -e 's|^[^:]*:||' -e 's|,| |g' -e "s|[^ ][^ ]*|'&'|g" -e 's|~| |g' "$@"
}

cd_and_print_summary (){
    # $1 - pkgpath
    real_pkgpath="`echo $1 | cut -d: -f1`"
    if test "$real_pkgpath" = "$1"; then
	var_assigns=''
    else
	var_assigns="$(echo $1 | pkgpath2multivar_opts)"
	var_assignments="_ASSIGNMENTS='$(echo $1 | sed 's,^[^:]*:,,')'"
    fi

    # PLIST pre
    if test -n "$make_plist"; then
	rm -f "$real_plistfn"
	var_assigns="$var_assigns plist PLIST=$real_plistfn"
    fi

    # INHER_ASSIGNS must be before DEPENDS and BUILD_DEPENDS, see below
    ( cd "$real_pkgpath" && eval ${BMAKE} -f ./Makefile -f ../../mk/pbulk/pbulk-index.mk \
	-f "$PSS_MKSCRIPTSDIR"/pkg_src_summary.mk my-show-vars \
	VARNAMES="'_INHER_ASSIGNS _INHER_ASSIGNS_REJ _INHER_ASSIGNS_BAD ASSIGNMENTS $varnames'" \
	$var_assignments $var_assigns ) > "$summaryfn" || return 1

    # PLIST post
    if test -n "$make_plist"; then
	awk '/^[^@]/ {print "PLIST=" $0}' "$real_plistfn" >> "$summaryfn"
    fi

    # CVS_CHECKSUM
    if test "$field_cvs_checksum"; then
	cvs_checksum "$real_pkgpath" > "$tmpfn" || return 1
	read cksum < "$tmpfn" || return 1
	printf "CVS_CHECKSUM=%s\n" "$cksum" >>"$summaryfn" || return 1
    fi
}

summary2deps (){
    awk '
match($0, /^(BUILD_)?DEPENDS=/) {
   $0=substr($0, RLENGTH+1)
   gsub(/[^ :]*:[.][.]\/[.][.]\//, "")
   print
}' "$@"
}

generate_summary (){
    # general information
    if cd_and_print_summary $1 2>"$errsfn"
    then
	all_deps=$(summary2deps "$summaryfn")

	bad_deps=''
	for d in $all_deps; do
	    if ! test -d "$PKGSRCDIR/$d"; then
		if test -z "$bad_deps"; then
		    printf ' ------------------\n' 1>&2
		    printf "Bad package %s, skipped\n" "$1" 1>&2
		fi
		bad_deps=1
		printf "     not existing dependancy: %s\n" "$d" 1>&2
	    fi
	done

	if test -z "$bad_deps"; then
	    cat "$summaryfn"

	    echo '' # empty line - separator
	fi
	if test -s "$errsfn"; then
	    printf ' ------------------\n' 1>&2
	    printf "Package %s\n" "$1" 1>&2
	    cat "$errsfn" 1>&2
	fi
    else
	printf ' ------------------\n' 1>&2
	printf "Bad package %s, skipped\n" "$1" 1>&2
	cat "$errsfn" 1>&2
    fi
}

############################################################
installed_packages2stdout (){
    $PKG_INFO_CMD -Xa | sed -n 's,^PKGPATH=,,p'
}

packages2stdout (){
    if test -n "$installed_pkgs"; then
	installed_packages2stdout
    elif test $# -eq 0; then
	# processing stdin
	awk '{for (i=1; i <= NF; ++i) {print $i}}' "$@"
    else
	# processing arguments
	for pkgpath in "$@"; do
	    echo "$pkgpath"
	done
    fi
}

process_one_by_one (){
    while read pkgpaths; do
	for pkgpath in $pkgpaths; do
	    generate_summary "$pkgpath" |
	    pkg_enrich_summary $with_bootstrap $with_tool_depends
	done

	if test "$slave"; then
	    # for paexec
	    echo 'success'
	    echo "$PAEXEC_EOT"
	fi
    done
}

############################################################
# direct packages
partial_summary_fn="$tmp_dir/partial_summary.txt"
printf '' > "$partial_summary_fn"

show_progress ()(
    while test -f "$partial_summary_fn"; do
	printf '\rProcessed packages: ' 1>&2
	awk 'NF == 0 {++cnt} END {printf "%d", cnt}' "$partial_summary_fn" 1>&2 2>/dev/null
	sleep 1
    done
    echo ''
)

if test -n "$slave"; then
    cd "$PKGSRCDIR"
    process_one_by_one
    exit 0
elif test -n "$verbose"; then
    show_progress &
    show_progress_pid="$!"
fi

do_src_summary (){
    if test "$PSS_SLAVES"; then
	qfields="$(echo $PSS_FIELDS | sed 's| |,|g')"
	environ="$PSS_ENVIRON PSS_SLAVES= BMAKE=$BMAKE CKSUM=$CKSUM PKGSRCDIR=$PKGSRCDIR"
	environ="$environ PSS_PRE_PATH=$PSS_PRE_PATH PSS_POST_PATH=$PSS_POST_PATH"
	if test -n "$MAKECONF"; then
	    environ="$environ MAKECONF=$MAKECONF"
	fi

	runpipe0 \
	    packages2stdout $pkgpaths '|' \
	    paexec -gylez $PSS_PAEXEC_EXTRA_OPTS \
		-n "$PSS_SLAVES" \
		-t "$PSS_TRANSPORT" \
		-c "env $environ $0 -s $with_xxx -f '$qfields'" '|' \
		paexec_reorder -Mm -yg '|' \
	    sed '/^success$/ d'
    else
	cd "$PKGSRCDIR"

	runpipe0 \
	    packages2stdout $pkgpaths '|' \
	    process_one_by_one
    fi
}

if test -n "${with_dep}${with_bdep}${with_tdep}${with_Bdep}${multi_var}"; then
    do_src_summary > "$partial_summary_fn"
elif test -n "$verbose"; then
    do_src_summary > "$partial_summary_fn"
    cat "$partial_summary_fn"
    exit $?
else
    do_src_summary
    exit $?
fi

############################################################
# multi-variant packages
export PSS_FIELDS

if test "_$multi_var" = _2; then
    move_ASSIGNMENTS_to_PKGPATH (){
	pkg_assignments2pkgpath "$@"
    }
else
    move_ASSIGNMENTS_to_PKGPATH (){
	cat "$@"
    }
fi

if test "$multi_var"; then
    if test -n "$debug"; then
	echo '===== partial_summary.txt: =========' 1>&2
	cat "$partial_summary_fn" 1>&2
    fi

    pkg_grep_summary -f PKGPATH,_VARIANTS,_INHER_ASSIGNS,_INHER_ASSIGNS_REJ \
	-v -e _VARIANTS < "$partial_summary_fn" >"$multi_pkgs_fn"

    if test -n "$debug"; then
	echo '===== multi_pkgs.txt: =========' 1>&2
	cat "$multi_pkgs_fn" 1>&2
    fi

    if test -s "$multi_pkgs_fn"; then
	pkg_grep_summary -e _VARIANTS \
	    < "$partial_summary_fn" >"$normal_pkgs_fn"
	if test -n "$debug"; then
	    echo '===== normal_pkgs.txt: =========' 1>&2
	    cat "$normal_pkgs_fn" 1>&2
	fi
	mv "$normal_pkgs_fn" "$partial_summary_fn"

	"$LIBEXECDIR/summary2multi_variants" < "$multi_pkgs_fn" > "$multi_vars_fn"
	if test -n "$debug"; then
	    echo '===== multi_vars.txt: =========' 1>&2
	    cat "$multi_vars_fn" 1>&2
	fi

	runpipe0 \
	    env _PSS_RECURS=1 $0 $with_xxx $make_plist < "$multi_vars_fn" '|' \
	    move_ASSIGNMENTS_to_PKGPATH |
	    if test -n "$uniq_pkgname"; then
		pkg_uniq_summary -Fn
	    else
		cat
	    fi >> "$partial_summary_fn"
    fi
fi

############################################################
# dependencies

extra_deps_fn="$tmp_dir/extra_deps.txt"

processed_pkgs_fn="$tmp_dir/processed_pkgs.txt"

"$LIBEXECDIR/get_processed_pkgs" < "$partial_summary_fn" > "$processed_pkgs_fn"

deps1_all_fn="$tmp_dir/level1_all_deps.txt"

while test -n "${with_dep}${with_bdep}${with_tdep}${with_Bdep}"; do
    if test -n "$debug"; then
	echo '===== partial_summary: =========' 1>&2
	cat "$partial_summary_fn" 1>&2
    fi

    if test -n "$debug"; then
	echo '===== processed_pkgs: =========' 1>&2
	cat "$processed_pkgs_fn" 1>&2
    fi

    ##
    "$LIBEXECDIR/direct_deps" $with_dep $with_bdep $with_tdep $with_Bdep -p "$processed_pkgs_fn" \
	"$partial_summary_fn" > "$extra_deps_fn"

    if test -n "$debug"; then
	echo '===== extra_deps: =========' 1>&2
	sort "$extra_deps_fn" 1>&2
    fi

    if ! test -s "$extra_deps_fn"; then
	break
    fi

    ##
    env _PSS_RECURS=1 $0 -m $with_xxx $make_plist \
	< "$extra_deps_fn" > "$deps1_all_fn".tmp
    pkg_grep_summary -e _INHER_ASSIGNS_BAD > "$deps1_all_fn" \
	< "$deps1_all_fn".tmp > "$deps1_all_fn"

    if test -n "$debug"; then
	echo '===== level-1 deps summaries =========' 1>&2
	cat "$deps1_all_fn" 1>&2
    fi

    ##
    cat "$deps1_all_fn" >> "$partial_summary_fn"

    pkg_uniq_summary "$partial_summary_fn" > "$partial_summary_fn".tmp
    mv "$partial_summary_fn".tmp "$partial_summary_fn"

    "$LIBEXECDIR/get_processed_pkgs" "$deps1_all_fn" >> "$processed_pkgs_fn"
    cat "$extra_deps_fn" >> "$processed_pkgs_fn"
    sort -u "$processed_pkgs_fn" > "$processed_pkgs_fn".tmp
    mv "$processed_pkgs_fn".tmp "$processed_pkgs_fn"
done

remove_internal_fields (){
    grep -v '^_.*=' "$@" || true
}

if test -z "$_PSS_RECURS"; then
    if test -n "$debug"; then
	echo '===== final result: =========' 1>&2
    fi
    remove_internal_fields "$partial_summary_fn"
else
    cat "$partial_summary_fn"
fi
