termux-packages/scripts/bin/update-packages
Aditya Alok d72177e5ce
feat(auto-update): create issue if update fails
- now a new issue with output logs will be created and assigned to
  $GITHUB_ACTOR, if auto update fails for a package

Signed-off-by: Aditya Alok <dev.aditya.alok@gmail.com>
2022-04-23 09:47:32 +05:30

228 lines
8.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck source-path=/data/data/com.termux/files/home/termux-packages
set -u
# Following variables should be set in environment outside of this script.
# Build updated packages.
: "${BUILD_PACKAGES:=false}"
# Commit changes to Git.
: "${GIT_COMMIT_PACKAGES:=false}"
# Push changes to remote.
: "${GIT_PUSH_PACKAGES:=false}"
export TERMUX_PKG_UPDATE_METHOD="" # Which method to use for updating? (repology, github or gitlab)
export TERMUX_PKG_UPDATE_TAG_TYPE="" # Whether to use latest-release-tag or newest-tag.
export TERMUX_GITLAB_API_HOST="gitlab.com" # Default host for gitlab-ci.
export TERMUX_PKG_AUTO_UPDATE=false # Whether to auto-update or not. Disabled by default.
export TERMUX_PKG_UPDATE_VERSION_REGEXP="" # Regexp to extract version.
export TERMUX_REPOLOGY_DATA_FILE
TERMUX_REPOLOGY_DATA_FILE="$(mktemp -t termux-repology.XXXXXX)" # File to store repology data.
export TERMUX_SCRIPTDIR
TERMUX_SCRIPTDIR="$(realpath "$(dirname "$0")/../..")" # Script directory.
# Define few more variables used by scripts.
# shellcheck source=scripts/properties.sh
. "${TERMUX_SCRIPTDIR}/scripts/properties.sh"
# Utility function to write error message to stderr.
# shellcheck source=scripts/updates/utils/termux_error_exit.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/utils/termux_error_exit.sh
# Utility function to write updated version to build.sh.
# shellcheck source=scripts/updates/utils/termux_pkg_upgrade_version.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/utils/termux_pkg_upgrade_version.sh
# Utility function to check if package needs to be updated, based on version comparison.
# shellcheck source=scripts/updates/utils/termux_pkg_is_update_needed.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/utils/termux_pkg_is_update_needed.sh
# Wrapper around github api to get latest release or newest tag.
# shellcheck source=scripts/updates/api/termux_github_api_get_tag.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/api/termux_github_api_get_tag.sh
# Wrapper around gitlab api to get latest release or newest tag.
# shellcheck source=scripts/updates/api/termux_gitlab_api_get_tag.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/api/termux_gitlab_api_get_tag.sh
# Function to get latest version of a package as per repology.
# shellcheck source=scripts/updates/api/termux_repology_api_get_latest_version.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/api/termux_repology_api_get_latest_version.sh
# Default auto update script for packages hosted on github.com. Should not be overrided by build.sh.
# To use custom algorithm, one should override termux_pkg_auto_update().
# shellcheck source=scripts/updates/internal/termux_github_auto_update.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/internal/termux_github_auto_update.sh
# Default auto update script for packages hosted on hosts using gitlab-ci. Should not be overrided by build.sh.
# To use custom algorithm, one should override termux_pkg_auto_update().
# shellcheck source=scripts/updates/internal/termux_gitlab_auto_update.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/internal/termux_gitlab_auto_update.sh
# Default auto update script for rest packages. Should not be overrided by build.sh.
# To use custom algorithm, one should override termux_pkg_auto_update().
# shellcheck source=scripts/updates/internal/termux_repology_auto_update.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/internal/termux_repology_auto_update.sh
# Main script to:
# - by default, decide which update method to use,
# - but can be overrided by build.sh to use custom update method.
# - For example: see neovim-nightly's build.sh.
# shellcheck source=scripts/updates/termux_pkg_auto_update.sh
. "${TERMUX_SCRIPTDIR}"/scripts/updates/termux_pkg_auto_update.sh
_update() {
export TERMUX_PKG_NAME
TERMUX_PKG_NAME="$(basename "$1")"
export TERMUX_PKG_BUILDER_DIR
TERMUX_PKG_BUILDER_DIR="$(realpath "$1")" # Directory containing build.sh.
# Avoid:
# - ending on errors such as $(which prog), where prog is not installed.
# - error on unbound variable.
#
# Variables used by auto update script should be covered by above variables and properties.sh.
set +e +u
# shellcheck source=/dev/null
. "${pkg_dir}"/build.sh 2>/dev/null
set -e -u
IFS="," read -r -a BLACKLISTED_ARCH <<<"${TERMUX_PKG_BLACKLISTED_ARCHES:-}"
export TERMUX_ARCH="" # Arch to test updates.
for arch in aarch64 arm i686 x86_64; do
# shellcheck disable=SC2076
if [[ ! " ${BLACKLISTED_ARCH[*]} " =~ " ${arch} " ]]; then
TERMUX_ARCH="${arch}"
break
fi
done
# Only update if auto update is enabled.
if [[ "${TERMUX_PKG_AUTO_UPDATE}" == "true" ]]; then
echo # Newline.
echo "INFO: Updating ${TERMUX_PKG_NAME} [Current version: ${TERMUX_PKG_VERSION}]..."
termux_pkg_auto_update
fi
}
declare -A _FAILED_UPDATES=()
_run_update() {
local pkg_dir="$1"
# Check if this `pkg_dir` has a `build.sh` file.
if [[ ! -f "${pkg_dir}/build.sh" ]]; then
# Fail if detected a non-package directory.
termux_error_exit "ERROR: directory '${pkg_dir}' is not a package."
fi
# Run each package update in separate process since we include their environment variables.
local output=""
{
output=$(
set -euo pipefail
_update "${pkg_dir}" 2>&1 | tee /dev/fd/3 # fd 3 is used to output to stdout as well.
exit "${PIPESTATUS[0]}" # Return exit code of _update.
)
} 3>&1
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
_FAILED_UPDATES["$(basename "${pkg_dir}")"]="${output}"
fi
}
_create_gh_issue() {
local pkg="$1"
gh issue create --title "Auto update failing for ${pkg}" --label "auto update" \
--assignee "${GITHUB_ACTOR}" --body "$(
cat <<-EOF
Auto update failed for ${pkg}.
<details><summary>Output log</summary>
<pre lang="bash">
${_FAILED_UPDATES[$pkg]}
</pre>
</details>
<i>Automatically submitted by github ci.<br>
Run ID: <a href="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}">${GITHUB_RUN_ID}</a><br>
Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")<br></i>
EOF
)"
}
_update_gh_issue() {
local issue_number="$1"
local pkg="$2"
gh issue edit "$issue_number" --body "$(
gh issue view --json body --jq '.body' "${issue_number}"
echo "<br>"
cat <<-EOF
<h4>EDIT:</h4>
Update failed again.
<details><summary>Output log</summary>
<pre lang="bash">
${_FAILED_UPDATES[$pkg]}
</pre>
</details>
<i>Automatically submitted by github ci.<br>
Run ID: <a href="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}">${GITHUB_RUN_ID}</a><br>
Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")<br></i>
EOF
)"
}
_handle_failure() {
if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then # GITHUB_ACTIONS is always set to true on github CI.
echo "INFO: Creating issue for failed updates."
declare -A existing_issues # Map of ==> issue title :: issue number
for issue_number in $(gh issue list --label "auto update" --json number --jq '.[] | .number' --state open); do
existing_issues["$(gh issue view "${issue_number}" --json title --jq '.title')"]="${issue_number}"
done
for pkg in "${!_FAILED_UPDATES[@]}"; do
# shellcheck disable=SC2076
# Here we check if an issue with same title already existis then update that issue,
# rather than crating a new one again.
if [[ " ${!existing_issues[*]} " =~ " Auto update failing for ${pkg} " ]]; then
# Here we are passing issue_number, pkg name to _update_gh_issue function.
_update_gh_issue "${existing_issues["Auto update failing for ${pkg}"]}" "${pkg}"
else
_create_gh_issue "${pkg}" # Create a new issue with this package name.
fi
done
else
echo # Newline.
echo "==> Failed updates:"
for pkg in "${!_FAILED_UPDATES[@]}"; do
echo "-> ${pkg}" # Only print package name.
done
exit 1
fi
}
main() {
echo "INFO: Running update for: $*"
if [[ "$1" == "@all" ]]; then
for repo_dir in $(jq --raw-output 'keys | .[]' "${TERMUX_SCRIPTDIR}/repo.json"); do
for pkg_dir in "${repo_dir}"/*; do
_run_update "${pkg_dir}"
done
done
else
for pkg in "$@"; do
if [ ! -d "${pkg}" ]; then # If only package name is given, try to find it's directory.
for repo_dir in $(jq --raw-output 'keys | .[]' "${TERMUX_SCRIPTDIR}/repo.json"); do
if [ -d "${repo_dir}/${pkg}" ]; then
pkg="${repo_dir}/${pkg}"
break
fi
done
fi
_run_update "${pkg}" # Here, `pkg` is a directory.
done
fi
if [[ ${#_FAILED_UPDATES[@]} -gt 0 ]]; then
_handle_failure
fi
}
main "$@"