#!/bin/sh -ef
#
# The hsh utility for the hasher project.
#
# Copyright (C) 2003-2022  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2006  Alexey Gladkov <legion@altlinux.org>
# Copyright (C) 2007  Kirill A. Shutemov <kas@altlinux.org>
# Copyright (C) 2020-2024  Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
# All rights reserved.
#
# SPDX-License-Identifier: GPL-2.0-or-later
#

. hsh-sh-functions

show_help()
{
	cat <<EOF
hsh - build package using hasher utilities.

Usage: $PROG [options] [<path-to-workdir>] <package>...
  or:  $PROG [options] --workdir=DIR <package>...
  or:  $PROG [options] --initroot-only [<path-to-workdir>]
  or:  $PROG [options] --initroot-only --workdir=DIR
  or:  $PROG [options] --cleanup-only [<path-to-workdir>]
  or:  $PROG [options] --cleanup-only --workdir=DIR
  or:  $PROG [options] --printenv-only [<path-to-workdir>]
  or:  $PROG [options] --printenv-only --workdir=DIR

<path-to-workdir> must be valid writable directory.

Options:
  --apt-config=FILE                 path to custom apt.conf file;
  --apt-prefix=DIR                  path to apt directory prefix (e.g. /usr);
  --build-args=ARGS                 a deprecated synonym for --rpmbuild-args;
  --build-srpm-only                 do not build binary packages, produce src.rpm only;
  --cleanup-only                    do not build at all, just cleanup workdir;
  --eager-cleanup                   cleanup build environment after each
                                    successful build;
  --lazy-cleanup                    cleanup build environment before each
                                    new build;
  --excludedocs                     do not install documentation files;
  --hasher-priv-dir=DIR             alternative hasher-priv directory;
  --initroot-only                   do not build, stop after initroot stage;
  --printenv                        print configuration environment variables;
  --install-langs=LIST              colon-separated list of languages to install;
  --mountpoints=LIST                comma-separated list of known mount points;
  --repo-mountpoints=LIST           comma-separated list of mountpoints
                                    containing a repo used for image building;
  --cache-compress=FILE             alternative cache compress executable;
  --cache-dir=DIR                   alternative cache directory;
  --no-cache                        do not use initroot cache;
  --no-contents-indices             do not use content indices;
  --no-repackage-source             do not repackage source along with binaries;
  --no-sisyphus-check-in[=LIST]     do not run sisyphus_check input tests
                                    [specified in comma-separated LIST];
  --no-sisyphus-check[=LIST]        do not run sisyphus_check tests
                                    [specified in comma-separated LIST];
  --no-sisyphus-check-out[=LIST]    do not run sisyphus_check output tests
                                    [specified in comma-separated LIST];
  --nprocs=NUMBER                   number of CPUs to use;
  --number=NUMBER                   subconfig identifier;
  --packager=EMAIL                  override default RPM packager tag;
  --pkg-build-list=LIST             override default build package file list;
  --pkg-init-list=LIST              override default initial package file list;
  --predb-prog=FILE                 program to run inside the chroot after unpacking
                                    of init list packages, but before rpmi --justdb;
  --query-repackage                 repackage the source before query
                                    for requirements (default);
  --no-query-repackage              query original source package
                                    for requirements;
  --query-req-prog=FILE             program to run to query for requirements
                                    instead of autogenerated script;
  --rebuild-prog=FILE               program to run for rebuild instead of
                                    autogenerated script;
  --repackage-source                repackage source along with binaries;
  --repo=DIR                        repository directory;
  --repo-bin=DIR                    binary packages destination directory,
                                    overriding --repo option for binary packages;
  --repo-src=DIR                    source packages destination directory,
                                    overriding --repo option for source packages;
  --images=DIR                      image output directory;
  --rpmbuild-args=ARGS              extra arguments for rpmbuild;
  --save-fakeroot                   save fakeroot state;
  --target=ARCH                     target architecture;
  --wait-lock                       wait for workdir and hasher-priv locks;
  --no-wait-lock                    do not wait for workdir and hasher-priv locks;
  --img-apt-config                  path to a custom apt.conf file used for image building;
  --without-stuff                   do not use built packages;
  --with-stuff                      allow use of built packages;
  --with-qemu=ARCH                  copy qemu executable into the chroot .host directory;
  --workdir=DIR                     path to workdir to use;
  -q, --quiet                       try to be more quiet;
  -v, --verbose                     print a message for each action;
  -V, --version                     print program version and exit;
  -h, --help                        show this text and exit.

Report bugs to https://bugzilla.altlinux.org/

EOF
	exit
}

