#!/bin/sh

##########################################################################
# Genplist
#   Generates a static package list for a FreeBSD port.
#
# Author:
#   Jason W. Bacon
#
# Contributors:
#   Greg Larkin, SourceHosting.net
#   Olli Hauer


###########################################
# Replace hard-coded paths with variables.

plist_stripper()
{
    sed "s|share/${portname}|%%DATADIR%%|g" < $1 \
	    | sed "s|share/doc/${portname}|%%DOCSDIR%%|g" \
	    | sed "s|share/examples/${portname}|%%EXAMPLESDIR%%|g" \
	    | sed "s|share/java/classes|%%JAVAJARDIR%%|g" \
	    | sed "s|www/${portname}|%%WWWDIR%%|g" \
	    | sed "s|etc/${portname}$|%%ETCDIR%%|g" \
	    | sed "s|etc/${portname}/|%%ETCDIR%%/|g" \
	    | sed "s|etc/${portname}\\$|%%ETCDIR%%$|g" \
	    | sed "s|lib/python[[:digit:]+]\.[[:digit:]+]/site-packages|%%PYTHON_SITELIBDIR%%|g" \
	    | sed "s|lib/python[[:digit:]+]\.[[:digit:]+]/site-packages/|%%PYTHON_SITELIBDIR%%/|g"
    return ${EX_OK}
}


#############################################
# Remove pkg-plist.new and say why on stdout

