#!/usr/bin/env bash

# fail hard
set -o pipefail
# fail harder
set -eu

source $(dirname $BASH_SOURCE)/_util/include/manifest.sh

OUT_PREFIX=$1

dep_formula=${0#$WORKSPACE_DIR/}
dep_name=$(basename $BASH_SOURCE)
dep_version=${dep_formula#"${dep_name}-"}
dep_package=${dep_name}-${dep_version}
dep_dirname=php-${dep_version}
dep_archive_name=${dep_dirname}.tar.gz
if [[ $dep_version == *alpha* ]] || [[ $dep_version == *beta* ]] || [[ $dep_version == *RC* ]]; then
	if [[ $dep_version == 7.3.* ]]; then
		dep_url=https://downloads.php.net/~cmb/${dep_archive_name}
	elif [[ $dep_version == 7.4.* ]]; then
		dep_url=https://downloads.php.net/~derick/${dep_archive_name}
	elif [[ $dep_version == 8.0.* ]]; then
		dep_url=https://downloads.php.net/~carusogabriel/${dep_archive_name}
	elif [[ $dep_version == 8.1.* ]]; then
		dep_url=https://downloads.php.net/~ramsey/${dep_archive_name}
	elif [[ $dep_version == 8.2.* ]]; then
		dep_url=https://downloads.php.net/~pierrick/${dep_archive_name}
	elif [[ $dep_version == 8.3.* ]]; then
		dep_url=https://downloads.php.net/~eric/${dep_archive_name}
	fi
else
	dep_url=https://www.php.net/distributions/${dep_archive_name}
fi
dep_manifest=${dep_package}.composer.json

echo "-----> Building ${dep_package}..."

curl -L ${dep_url} | tar xz

pushd ${dep_dirname}

# we need libgmp for GMP, libpam0g/libc-client for IMAP, libicu for intl, libsasl2/krb/ldap for LDAP, libreadline for PHP
needed=( libgmp10 libpam0g libc-client2007e libsasl2-2 libkrb5-3 )
needed+=( libonig5 )
needed+=( libsodium23 )
if [[ $STACK == "heroku-20" ]]; then
	needed+=( libicu66 )
	needed+=( libldap-2.4-2 )
	needed+=( libreadline8 )
	needed+=( libzip5 )
elif [[ $STACK == "heroku-22" ]]; then
	needed+=( libicu70 )
	needed+=( libldap-2.5-0 )
	needed+=( libreadline8 )
	needed+=( libzip4 ) # went back from ABI v5 to v4 in 1.6 or 1.7
else
	needed+=( libicu74 )
	needed+=( libldap2 )
	needed+=( libreadline8t64 )
	needed+=( libzip4t64 )
fi
missing=$(comm -1 -3 <(dpkg-query -W -f '${package}\n' | sort) <(IFS=$'\n'; echo "${needed[*]}" | sort))
if [[ "$missing" ]]; then
	echo "Error! Missing libraries: $missing"
	exit 1
fi

# we need libgmp-dev for GMP, libpam0g-dev/libc-client-dev for IMAP, libicu-dev for intl, libsasl2/krb5/ldap2-dev for LDAP, libreadline-dev for PHP
needed=( libgmp-dev libpam0g-dev libc-client2007e-dev libicu-dev libsasl2-dev libkrb5-dev libldap2-dev libonig-dev libreadline-dev libsodium-dev libsqlite3-dev libzip-dev libwebp-dev )
missing=$(comm -1 -3 <(dpkg-query -W -f '${package}\n' | sort) <(IFS=$'\n'; echo "${needed[*]}" | sort))
if [[ "$missing" ]]; then
	apt-get update -qq || { echo "Failed to 'apt-get update'. You must build this formula using Docker."; exit 1; }
	apt-get install -q -y $missing
fi

# freetype-config is gone on 20.04, but 7.3 doesn't use pkg-config yet
if [[ $STACK == "heroku-20" && $dep_version == 7.3.* ]]; then
	patch -p1 < "$(dirname "$BASH_SOURCE")/patches/php/7/3/freetype-pkgconfig.patch"
	# we changed config.m4 for GD, re-generate configure
	./buildconf --force
fi
# all following sed "patches" modify configure directly, so we must do that after the above buildconf re-gen

# we want to build FPM with tracing using pread, which uses /proc/$pid/mem
# depending on host/container capabilities, it will likely detect ptrace as present, but that's blocked on the platform
# the only easy way to force it to use pread is by patching configure so it assumes the ptrace check has failed
# see the AC_DEFUN([AC_FPM_TRACE] bits in sapi/fpm/config.m4
sed -i 's/have_ptrace=yes/have_ptrace=no/' configure

if [[ $dep_version == 7.4.* ]]; then
	patch -p1 < "$(dirname $BASH_SOURCE)/patches/php/7/4/ignoresigterm.patch"
elif [[ $dep_version == 7.* ]]; then
	patch -p1 < "$(dirname $BASH_SOURCE)/patches/php/7/ignoresigterm.patch"
else
	patch -p1 < "$(dirname $BASH_SOURCE)/patches/php/ignoresigterm.patch"
fi

configureopts=()
if [[ $dep_version == 7.3.* ]]; then
	configureopts+=("--enable-opcache-file" "--enable-zip")
	configureopts+=("--with-sqlite3=shared,/usr" "--with-pdo-sqlite=shared,/usr")
	configureopts+=("--with-gd=shared" "--with-freetype-dir=/usr" "--with-jpeg-dir=/usr" "--with-png-dir=/usr" "--with-webp-dir=/usr") # for ext-gd
else
	configureopts+=("--with-zip")
	configureopts+=("--with-sqlite3=shared" "--with-pdo-sqlite=shared")
	configureopts+=("--enable-gd=shared" "--with-freetype" "--with-jpeg" "--with-webp") # for ext-gd
fi
if [[ $dep_version != 8.* ]]; then
	configureopts+=("--with-xmlrpc=shared")
fi
if [[ $dep_version == 7.3.* ]]; then
	configureopts+=("--with-libzip=/usr" "--with-onig=/usr") # PHP before 7.4 bundles these, so give explicit locations
fi

export PATH=${OUT_PREFIX}/bin:$PATH
# cannot be built shared: date, ereg, opcache (always), pcre, reflection, sockets (?), spl, standard,
# sqlite3 and pdo_sqlite are on by default but we're building them shared on purpose
./configure \
	--prefix=${OUT_PREFIX} \
	--with-config-file-path=/app/.heroku/php/etc/php \
	--with-config-file-scan-dir=/app/.heroku/php/etc/php/conf.d \
	--disable-phpdbg \
	--enable-fpm \
	--with-bz2 \
	--with-curl \
	--with-pdo-mysql \
	--with-mysqli \
	--with-openssl \
		--with-kerberos \
	--with-password-argon2 \
	--with-pgsql \
	--with-pdo-pgsql \
	--with-readline \
	--enable-sockets \
	--with-zlib \
	--enable-bcmath=shared \
	--enable-calendar=shared \
	--enable-exif=shared \
	--enable-ftp=shared \
	--with-gettext=shared \
	--with-gmp=shared \
	--with-imap=shared \
		--with-imap-ssl \
	--enable-intl=shared \
	--with-ldap=shared \
		--with-ldap-sasl \
	--enable-mbstring=shared \
	--enable-pcntl=shared \
	--enable-shmop=shared \
	--enable-soap=shared \
	--with-sodium=shared \
	--with-xsl=shared \
	"${configureopts[@]}"
make -s -j $(($(nproc)+1))
make install -s
find ${OUT_PREFIX} -type f \( -executable -o -name '*.a' \) -exec sh -c "file -i '{}' | grep -Eq 'application/x-(archive|(pie-)?executable|sharedlib); charset=binary'" \; -print | xargs strip --strip-unneeded
popd

rm -rf ${OUT_PREFIX}/php/man ${OUT_PREFIX}/lib/php/extensions/*/*.a

if [[ $dep_version == 7.[1-3].* ]]; then
	echo "-----> Preparing PECL..."
	${OUT_PREFIX}/bin/pecl channel-update pecl.php.net
fi

echo "-----> Preparing php.ini..."
mkdir -p ${OUT_PREFIX}/etc/php/conf.d
# we begin with PHP's recommended production config as the default
cp ${dep_dirname}/php.ini-production ${OUT_PREFIX}/etc/php/php.ini
# next, include any more specific config files that we have created
IFS='.' read -r -a version <<< "$dep_version" # read the parts of $dep_version into an array, e.g. (7 2 33)
# iterate over version parts so we try "" (from index 0) first, then "7", then "7/2/", then "7/2/11" for a version "7.2.11"
# in each case, we copy anything that's in that directory; more specific version configs will this overwrite less specific ones
echo "-----> Copying version-specific php.ini files..."
for (( i = 0; i < ${#version[@]}; i++)); do
	version_dir=$(IFS=/; echo "${version[*]:0:$i}") # set IFS to "/" for merging, but echo is a builtin, so it must be a subshell and a separate command
	cp -v $(dirname $BASH_SOURCE)/_conf/php/${version_dir}/php.ini ${OUT_PREFIX}/etc/php/ 2> /dev/null || true
	cp -v $(dirname $BASH_SOURCE)/_conf/php/${version_dir}/conf.d/*.ini ${OUT_PREFIX}/etc/php/conf.d/ 2> /dev/null || true
done

echo "-----> Generating export and profile scripts..."
# this gets sourced after package install, so that the buildpack and following buildpacks can invoke
cat > ${OUT_PREFIX}/bin/export.php.sh <<'EOF'
export PATH="/app/.heroku/php/bin:/app/.heroku/php/sbin:$PATH"
EOF
# this gets sourced on dyno boot
cat > ${OUT_PREFIX}/bin/profile.php.sh <<'EOF'
export PATH="$HOME/.heroku/php/bin:$HOME/.heroku/php/sbin:$PATH"

# read memory limit of dyno
mlib="/sys/fs/cgroup/memory/memory.limit_in_bytes"
# get php.ini location; don't forget to suppress INI scan dir to prevent e.g. New Relic from starting
php_ini_path=$(PHP_INI_SCAN_DIR= php -r 'echo get_cfg_var("cfg_file_path");')
if [[ -f "$mlib" && -n "$php_ini_path" ]]; then
	php_cli_ini_path=$(dirname "$php_ini_path")/php-cli.ini
	# create php-cli.ini from php.ini unless it exists
	# we can't cp -n instead because php_ini_path would already be php-cli.ini and that would error
	if [[ "$php_ini_path" != "$php_cli_ini_path" ]]; then
		cp "$php_ini_path" "$php_cli_ini_path"
	fi
	# compute memory limit, up to 16 GB
	max_memory_limit=$(( 16 * 1024 * 1024 * 1024 ))
	sys_memory_limit=$(cat "$mlib")
	memory_limit=$(( sys_memory_limit > max_memory_limit ? max_memory_limit : sys_memory_limit ))
	# append to php-cli.ini
	echo $'\n;inserted by ~/.profile.d/ script' >> "$php_cli_ini_path"
	echo "memory_limit=$memory_limit" >> "$php_cli_ini_path"
fi

EOF

# we need composer to extract all extensions with versions
curl -sL https://getcomposer.org/installer > composer-setup.php
if ! curl -sL https://composer.github.io/installer.sha384sum | sha384sum --quiet -c -; then
	>&2 echo 'ERROR: Invalid installer signature'
	rm composer-setup.php
	exit 1
fi

php composer-setup.php --2.2 # https://github.com/composer/composer/issues/11046

# first, read all platform packages (just "ext-" and "php-") that are already there; could be statically built, or enabled through one of the INIs loaded
platform_default=$(php composer.phar show --platform | grep -E '^(ext-\S+|php-\S+)' | sed s@^@heroku-sys/@ | tr -s " " | cut -d " " -f1,2 | sort)

# next enable all bundled shared extensions temporarily so we can fetch their info too
echo "-----> Preparing built-in extensions..."
for f in ${OUT_PREFIX}/lib/php/extensions/*/*.so; do
	if [[ $(basename $f) == "opcache.so" ]]; then
		# opcache needs to be loaded using zend_extension
		echo -n "zend_" >> ${OUT_PREFIX}/etc/php/conf.d/999-sharedexts.ini
	fi
	# if an extension is already loaded (due to a config loaded further above), there will be a warning on startup, but that doesn't matter for our purposes
	echo "extension=$(basename $f)" >> ${OUT_PREFIX}/etc/php/conf.d/999-sharedexts.ini
done

# now we'll get all platform packages (just "ext-" and "php-")
platform_all=$(php composer.phar show --platform | grep -E '^(ext-\S+|php-\S+)' | sed s@^@heroku-sys/@ | tr -s " " | cut -d " " -f1,2 | sort)

# remove temporary ini file that enables all extensions, and the composer download
rm ${OUT_PREFIX}/etc/php/conf.d/999-sharedexts.ini composer.phar

# extract only additions (that's the extensions people have to explicitly enable) from the diff
exts_shared=$(diff --new-line-format="%L" --old-line-format="" --unchanged-line-format="" <(cat <<<"$platform_default") <(cat <<<"$platform_all")) || diff_result=$?
(( $diff_result == 1 )) || { echo "Failed to diff list of shared extensions"; exit 1; }

echo "-----> Preparing manifest contents..."
MANIFEST_REQUIRE="${MANIFEST_REQUIRE:-"{}"}"
MANIFEST_CONFLICT="${MANIFEST_CONFLICT:-"{}"}"
MANIFEST_REPLACE=$(echo "$platform_default" | sed "s/ $dep_version\$/ self.version/" | python -c 'import sys, json; json.dump({item[0]:item[1] for item in [line.split() for line in sys.stdin]}, sys.stdout)')
MANIFEST_PROVIDE="${MANIFEST_PROVIDE:-"{}"}"
MANIFEST_EXTRA=$(echo "$exts_shared" | python -c 'import sys, json; json.dump({"shared": dict(item.split() for item in sys.stdin), "export": "bin/export.php.sh", "profile": "bin/profile.php.sh"}, sys.stdout)')

echo "-----> Generating manifest..."
python $(dirname $BASH_SOURCE)/_util/include/manifest.py "heroku-sys-php" "heroku-sys/${dep_name}" "$dep_version" "${dep_formula}.tar.gz" "$MANIFEST_REQUIRE" "$MANIFEST_CONFLICT" "$MANIFEST_REPLACE" "$MANIFEST_PROVIDE" "$MANIFEST_EXTRA" > $dep_manifest

print_or_export_manifest_cmd "$(generate_manifest_cmd "$dep_manifest")"