TEMP=`getopt -n $PROG -o $getopt_common_opts -l apt-config:,apt-prefix:,build-args:,build-srpm-only,cache-compress:,cache-dir:,cleanup-only,contents-index-all:,contents-index-bin:,eager-cleanup,excludedocs,hasher-priv-dir:,initroot-only,install-langs:,lazy-cleanup,mountpoints:,repo-mountpoints:,no-cache,no-contents-indices,no-repackage-source,no-sisyphus-check::,no-sisyphus-check-in::,no-sisyphus-check-out::,no-stuff,nprocs:,number:,packager:,pkg-build-list:,pkg-init-list:,predb-prog:,query-repackage,no-query-repackage,query-req-prog:,printenv,rebuild-prog:,repackage-source,repo:,repo-bin:,repo-src:,images:,rpmbuild-args:,save-fakeroot,target:,wait-lock,no-wait-lock,img-apt-config:,without-stuff,with-stuff,with-qemu:,workdir:,$getopt_common_longopts -- "$@"` ||
	show_usage
prepare_cgroup "$TEMP" "$0" "$@"
eval set -- "$TEMP"

cleanup_only=
initroot_only=
printenv_only=
build_srpm_only=
query_repackage_option=
workdir_from_opt=
while :; do
	case "$1" in
		--apt-config)
			apt_config="$(opt_check_read "$1" "$2")"
			shift
			;;
		--apt-prefix)
			apt_prefix="$(opt_check_dir "$1" "$2")"
			shift
			;;
		--build-srpm-only) build_srpm_only="$1"
			;;
		--cache-compress) shift; cache_compress="$1"
			;;
		--cache-dir) shift; cache_dir="$1"
			;;
		--cleanup-only) cleanup_only=1
			;;
		--eager-cleanup) lazy_cleanup=
			;;
		--excludedocs) exclude_docs=--excludedocs
			;;
		--hasher-priv-dir)
			hasher_priv_dir="$(opt_check_dir "$1" "$2")"
			shift
			;;
		--initroot-only) initroot_only=1
			;;
		--install-langs) shift; install_langs="$1"
			;;
		--lazy-cleanup) lazy_cleanup=1
			;;
		--mountpoints) shift; known_mountpoints="$1"
			;;
		--repo-mountpoints) shift; repo_mountpoints="$1"
			;;
		--no-cache) no_cache=1
			;;
		--no-contents-indices) no_contents_indices=1
			;;
		--no-repackage-source|--repackage-source) repackage_source="$1"
			;;
		--no-sisyphus-check) shift
			[ -n "$1" ] && no_sisyphus_check="$1" || no_sisyphus_check=all
			;;
		--no-sisyphus-check-in) shift
			[ -n "$1" ] && no_sisyphus_check_in="$1" || no_sisyphus_check_in=all
			;;
		--no-sisyphus-check-out) shift
			[ -n "$1" ] && no_sisyphus_check_out="$1" || no_sisyphus_check_out=all
			;;
		--nprocs)
			nprocs="$(opt_check_number "$1" "$2")"
			shift
			;;
		--number)
			if [ -z "${2##/*}" ]; then
				[ -d "$2" ] ||
					fatal "$1: $2: Directory not available."
				number="$2"
			else
				number="$(opt_check_number_ge_0 "$1" "$2")"
			fi
			shift
			;;
		--packager) shift; packager="$1"
			;;
		--pkg-build-list) shift; build_list="$1 "
			;;
		--pkg-init-list) shift; init_list="$1 "
			;;
		--predb-prog)
			prog_predb="$(opt_check_read "$1" "$2")"
			shift
			;;
		--printenv) printenv_only=1
			;;
		--query-repackage)
			query_repackage=1
			query_repackage_option="$1"
			;;
		--no-query-repackage)
			query_repackage=
			query_repackage_option="$1"
			;;
		--query-req-prog)
			prog_query_req="$(opt_check_read "$1" "$2")"
			shift
			;;
		--rebuild-prog)
			prog_rebuild="$(opt_check_read "$1" "$2")"
			shift
			;;
		--repo) shift; repo="$1"
			;;
		--repo-bin) shift; repo_bin="$1"
			;;
		--repo-src) shift; repo_src="$1"
			;;
		--images) shift; images="$1"
			;;
		--rpmbuild-args|--build-args) shift; rpmargs="$1"
			;;
		--save-fakeroot) save_fakeroot=1
			;;
		--target) shift; target="$1"
			[ -z "${target##[A-Za-z]*}" ] ||
				fatal "--target: $target: invalid architecture."
			;;
		--wait-lock) lock_nowait=
			;;
		--no-wait-lock) lock_nowait=1
			;;
		--img-apt-config) shift; img_apt_config="$1"
			;;
		--without-stuff|--no-stuff) no_stuff=1
			;;
		--with-stuff) no_stuff=
			;;
		--with-qemu)
			qemu_arch="$2"
			shift
			;;
		--workdir) shift; workdir="$1"
			workdir_from_opt=1
			;;
		--contents-index-*)
			message "Warning: obsolete option $1 ignored."
			shift
			;;
		--) shift; break
			;;
		*) parse_common_option "$1"
			;;
	esac
	shift