remove_plist_new()
{
    if [ $# -ne 1 ]; then
	printf "Usage: remove_plist_new reason\n"
	exit ${EX_SOFTWARE}
    fi
    
    reason=$1
    rm -f pkg-plist.new
    printf "\n*** $0:\n\t$reason: removed pkg-plist.new.\n"
    printf "\t(man-plist.new may still contain changes.)\n\n"
}


##########################################################################
# Generate a new plist and store in pkg-plist.new.

create_plist()
{
    # mtree has several different port types.  We support only local
    # at this time.
    porttype="local"
    
    # Extract port name.
    portname=`awk '($1 == "PORTNAME=") || ($1 == "PORTNAME?=") { print $2 }' Makefile`
    portversion=`awk '($1 == "PORTVERSION=") || ($1 == "PORTVERSION?=") { print $2 }' Makefile`
    # Check if we are running in a real ports directory and not somewhere else
    if [ ! -z ${portname} ]; then
	printf "PORTNAME = ${portname}\n"
    else
	master_dir=`awk '$1 == "MASTERDIR=" { print $2 }' Makefile`
	master_dir=${master_dir#\$\{\.CURDIR\}/}
	printf "$master_dir\n"
	if [ ! -z ${master_dir} ]; then
	    portname=`awk '($1 == "PORTNAME=") || ($1 == "PORTNAME?=") { print $2 }' $master_dir/Makefile`
	    if [ ! -z ${portname} ]; then
		printf "PORTNAME = ${portname}\n"
	    else
		printf "cannot identify PORTNAME\n"
		return ${EX_CONFIG}
	    fi
	else
	    printf "cannot identify PORTNAME\n"
	    return ${EX_CONFIG}
	fi
    fi
    
    # Get temporary prefix from command line.
    prefix=$(printf $1/${portname}-${portversion} | tr -s '//' '/')
    printf "${prefix}\n" > genplist.prefix
    printf "PREFIX = ${prefix}\n"
    
    # Remove old version.
    make PREFIX=${prefix} deinstall
    if [ -e ${prefix} ]; then
	printf "${prefix} currently exists.  Make sure all files from this port\n"
	printf "are removed from ${prefix}, and start again.\n"
	return ${EX_CANTCREAT}
    fi

    # Install dependencies without setting PREFIX first, so they install
    # under the standard PREFIX.
    make depends
    
    if [ 0`make -V NO_STAGE` != 0'yes' ]; then
	make PREFIX=${prefix} stage
	make PREFIX=${prefix} makeplist > pkg-plist.new
    else
	# Create standard directory tree for temporary installation using
	# mtree.
	if [ "${porttype}" != "none" ]; then
	    mkdir ${prefix}
	    OSREL=`sysctl -n kern.osreldate`
	    if [ -e ${PORTSDIR=/usr/ports}/Templates/BSD.${porttype}.dist -a ${OSREL} -lt 701000 ]; then
		mtree -U -f ${PORTSDIR=/usr/ports}/Templates/BSD.${porttype}.dist -d -e -p ${prefix} | grep -v '(created)$'
	    elif [ -e ${PORTSDIR=/usr/ports}/Templates/BSD.${porttype}.dist ]; then
		mtree -U -f ${PORTSDIR=/usr/ports}/Templates/BSD.${porttype}.dist \
		    -d -e -p ${prefix} | grep -v '(created)$'
	    else
		printf "Cannot find ${PORTSDIR=/usr/ports}/Templates/BSD.${porttype}.dist\n"
		return ${EX_OSFILE}
	    fi
	    printf "mtree finished ...\n"
	    
	    # Why is this here? Leftover from old script?
	    # make PREFIX=${prefix} depends
	    
	    # Store the directory structure in a new file.
	    (cd ${prefix} && find -d * -type d) | sort > OLD-DIRS
	else
	    # Make an empty OLD_DIRS for @dirrms below.
	    rm -f OLD-DIRS
	    touch OLD-DIRS
	fi
	
	# Create an empty pkg-plist file:
	touch pkg-plist.new
	
	# If your port honors PREFIX (which it should) you can then install
	# the port and create the package list.
	time nice make PREFIX=${prefix} reinstall
	printf "@comment $FreeBSD$\n" > pkg-plist.new
	printf "@comment Generated by sysutils/genplist\n" > pkg-plist.new
	(cd ${prefix} && find -d * \! -type d) | sort >> pkg-plist.new
	
	# Remove man pages from pkg-plist, and create a list that can be easily
	# inserted into Makefile
	awk -F '/' '$1 == "man" { printf("\t%s \\\n",$3) }' pkg-plist.new > man-plist.new
	awk -F '/' '$1 != "man" { printf("%s\n",$0) }' pkg-plist.new > temp \
		&& mv -f temp pkg-plist.new
	
	# Add any directories created by 'make install' to the packing list.
	# ( Anything added to what mtree created )
	(cd ${prefix} && find -d * -type d) | sort | comm -13 OLD-DIRS - | \
		sort -r | sed -e 's|^|@dirrm |' | \
		egrep -v '(share/locale)' >> pkg-plist.new
	
	# Make sure binary package creates any empty directories.
	(cd ${prefix} && find -d * -type d -empty) | sort | comm -13 OLD-DIRS - | \
		sort -r | sed -e 's|^|@exec mkdir -p %D/|' | \
		egrep -v '(share/locale)' >> pkg-plist.new
	
	# Replace hard-coded paths with variables and remove other
	# unnecessary entries.  Reduce shared library extensions
	# from lib.so.x.y to lib.so.x to conform with modifications made
	# by the ports system.  (The linker only cares about the major
	# version number, so minor numbers are stripped by the ports system.)
	
	plist_stripper pkg-plist.new \
		| sed "s|@dirrm man|@dirrmtry man|g" \
		| sed "s|@dirrm src$|@dirrmtry src|g" \
		| awk -F '.' ' { if (($2 == "so") && (NF == 4)) \
			    printf("%s.%s.%s\n",$1,$2,$3); \
			else \
			    print $0 }' \
		| awk ' { if ( $0 ~ "%%DOCSDIR%%" ) \
			    printf("%%%%PORTDOCS%%%%%s\n",$0); \
			else \
			    print $0 }' \
		| awk ' { if ( $0 ~ "%%EXAMPLESDIR%%" ) \
			    printf("%%%%PORTEXAMPLES%%%%%s\n",$0); \
			else \
			    print $0 }' \
		| awk ' { if ( $0 ~ "share/locale" ) \
			    printf("%%%%NLS%%%%%s\n",$0); \
			else \
			    print $0 }' \
		| egrep -v '(etc/rc.d|share/licenses|share/nls|share/pixmaps|include/X11|lib/X11|libdata/ldconfig)' \
	    > temp && mv -f temp pkg-plist.new
    
	# Build a list of files/directories added by the ports framework
	# to pkg-plist, used to strip the entries out of pkg-plist.new.
	make -V PLIST_FILES   PREFIX=${prefix} | tr -s ' ' '\n' | awk '$1 != "" { printf "^%s$\n", $1 }' > temp
	make -V PORTEXAMPLES  PREFIX=${prefix} | tr -s ' ' '\n' | awk '$1 != "" { printf "^%%%%PORTEXAMPLES%%%%%%%%EXAMPLESDIR%%%%/%s$\n", $1 }' >> temp
	make -V PORTDOCS      PREFIX=${prefix} | tr -s ' ' '\n' | awk '$1 != "" { printf "^%%%%PORTDOCS%%%%%%%%DOCSDIR%%%%/%s$\n", $1 }' >> temp
	# Special handling for directories: genplist doesn't handle "dirrmtry"
	# so we have to search "dirrm" instead.
	make -V PLIST_DIRS    PREFIX=${prefix} | tr -s ' ' '\n' | awk '$1 != "" { printf "^@dirrm %s$\n^@exec mkdir -p %%D/%s$\n", $1, $1 }' >> temp
	make -V PLIST_DIRSTRY PREFIX=${prefix} | tr -s ' ' '\n' | awk '$1 != "" { printf "^@dirrm %s$\n^@exec mkdir -p %%D/%s$\n", $1, $1 }' >> temp
	
	# If PORTDOCS is defined, remove DOCSDIR.
	if grep -q '%%PORTDOCS%%%%DOCSDIR%%/' temp; then
	    printf '^%%%%PORTDOCS%%%%@dirrm %%%%DOCSDIR%%%%$\n' >> temp
	fi
	
	# If PORTEXAMPLES is defined, remove EXAMPLESDIR.
	if grep -q '%%PORTEXAMPLES%%%%EXAMPLESDIR%%/' temp; then
	    printf '^%%%%PORTEXAMPLES%%%%@dirrm %%%%EXAMPLESDIR%%%%$\n' >> temp
	fi
    
	# Replace hard-coded paths with variables.
	plist_stripper temp > temp1 && mv -f temp1 plist.temp
	if [ -s plist.temp ]; then
	    # grep returns "1" if all lines are removed!
	    grep -v -f plist.temp pkg-plist.new > temp
	    mv -f temp pkg-plist.new
	fi
	rm -f OLD-DIRS plist.temp temp temp1
    
	# Look for port-specific "plugin" scripts containing expressions like
	# "sed -i '' -E -e 's|^(foobar)|%%WITHFOOBAR%%\1|' ${1}".
	PL=$(realpath pkg-plist.new)
	if [ -f ~/.genplist/${portname} -a -x ~/.genplist/${portname} ]; then
	    printf "\n $0: executing ~/.genplist/${portname} %s\n" ${PL}
	    ~/.genplist/${portname} ${PL}
	fi
    fi
    
    # If pkg-plist matches pkg-plist.new remove it, otherwise cmp output
    # will show a hint.
    if [ -f pkg-plist -a -f pkg-plist.new ]; then
	if cmp pkg-plist pkg-plist.new; then
	    remove_plist_new "No changes to package list"
	fi
	return ${EX_OK}
    fi
    
    # If pkg-plist.new is empty remove it.
    if [ -f pkg-plist.new -a ! -s pkg-plist.new ]; then
	remove_plist_new "Package list is empty."
	return ${EX_OK}
    fi
    
    return ${EX_TEMPFAIL}
}


##############################################
# Show differences between old and new plists

diff_plist()
{
    if [ -e pkg-plist.new ]; then
	diff -u pkg-plist pkg-plist.new
    else
	printf 'pkg-plist.new does not exist.\n'
	printf 'diff must come after create and before commit.\n'
    fi
    return ${EX_OK}
}


#############################
# Replace old plist with new

commit_plist()
{
    printf "This will overwrite pkg-plist with pkg-plist.new.\n"
    printf "Are you sure? (y/[n]) "
    read response
    if [ ${response}0 = "y0" ]; then
	mv -f pkg-plist.new pkg-plist
    else
	printf "Commit aborted.\n"
    fi
    return ${EX_OK}
}


check_leftovers()
{
    make PREFIX=${prefix} deinstall
    leftovers=`find ${prefix} \! -type d`
    if [ "${leftovers}0" = '0' ]; then
	printf 'No leftover files.  Deinstall looks clean.\n'
	return ${EX_OK}
    else
	printf 'Deinstall failed to remove some files:\n'
	printf "${leftovers}\n"
	return ${EX_CONFIG}
    fi
}


############################################################
# Test plist by uninstalling and looking for leftover files

test_plist()
{
    if [ -e pkg-plist.new ]; then
	printf "Error: pkg-plist.new still exists.\n"
	printf "You must run 'genplist commit' before 'genplist test'.\n"
	return ${EX_USAGE}
    fi
    prefix=`cat genplist.prefix`
    if [ $? -ne 0 ]; then
	return ${EX_UNAVAILABLE}
    fi
    
    # Standard
    make PREFIX=${prefix} deinstall > /dev/null 2>&1
    make PREFIX=${prefix} reinstall
    if ! check_leftovers; then
	return $?
    fi

    # NOPORTDOCS
    if fgrep -q '%%PORTDOCS%%' pkg-plist; then
	printf "Rebuild before testing NOPORTDOCS? (y/[n]) "
	read resp
	if [ 0$resp = 0y ]; then
	    make clean
	fi
	make PREFIX=${prefix} deinstall > /dev/null 2>&1
	make PREFIX=${prefix} NOPORTDOCS=1 reinstall
	if ! check_leftovers; then
	    return $?
	fi
    fi
    
    # NOPORTEXAMPLES
    if fgrep -q '%%PORTEXAMPLES%%' pkg-plist; then
	printf "Rebuild before testing NOPORTEXAMPLES? (y/[n]) "
	read resp
	if [ 0$resp = 0y ]; then
	    make clean
	fi
	printf "Testing with NOPORTEXAMPLES...\n"
	make PREFIX=${prefix} deinstall > /dev/null 2>&1
	make PREFIX=${prefix} NOPORTEXAMPLES=1 reinstall
	if ! check_leftovers; then
	    return $?
	fi
    fi
    
    return ${EX_OK}
}


############################################
# Clean up temporary installation directory

clean()
{
    make PREFIX=`cat genplist.prefix` deinstall
    if [ $? -ne 0 ]; then
	return ${EX_UNAVAILABLE}
    fi
    rm -rf `cat genplist.prefix`
    rm -f genplist.prefix pkg-plist.new man-plist.new
    return ${EX_OK}
}


usage()
{
    printf "Usage:\n"
    printf "\t$0 create <Temp PREFIX>\n"
    printf "\t$0 diff\n"
    printf "\t$0 commit\n"
    printf "\t$0 test\n"
    printf "\t$0 clean\n"
    exit ${EX_USAGE}
}

############################################################################
# Main program

# Codes from sysexits.h
EX_OK=0
EX__BASE=64
EX_USAGE=64
EX_DATAERR=65
EX_NOINPUT=66
EX_NOUSER=67
EX_NOHOST=68
EX_UNAVAILABLE=69
EX_SOFTWARE=70
EX_OSERR=71
EX_OSFILE=72
EX_CANTCREAT=73
EX_IOERR=74
EX_TEMPFAIL=75
EX_PROTOCOL=76
EX_NOPERM=77
EX_CONFIG=78

if [ $# -lt 1 ]; then
    usage
fi

case $1 in
"create")
    if [ $# -lt 2 ]; then
	usage
    fi
    create_plist $2
    status=$?
    ;;
"diff")
    diff_plist
    status=$?
    ;;
"commit")
    commit_plist
    status=$?
    ;;
"test")
    test_plist
    status=$?
    ;;
"clean")
    clean
    status=$?
    ;;
*)
    usage
    ;;
esac

exit $status

