build/lib/functions/compilation/patch/patching.sh

229 lines
10 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2013-2023 Igor Pecovnik, igor@armbian.com
#
# This file is a part of the Armbian Build Framework
# https://github.com/armbian/build/
# advanced_patch <patch_kind> <{patch_dir}> <board> <target> <branch> <description>
#
# parameters:
# <patch_kind>: u-boot, kernel, atf
# <{patch_dir}>: u-boot: u-boot, u-boot-neo; kernel: sun4i-default, sunxi-next, ...
# <board>: cubieboard, cubieboard2, cubietruck, ...
# <target>: optional subdirectory
# <description>: additional description text
# calls:
# ${patch_kind} ${patch_dir} $board $target $branch $description
# kernel: advanced_patch "kernel" "$KERNELPATCHDIR" "$BOARD" "" "$BRANCH" "$LINUXFAMILY-$BRANCH"
# u-boot: advanced_patch "u-boot" "$BOOTPATCHDIR" "$BOARD" "$target_patchdir" "$BRANCH" "${LINUXFAMILY}-${BOARD}-${BRANCH}"
function advanced_patch() {
local patch_kind="$1"
local patch_dir="$2"
local board="$3"
local target="$4"
local branch="$5"
local description="$6"
display_alert "Started patching process for" "${patch_kind} $description" "info"
display_alert "Looking for user patches in" "userpatches/${patch_kind}/${patch_dir}" "info"
local names=()
local dirs=(
"$USERPATCHES_PATH/${patch_kind}/${patch_dir}/target_${target}:[\e[33mu\e[0m][\e[34mt\e[0m]"
"$USERPATCHES_PATH/${patch_kind}/${patch_dir}/board_${board}:[\e[33mu\e[0m][\e[35mb\e[0m]"
"$USERPATCHES_PATH/${patch_kind}/${patch_dir}/branch_${branch}:[\e[33mu\e[0m][\e[33mb\e[0m]"
"$USERPATCHES_PATH/${patch_kind}/${patch_dir}:[\e[33mu\e[0m][\e[32mc\e[0m]"
"$SRC/patch/${patch_kind}/${patch_dir}/target_${target}:[\e[32ml\e[0m][\e[34mt\e[0m]" # used for u-boot "spi" stuff
"$SRC/patch/${patch_kind}/${patch_dir}/board_${board}:[\e[32ml\e[0m][\e[35mb\e[0m]" # used for u-boot board-specific stuff
"$SRC/patch/${patch_kind}/${patch_dir}/branch_${branch}:[\e[32ml\e[0m][\e[33mb\e[0m]" # NOT used, I think.
"$SRC/patch/${patch_kind}/${patch_dir}:[\e[32ml\e[0m][\e[32mc\e[0m]" # used for everything
)
local links=()
# required for "for" command
# @TODO these shopts leak for the rest of the build script! either make global, or restore them after this function
shopt -s nullglob dotglob
# get patch file names
for dir in "${dirs[@]}"; do
for patch in ${dir%%:*}/*.patch; do
names+=($(basename "${patch}"))
done
# add linked patch directories
if [[ -d ${dir%%:*} ]]; then
local findlinks
findlinks=$(find "${dir%%:*}" -maxdepth 1 -type l -print0 2>&1 | xargs -0)
[[ -n $findlinks ]] && readarray -d '' links < <(find "${findlinks}" -maxdepth 1 -type f -follow -print -iname "*.patch" -print | grep "\.patch$" | sed "s|${dir%%:*}/||g" 2>&1)
fi
done
# merge static and linked
names=("${names[@]}" "${links[@]}")
# remove duplicates
local names_s=($(echo "${names[@]}" | tr ' ' '\n' | LC_ALL=C sort -u | tr '\n' ' '))
# apply patches
for name in "${names_s[@]}"; do
for dir in "${dirs[@]}"; do
if [[ -f ${dir%%:*}/$name ]]; then
if [[ -s ${dir%%:*}/$name ]]; then
process_patch_file "${dir%%:*}/$name" "${dir##*:}"
else
display_alert "* ${dir##*:} $name" "skipped"
fi
break # next name
fi
done
done
}
# process_patch_file <file> <description>
#
# parameters:
# <file>: path to patch file
# <status>: additional status text
#
process_patch_file() {
local patch="${1}"
local status="${2}"
local -i patch_date
local relative_patch="${patch##"${SRC}"/}" # ${FOO##prefix} remove prefix from FOO
# detect and remove files which patch will create
lsdiff -s --strip=1 "${patch}" | grep '^+' | awk '{print $2}' | xargs -I % sh -c 'rm -f %'
# shellcheck disable=SC2015 # noted, thanks. I need to handle exit code here.
patch --batch -p1 -N --input="${patch}" --quiet --reject-file=- && { # "-" discards rejects
display_alert "* $status ${relative_patch}" "" "info"
} || {
display_alert "* $status ${relative_patch}" "failed" "err"
exit_with_error "Patching error, exiting."
}
return 0 # short-circuit above, avoid exiting with error
}
function userpatch_create() {
declare patch_type="${1}"
declare -a common_git_params=(
"-c" "commit.gpgsign=false"
"-c" "user.name='${MAINTAINER}'"
"-c" "user.email='${MAINTAINERMAIL}'"
)
# export the commit as a patch
declare formatpatch_params=(
"-1" "HEAD" "--stdout"
"--unified=5" # force 5 lines of diff context
"--keep-subject" # do not add a prefix to the subject "[PATCH] "
'--signature' "'Created with Armbian build tools https://github.com/armbian/build'"
'--stat=120' # 'wider' stat output; default is 80
'--stat-graph-width=10' # shorten the diffgraph graph part, it's too long
"--zero-commit" # Output an all-zero hash in each patchs From header instead of the hash of the commit.
)
# if stdin is not a terminal, bail out
[[ -t 0 ]] || exit_with_error "patching: stdin is not a terminal"
[[ -t 1 ]] || exit_with_error "patching: stdout is not a terminal"
# Display a header with instructions about MAINTAINER and MAINTAINERMAIL
display_alert "Starting" "interactive patching process for ${patch_type}" "ext"
# create commit to start from clean source; don't fail.
display_alert "Creating commit to start from clean source" "" "info"
run_host_command_logged git "${common_git_params[@]}" add . "||" true
run_host_command_logged git "${common_git_params[@]}" commit -q -m "'Previous changes made by Armbian'" "||" true
display_alert "Patches will be created" "with the following maintainer information" "info"
display_alert "MAINTAINER (Real name): " "${MAINTAINER}" "info"
display_alert "MAINTAINERMAIL (Email): " "${MAINTAINERMAIL}" "info"
display_alert "If those are not correct, set them in your environment, command line, or config file and restart the process" "" ""
mkdir -p "${DEST}/patch"
declare patch="${DEST}/patch/${patch_type}-${LINUXFAMILY}-${BRANCH}.patch"
# prompt to alter source
display_alert "Make your changes in this directory:" "$(pwd)" "wrn"
if [[ "${ARMBIAN_RUNNING_IN_CONTAINER}" == "yes" ]]; then
display_alert "You are running in a container" "Path shown above might not match host system, be aware." "wrn"
fi
# If the ${patch} file already exists, offer to apply it before continuing patching.
if [[ -f "${patch}" ]]; then
display_alert "A previously-created patch file already exists!" "${patch}" "wrn"
declare apply_patch
read -r -e -p "Do you want to apply it before continuing? [y/N] " apply_patch
if [[ "${apply_patch}" == "y" ]]; then
display_alert "Applying patch" "${patch}" "info"
run_host_command_logged git "${common_git_params[@]}" apply "${patch}" || display_alert "Patch failed to apply, continuing..." "${patch}" "wrn"
fi
fi
# Enter a loop, waiting for ENTER, then showing the git diff, and have the user confirm he is happy with patch
declare user_happy="no"
while [[ "${user_happy}" != "yes" ]]; do
display_alert "Press <ENTER> after you are done" "editing files in $(pwd)" "wrn"
# Wait for user to press ENTER
declare stop_patching
read -r -e -p "Press ENTER to show a preview of your patch, or type 'stop' to stop patching..." stop_patching
[[ "${stop_patching}" == "stop" ]] && exit_with_error "Aborting due to" "user request"
# Detect if there are any changes done to the working tree
declare -i changes_in_working_tree
changes_in_working_tree=$(git "${common_git_params[@]}" status --porcelain | wc -l)
if [[ ${changes_in_working_tree} -lt 1 ]]; then
display_alert "No changes detected!" "No changes in the working tree, please edit files and try again" "wrn"
continue # no changes, loop again
fi
display_alert "OK, here's how your diff looks like" "showing patch diff" "info"
git "${common_git_params[@]}" diff | run_tool_batcat --file-name "${patch}" -
# Prompt the user if he is happy with the patch
display_alert "Are you happy with this patch?" "Type 'yes' to accept, 'stop' to stop patching, or anything else to keep patching" "wrn"
# Wait for user to type yes or no
read -r -e -p "Are you happy with the diff above? Type 'y' or 'yes' to accept, 'stop' to stop patching, anything else to keep patching: " -i "" user_happy
declare first_uppercase_character_of_user_happy="${user_happy:0:1}"
first_uppercase_character_of_user_happy="${first_uppercase_character_of_user_happy^^}"
[[ "${first_uppercase_character_of_user_happy}" == "Y" ]] && break
[[ "${first_uppercase_character_of_user_happy}" == "S" ]] && exit_with_error "Aborting due to user request"
display_alert "Not happy? No problem!" "just keep on editing the files..." "wrn"
done
display_alert "OK, user is happy with diff" "proceeding with patch creation" "ext"
run_host_command_logged git add .
# create patch out of changes
if ! git "${common_git_params[@]}" diff-index --quiet --cached HEAD; then
# Default the patch_commit_message.
# Get a list of all the filenames in the git diff into a bash array...
declare -a changed_filenames=($(git "${common_git_params[@]}" diff-index --cached --name-only HEAD))
display_alert "Names of the changed files" "${changed_filenames[*]@Q}" "info"
declare patch_commit_message="Patching ${patch_type} ${LINUXFAMILY} files ${changed_filenames[*]@Q}"
# If Git is configured, create proper patch and ask for a name
display_alert "Add / change patch name" "${patch_commit_message}" "wrn"
read -e -p "Patch Subject: " -i "${patch_commit_message}" patch_commit_message
[[ -z "${patch_commit_message}" ]] && patch_commit_message="Patching something unknown and mysterious"
run_host_command_logged git "${common_git_params[@]}" commit -s -m "'${patch_commit_message}'"
run_host_command_logged git "${common_git_params[@]}" format-patch "${formatpatch_params[@]}" ">" "${patch}"
display_alert "You will find your patch here:" "${patch}" "info"
run_tool_batcat --file-name "${patch}" "${patch}"
display_alert "You will find your patch here:" "${patch}" "info"
display_alert "Now you can manually move the produced patch to your userpatches or core patches to have it included in the next build" "${patch}" "ext"
else
display_alert "No changes found, skipping patch creation" "" "err"
fi
}