done

[ "$repackage_source" = '--no-repackage-source' ] ||
	repackage_source='--repackage-source'

case "${initroot_only:+1}${cleanup_only:+1}${printenv_only:+1}" in
	11*) show_usage '--initroot-only, --cleanup-only and --printenv are mutually exclusive options.' ;;
esac

if [ -n "$prog_query_req" ] && [ -n "$query_repackage_option" ]; then
	show_usage "--query-req-prog and $query_repackage_option are mutually exclusive options."
fi

if [ -z "$workdir_from_opt" ]; then
	if [ -z "$workdir" ] || [ -d "${1-}" ]; then
		# At least one argument.
		[ "$#" -ge 1 ] || show_usage 'Insufficient arguments.'
		workdir="$1"
		shift
	fi
fi

if [ -z "$initroot_only" -a -z "$cleanup_only" -a -z "$printenv_only" ]; then
	# At least one argument.
	[ "$#" -ge 1 ] || show_usage 'Insufficient arguments.'
else
	# No arguments.
	[ "$#" -eq 0 ] || show_usage 'Too many arguments.'
fi

set_workdir

for f; do
	[ -z "${f##/*}" ] || f="$saved_cwd/$f"
	[ -e "$f" ] ||
		fatal "$f: file not found."
	[ -f "$f" ] ||
		fatal "$f: not a regular file."
	[ -r "$f" ] ||
		fatal "$f: cannot access source."
	[ -s "$f" ] ||
		fatal "$f: empty source file."
done

[ -z "$nprocs" ] ||
	opt_check_number nprocs "$nprocs" > /dev/null

cleanup_workdir()
{
	if [ -d chroot ]; then
		deduce_lock_hasher_priv
		hsh-rmchroot --no-lock "$workdir" \
			${hasher_priv_dir:+--hasher-priv-dir="$hasher_priv_dir"} \
			${quiet:+$quiet} \
			${verbose:+$verbose} \
			${number:+--number=$number} \
			&&
			verbose 'Removed chroot.' ||
			fatal 'Failed to remove chroot.'
	fi
	if [ -d aptbox ]; then
		rm -rf aptbox &&
			verbose 'Removed aptbox.' ||
			fatal 'Failed to remove aptbox.'
	fi
	if [ -d img-workdir ]; then
		rm -rf img-workdir &&
			verbose 'Removed img-workdir.' ||
			fatal 'Failed to remove img-workdir.'
	fi
	if [ -d cache ]; then
		rm -rf cache &&
			verbose 'Removed cache.' ||
			fatal 'Failed to remove cache.'
	fi
	if [ -d repo ]; then
		rm -rf repo &&
			verbose 'Removed repo.' ||
			fatal 'Failed to remove repo.'
	fi
}

