#!/bin/sh
#
# Script to automatically install all Wine Staging patches
#
# Copyright (C) 2015-2017 Sebastian Lackner
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#

# Show usage information
usage()
{{
	echo ""
	echo "Usage: ./patchinstall.sh [DESTDIR=path] [--all] [-W patchset] [patchset ...]"
	echo ""
	echo "Autogenerated script to apply all Wine Staging patches on your Wine"
	echo "source tree."
	echo ""
	echo "Configuration:"
	echo "  DESTDIR=path         Specify the path to the wine source tree"
	echo "  --all                Select all patches"
	echo "  --force-autoconf     Run autoreconf and tools/make_requests after each patch"
	echo "  --help               Display this help and exit"
	echo "  --no-autoconf        Do not run autoreconf and tools/make_requests"
	echo "  --no-patchlist       Do not apply patchlist (needed for 'wine --patches')"
	echo "  --upstream-commit    Print the upstream Wine commit SHA1 and exit"
	echo "  --version            Show version information and exit"
	echo "  -W patchset          Exclude a specific patchset"
	echo ""
	echo "Backends:"
	echo "  --backend=patch      Use regular 'patch' utility to apply patches (default)"
	echo "  --backend=eapply     Use 'eapply' to apply patches (Gentoo only)"
	echo "  --backend=epatch     Use 'epatch' to apply patches (Gentoo only, deprecated)"
	echo "  --backend=git-am     Use 'git am' to apply patches"
	echo "  --backend=git-apply  Use 'git apply' to apply patches"
	echo "  --backend=stg        Import the patches using stacked git"
	echo ""
}}

# Get the upstream commit sha
upstream_commit()
{{
	echo "{upstream_commit}"
}}

# Show version information
version()
{{
	echo "{staging_version}"
	echo "Copyright (C) 2014-2019 the Wine Staging project authors."
	echo "Copyright (C) 2018-2019 Alistair Leslie-Hughes"
	echo ""
	echo "Patchset to be applied on upstream Wine:"
	echo "  commit $(upstream_commit)"
	echo ""
}}

# Critical error, abort
abort()
{{
	printf '%s\n' "ERROR: $1" >&2
	exit 1
}}

# Show a warning
warning()
{{
	printf '%s\n' "WARNING: $1" >&2
}}

{patch_helpers}

# Default settings
patch_enable_all 0
enable_patchlist=1
enable_autoconf=1
patchlist="/dev/null"
backend="patch"

# Find location of patches
patchdir="$(cd "$(dirname "$0")" && pwd)"
if test ! -f "$patchdir/patchinstall.sh"; then
	if test -f ./patchinstall.sh; then
		patchdir="$(pwd)"
	else
		abort "Failed to find patch directory."
	fi
fi

# Parse commandline arguments
if test "$#" -eq 0; then
	abort "No commandline arguments given, don't know what to do."
fi

while test "$#" -gt 0; do
	case "$1" in
		DESTDIR=*)
			DESTDIR="${{1#*=}}"
			shift
			;;

		--all)
			patch_enable_all 1
			shift
			;;

		--backend=*)
			backend="${{1#*=}}"
			shift
			;;

		--force-autoconf)
			enable_autoconf=2
			shift
			;;

		--help)
			usage
			exit 0
			;;

		--no-patchlist)
			enable_patchlist=0
			shift
			;;

		--no-autoconf)
			enable_autoconf=0
			shift
			;;

		--upstream-commit)
			upstream_commit
			exit 0
			;;

		--version)
			version
			exit 0
			;;

		-W)
			# Disable patchset
			if ! patch_enable "$2" 2; then
				abort "Wrong usage of -W commandline argument, expected patchname."
			fi
			shift
			shift
			;;

		*)
			# Enable patchset
			if ! patch_enable "$1" 1; then
				abort "Unknown commandline argument $1."
			fi
			shift
			;;
	esac
done

# Determine DESTDIR if not explicitly specified
if test -z "$DESTDIR" -a -f ./tools/make_requests; then
	DESTDIR="$(pwd)"

elif test ! -f "$DESTDIR/tools/make_requests"; then
	abort "DESTDIR does not point to the Wine source tree."
fi

# Change directory to DESTDIR, eapply/epatch depends on that
if ! cd "$DESTDIR"; then
	abort "Unable to change directory to $DESTDIR."
fi

