#!/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_dir}> # # parameters: # : u-boot, kernel, atf # <{patch_dir}>: u-boot: u-boot, u-boot-neo; kernel: sun4i-default, sunxi-next, ... # : cubieboard, cubieboard2, cubietruck, ... # : optional subdirectory # : 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 # # parameters: # : path to patch file # : 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 patch’s 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 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 }