handle_package()
{
	local f=
	if [ $# -ge 1 ]; then
		f="$1"; shift
	fi
	[ -z "${f##/*}" ] || f="$saved_cwd/$f"

	rm -rf aptbox
	if [ -d chroot ]; then
		deduce_lock_hasher_priv
		hsh-rmchroot --no-lock "$workdir" \
		${hasher_priv_dir:+--hasher-priv-dir="$hasher_priv_dir"} \
		${quiet:+$quiet} \
		${verbose:+$verbose} \
		${number:+--number=$number} \
		#
	else
		if [ -n "$number" -a -z "${number##/*}" ]; then
			deduce_lock_hasher_priv "$number"
		else
			allocate_lock_hasher_priv
		fi
	fi

	mkaptbox --no-lock "$workdir" \
		${target:+--target="$target"} \
		${repo:+--repo="$repo"} \
		${repo_bin:+--repo-bin="$repo_bin"} \
		${repo_src:+--repo-src="$repo_src"} \
		${apt_config:+--apt-config="$apt_config"} \
		${apt_prefix:+--apt-prefix="$apt_prefix"} \
		${quiet:+$quiet} \
		${verbose:+$verbose} \
		${no_stuff:+--no-stuff} \
		#
	hsh-mkchroot --no-lock "$workdir" \
		${hasher_priv_dir:+--hasher-priv-dir="$hasher_priv_dir"} \
		${quiet:+$quiet} \
		${verbose:+$verbose} \
		${number:+--number=$number} \
		${qemu_arch:+--with-qemu=$qemu_arch} \
		#
	hsh-initroot --no-lock "$workdir" \
		${hasher_priv_dir:+--hasher-priv-dir="$hasher_priv_dir"} \
		${quiet:+$quiet} \
		${verbose:+$verbose} \
		${cache_compress:+--cache-compress="$cache_compress"} \
		${cache_dir:+--cache-dir="$cache_dir"} \
		${no_cache:+--no-cache} \
		${no_contents_indices:+--no-contents-indices} \
		${repackage_source:+$repackage_source} \
		${exclude_docs:+--excludedocs} \
		${save_fakeroot:+--save-fakeroot} \
		${number:+--number=$number} \
		${nprocs:+--nprocs=$nprocs} \
		${install_langs:+--install-langs="$install_langs"} \
		${packager:+--packager="$packager"} \
		${init_list:+--pkg-init-list="$init_list"} \
		${build_list:+--pkg-build-list="$build_list"} \
		${prog_predb:+--predb-prog="$prog_predb"} \
		#
	[ -n "$f" ] || return 0
	hsh-rebuild --no-lock "$workdir" \
		${target:+--target="$target"} \
		${repo:+--repo="$repo"} \
		${repo_bin:+--repo-bin="$repo_bin"} \
		${repo_src:+--repo-src="$repo_src"} \
		${hasher_priv_dir:+--hasher-priv-dir="$hasher_priv_dir"} \
		${known_mountpoints:+--mountpoints="$known_mountpoints"} \
		${repo_mountpoints:+--repo-mountpoints="$repo_mountpoints"} \
		${quiet:+$quiet} \
		${verbose:+$verbose} \
		${img_apt_config:+--img-apt-config=$img_apt_config} \
		${no_stuff:+--no-stuff} \
		${exclude_docs:+--excludedocs} \
		${save_fakeroot:+--save-fakeroot} \
		${no_sisyphus_check:+--no-sisyphus-check="$no_sisyphus_check"} \
		${no_sisyphus_check_in:+--no-sisyphus-check-in="$no_sisyphus_check_in"} \
		${no_sisyphus_check_out:+--no-sisyphus-check-out="$no_sisyphus_check_out"} \
		$build_srpm_only \
		${prog_rebuild:+--rebuild-prog="$prog_rebuild"} \
		${prog_query_req:+--query-req-prog="$prog_query_req"} \
		$query_repackage_option \
		${number:+--number=$number} \
		${rpmargs:+--rpmbuild-args="$rpmargs"} \
		-- "$f"
	if [ -z "$lazy_cleanup" -a -z "$initroot_only" ]; then
		[ ! -d chroot ] || hsh-rmchroot --no-lock "$workdir" \
		${hasher_priv_dir:+--hasher-priv-dir="$hasher_priv_dir"} \
		${quiet:+$quiet} \
		${verbose:+$verbose} \
		${number:+--number=$number} \
		#
		rm -rf aptbox
	fi
}

if [ -n "$printenv_only" ]; then
	set | sed -n 's/^hsh_cfg_var_\([^=]\+\)=$/\1/p' |
	while read i; do
		eval quote_shell_variable q "\$$i"
		[ -z "$q" ] ||
			printf '%s="%s"\n' "$i" "$q"
	done
	exit
fi

lock_workdir

if [ -n "$cleanup_only" ]; then
	cleanup_workdir
elif [ -n "$initroot_only" ]; then
	handle_package
else
	for f; do
		handle_package "$f"
	done
fi