# Helper to update configure / the wineserver protocol if required
if ! command -v diff >/dev/null 2>&1 ||
   ! command -v grep >/dev/null 2>&1 ||
   ! command -v cmp >/dev/null 2>&1; then

	update_configure()
	{{
		autoreconf -f
	}}

	update_protocol()
	{{
		./tools/make_requests
	}}

else

	update_configure()
	{{
		_file="./configure"

		if ! cp -a "$_file" "$_file.old"; then
			abort "failed to create $_file.old"
		fi

		if ! autoreconf -f; then
			rm "$_file.old"
			unset _file
			return 1
		fi

		# Shifting by 62 bits is undefined behaviour when off_t is 32-bit, see also
		# https://launchpad.net/ubuntu/+source/autoconf/2.69-6 - the bug is still
		# present in some other distros (including Archlinux).
		_large_off_old="^#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))\$"
		_large_off_new="#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))"
		sed -i'' -e "s|$_large_off_old|$_large_off_new|g" "$_file"
		unset _large_off_old _large_off_new

		# Restore original timestamp when nothing changed
		if ! cmp "$_file.old" "$_file" >/dev/null; then
			rm "$_file.old"
		else
			mv "$_file.old" "$_file"
		fi

		unset _file
		return 0
	}}

	update_protocol()
	{{
		_file="./include/wine/server_protocol.h"

		if ! cp -a "$_file" "$_file.old"; then
			abort "failed to create $_file.old"
		fi

		if ! ./tools/make_requests; then
			rm "$_file.old"
			unset _file
			return 1
		fi

		# Restore original timestamp when nothing changed
		if diff -u "$_file.old" "$_file" |
		   grep -v "^[+-]#define SERVER_PROTOCOL_VERSION" |
		   grep -v "^\(+++\|---\)" | grep -q "^[+-]"; then
			rm "$_file.old"
		else
			mv "$_file.old" "$_file"
		fi

		unset _file
		return 0
	}}
fi


# Most backends will try to use git, either directly or indirectly.
# Unfortunately this does not work when "$DESTDIR" points to a
# subdirectory of a git tree, which has the effect that no patches
# are applied, but the exitcode is zero. To avoid broken builds we
# will workaround this issue or abort.
test ! -d ".git" && git rev-parse --git-dir >/dev/null 2>&1
workaround_git_bug="$?"

# Apply the patches using gitapply.sh, a small wrapper around 'patch'
if test "$backend" = "patch"; then

	if test "$workaround_git_bug" -eq 0; then
		gitapply_args="--nogit"
	else
		gitapply_args=""
	fi

	if test "$enable_autoconf" -gt 1; then
		warning "Ignoring commandline argument --force-autoconf."
		enable_autoconf=1
	fi

	patch_apply_file()
	{{
		printf '%s\n' "Applying $1"
		if ! "$patchdir/gitapply.sh" $gitapply_args < "$1"; then
			abort "Failed to apply patch, aborting!"
		fi
	}}

# 'eapply/epatch' backend - used on Gentoo
elif test "$backend" = "eapply" -o "$backend" = "epatch"; then

	if test "$workaround_git_bug" -eq 0; then
		gitapply_args="--nogit"
	else
		gitapply_args=""
	fi

	if ! command -v "$backend"  >/dev/null 2>&1 || \
	   ! command -v ebegin      >/dev/null 2>&1 || \
	   ! command -v eend        >/dev/null 2>&1 || \
	   ! command -v nonfatal    >/dev/null 2>&1; then
		abort "Shell functions $backend/ebegin/eend/nonfatal not found. You have to source this script from your ebuild."
	fi

	if test "$enable_autoconf" -gt 1; then
		warning "Ignoring commandline argument --force-autoconf."
		enable_autoconf=1
	fi

	patch_apply_file()
	{{
		_shortname="$(basename "$1")"
		if grep -q "^GIT binary patch" "$1"; then
			ebegin "Applying $_shortname"
			"$patchdir/gitapply.sh" $gitapply_args < "$1"
			if ! eend $?; then
				exit 1
			fi
		else
			# we are run from a subshell, so we can't call die
			if ! nonfatal "$backend" "$1"; then
				exit 1
			fi
		fi
		unset _shortname
	}}

# GIT backend - apply patches using 'git am'
elif test "$backend" = "git" -o "$backend" = "git-am"; then

	if test "$workaround_git_bug" -eq 0; then
		abort "Backend 'git-am' not possible when DESTDIR points to a git subdirectory."
	fi

	patch_apply_file()
	{{
		printf '%s\n' "Applying $1"
		if ! git am "$1"; then
			abort "Failed to apply patch, aborting!"
		fi
		if test "$enable_autoconf" -gt 1; then
			_do_commit=0

			# Run 'autoreconf -f' if required
			if git show --pretty=format: --name-only | grep -q "^\(configure.ac\|aclocal.m4\)$"; then
				if ! update_configure; then
					abort "'autoreconf -f' failed."
				fi
				git add ./configure
				git add ./include/config.h.in
				_do_commit=1
			fi

			# Run './tools/make_requests' if required
			if git show --pretty=format: --name-only | grep -q "^server/"; then
				if ! update_protocol; then
					abort "'./tools/make_requests' failed."
				fi
				git add ./include/wine/server_protocol.h
				git add ./server/trace.c
				git add ./server/request.h
				_do_commit=1
			fi

			if test "$_do_commit" -ne 0; then
				if ! git commit --amend --reuse-message HEAD; then
					abort "Failed to include autogenerated changes in commit."
				fi
			fi

			unset _do_commit
		fi
	}}

# Git apply backend
elif test "$backend" = "git-apply"; then

	if test "$workaround_git_bug" -eq 0; then
		abort "Backend 'git-apply' not possible when DESTDIR points to a git subdirectory."
	fi

	if test "$enable_autoconf" -gt 1; then
		warning "Ignoring commandline argument --force-autoconf."
		enable_autoconf=1
	fi

	patch_apply_file()
	{{
		printf '%s\n' "Applying $1"
		if ! git apply "$1"; then
			abort "Failed to apply patch, aborting!"
		fi
	}}

# Stacked GIT backend - import the patches (mainly for developers)
elif test "$backend" = "stg"; then

	if test "$workaround_git_bug" -eq 0; then
		abort "Backend 'stg' not possible when DESTDIR points to a git subdirectory."
	fi

	# Only import the regular patches, no autogenerated ones -
	# moreover, don't run autoreconf or ./tools/make_requests.
	enable_patchlist=0
	enable_autoconf=0

	patch_apply_file()
	{{
		printf '%s\n' "Applying $1"
		_shortname="$(basename "$1")"
		if ! printf '%s\n' "staging/$_shortname" | cat - "$1" | stg import; then
			abort "Failed to apply patch, aborting!"
		fi
		unset _shortname
	}}

else
	abort "Selected backend $backend not supported."
fi

patch_apply()
{{
	patch_apply_file "$patchdir/$1"
}}


{patch_resolver}


# If autoupdate is enabled then create a tempfile to keep track of all patches
if test "$enable_patchlist" -eq 1; then
	if test "$enable_Staging" -eq 1; then
		# macOS 10.10 and prior do not allow mktemp with empty argument
		patchlist=$(mktemp 2>/dev/null || mktemp -t tmp)
		if test ! -f "$patchlist"; then
			abort "Unable to create temporary file for patchlist."
		fi
	else
		warning "Skipping generation of patchlist because 'Staging' patchset is disabled."
		enable_patchlist=0
	fi
fi


{patch_apply}


if test "$enable_patchlist" -eq 1; then

	# Generate a temporary patch containing the patchlist and apply it
	patch_data=$(cat "$patchlist" | sort)
	if test ! -z "$patch_data"; then
		patch_lines=$(printf '%s\n' "$patch_data" | wc -l)
		patch_lines=$((${{patch_lines}}+21))
		cat > "$patchlist" <<EOF
From: Wine Staging Team <webmaster@fds-team.de>
Subject: Autogenerated patch list.

diff --git a/libs/wine/config.c b/libs/wine/config.c
index 5262c76..0a3182f 100644
--- a/libs/wine/config.c
+++ b/libs/wine/config.c
@@ -478,10 +478,${{patch_lines}} @@ const char *wine_get_version(void)
     return PACKAGE_VERSION;
 }}
 
+static const struct
+{{
+    const char *author;
+    const char *subject;
+    int revision;
+}}
+wine_patch_data[] =
+{{
${{patch_data}}
+    {{ NULL, NULL, 0 }}
+}};
+
 /* return the applied non-standard patches */
 const void *wine_get_patches(void)
 {{
-    return NULL;
+    return &wine_patch_data[0];
 }}
 
 /* return the build id string */
EOF
		patch_apply_file "$patchlist"
	fi
	rm "$patchlist"
fi

if test "$enable_autoconf" -eq 1; then
	if ! update_configure; then
		abort "'autoreconf -f' failed."
	fi
	if ! update_protocol; then
		abort "'./tools/make_requests' failed."
	fi
fi
# Success
exit 0
