From 04f4c6f616e2daa95c005ee93b626495681bd586 Mon Sep 17 00:00:00 2001 From: Michał Górny Date: Sat, 11 Mar 2017 14:31:19 +0100 Subject: epatch.eclass: Split epatch* logic from eutils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move epatch and epatch_user (along with the descriptions for all their variables) into a dedicated epatch.eclass. This function is very complex, therefore it benefits from separate eclass and a dedicated maintainer. Furthermore, it is mostly obsoleted by eapply* in EAPI 6. The new eclass is implicitly inherited by eutils to preserve compatibility. However, the inherit will be removed in EAPI 7, and the ebuilds should switch to inheriting epatch directly or using eapply*. Thanks to Ulrich Müller for doing the necessary research. --- eclass/epatch.eclass | 458 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 eclass/epatch.eclass (limited to 'eclass/epatch.eclass') diff --git a/eclass/epatch.eclass b/eclass/epatch.eclass new file mode 100644 index 000000000000..fb0a10b53583 --- /dev/null +++ b/eclass/epatch.eclass @@ -0,0 +1,458 @@ +# Copyright 1999-2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: epatch.eclass +# @MAINTAINER: +# base-system@gentoo.org +# @BLURB: easy patch application functions +# @DESCRIPTION: +# An eclass providing epatch and epatch_user functions to easily apply +# patches to ebuilds. Mostly superseded by eapply* in EAPI 6. + +if [[ -z ${_EPATCH_ECLASS} ]]; then + +# @VARIABLE: EPATCH_SOURCE +# @DESCRIPTION: +# Default directory to search for patches. +EPATCH_SOURCE="${WORKDIR}/patch" +# @VARIABLE: EPATCH_SUFFIX +# @DESCRIPTION: +# Default extension for patches (do not prefix the period yourself). +EPATCH_SUFFIX="patch.bz2" +# @VARIABLE: EPATCH_OPTS +# @DESCRIPTION: +# Options to pass to patch. Meant for ebuild/package-specific tweaking +# such as forcing the patch level (-p#) or fuzz (-F#) factor. Note that +# for single patch tweaking, you can also pass flags directly to epatch. +EPATCH_OPTS="" +# @VARIABLE: EPATCH_COMMON_OPTS +# @DESCRIPTION: +# Common options to pass to `patch`. You probably should never need to +# change these. If you do, please discuss it with base-system first to +# be sure. +# @CODE +# -g0 - keep RCS, ClearCase, Perforce and SCCS happy #24571 +# --no-backup-if-mismatch - do not leave .orig files behind +# -E - automatically remove empty files +# @CODE +EPATCH_COMMON_OPTS="-g0 -E --no-backup-if-mismatch" +# @VARIABLE: EPATCH_EXCLUDE +# @DESCRIPTION: +# List of patches not to apply. Note this is only file names, +# and not the full path. Globs accepted. +EPATCH_EXCLUDE="" +# @VARIABLE: EPATCH_SINGLE_MSG +# @DESCRIPTION: +# Change the printed message for a single patch. +EPATCH_SINGLE_MSG="" +# @VARIABLE: EPATCH_MULTI_MSG +# @DESCRIPTION: +# Change the printed message for multiple patches. +EPATCH_MULTI_MSG="Applying various patches (bugfixes/updates) ..." +# @VARIABLE: EPATCH_FORCE +# @DESCRIPTION: +# Only require patches to match EPATCH_SUFFIX rather than the extended +# arch naming style. +EPATCH_FORCE="no" +# @VARIABLE: EPATCH_USER_EXCLUDE +# @DEFAULT_UNSET +# @DESCRIPTION: +# List of patches not to apply. Note this is only file names, +# and not the full path. Globs accepted. + +# @FUNCTION: epatch +# @USAGE: [options] [patches] [dirs of patches] +# @DESCRIPTION: +# epatch is designed to greatly simplify the application of patches. It can +# process patch files directly, or directories of patches. The patches may be +# compressed (bzip/gzip/etc...) or plain text. You generally need not specify +# the -p option as epatch will automatically attempt -p0 to -p4 until things +# apply successfully. +# +# If you do not specify any patches/dirs, then epatch will default to the +# directory specified by EPATCH_SOURCE. +# +# Any options specified that start with a dash will be passed down to patch +# for this specific invocation. As soon as an arg w/out a dash is found, then +# arg processing stops. +# +# When processing directories, epatch will apply all patches that match: +# @CODE +# if ${EPATCH_FORCE} != "yes" +# ??_${ARCH}_foo.${EPATCH_SUFFIX} +# else +# *.${EPATCH_SUFFIX} +# @CODE +# The leading ?? are typically numbers used to force consistent patch ordering. +# The arch field is used to apply patches only for the host architecture with +# the special value of "all" means apply for everyone. Note that using values +# other than "all" is highly discouraged -- you should apply patches all the +# time and let architecture details be detected at configure/compile time. +# +# If EPATCH_SUFFIX is empty, then no period before it is implied when searching +# for patches to apply. +# +# Refer to the other EPATCH_xxx variables for more customization of behavior. +epatch() { + _epatch_draw_line() { + # create a line of same length as input string + [[ -z $1 ]] && set "$(printf "%65s" '')" + echo "${1//?/=}" + } + + unset P4CONFIG P4PORT P4USER # keep perforce at bay #56402 + + # First process options. We localize the EPATCH_OPTS setting + # from above so that we can pass it on in the loop below with + # any additional values the user has specified. + local EPATCH_OPTS=( ${EPATCH_OPTS[*]} ) + while [[ $# -gt 0 ]] ; do + case $1 in + -*) EPATCH_OPTS+=( "$1" ) ;; + *) break ;; + esac + shift + done + + # Let the rest of the code process one user arg at a time -- + # each arg may expand into multiple patches, and each arg may + # need to start off with the default global EPATCH_xxx values + if [[ $# -gt 1 ]] ; then + local m + for m in "$@" ; do + epatch "${m}" + done + return 0 + fi + + local SINGLE_PATCH="no" + # no args means process ${EPATCH_SOURCE} + [[ $# -eq 0 ]] && set -- "${EPATCH_SOURCE}" + + if [[ -f $1 ]] ; then + SINGLE_PATCH="yes" + set -- "$1" + # Use the suffix from the single patch (localize it); the code + # below will find the suffix for us + local EPATCH_SUFFIX=$1 + + elif [[ -d $1 ]] ; then + # We have to force sorting to C so that the wildcard expansion is consistent #471666. + evar_push_set LC_COLLATE C + # Some people like to make dirs of patches w/out suffixes (vim). + set -- "$1"/*${EPATCH_SUFFIX:+."${EPATCH_SUFFIX}"} + evar_pop + + elif [[ -f ${EPATCH_SOURCE}/$1 ]] ; then + # Re-use EPATCH_SOURCE as a search dir + epatch "${EPATCH_SOURCE}/$1" + return $? + + else + # sanity check ... if it isn't a dir or file, wtf man ? + [[ $# -ne 0 ]] && EPATCH_SOURCE=$1 + echo + eerror "Cannot find \$EPATCH_SOURCE! Value for \$EPATCH_SOURCE is:" + eerror + eerror " ${EPATCH_SOURCE}" + eerror " ( ${EPATCH_SOURCE##*/} )" + echo + die "Cannot find \$EPATCH_SOURCE!" + fi + + # Now that we know we're actually going to apply something, merge + # all of the patch options back in to a single variable for below. + EPATCH_OPTS="${EPATCH_COMMON_OPTS} ${EPATCH_OPTS[*]}" + + local PIPE_CMD + case ${EPATCH_SUFFIX##*\.} in + xz) PIPE_CMD="xz -dc" ;; + lzma) PIPE_CMD="lzma -dc" ;; + bz2) PIPE_CMD="bzip2 -dc" ;; + gz|Z|z) PIPE_CMD="gzip -dc" ;; + ZIP|zip) PIPE_CMD="unzip -p" ;; + *) ;; + esac + + [[ ${SINGLE_PATCH} == "no" ]] && einfo "${EPATCH_MULTI_MSG}" + + local x + for x in "$@" ; do + # If the patch dir given contains subdirs, or our EPATCH_SUFFIX + # didn't match anything, ignore continue on + [[ ! -f ${x} ]] && continue + + local patchname=${x##*/} + + # Apply single patches, or forced sets of patches, or + # patches with ARCH dependant names. + # ???_arch_foo.patch + # Else, skip this input altogether + local a=${patchname#*_} # strip the ???_ + a=${a%%_*} # strip the _foo.patch + if ! [[ ${SINGLE_PATCH} == "yes" || \ + ${EPATCH_FORCE} == "yes" || \ + ${a} == all || \ + ${a} == ${ARCH} ]] + then + continue + fi + + # Let people filter things dynamically + if [[ -n ${EPATCH_EXCLUDE}${EPATCH_USER_EXCLUDE} ]] ; then + # let people use globs in the exclude + eshopts_push -o noglob + + local ex + for ex in ${EPATCH_EXCLUDE} ; do + if [[ ${patchname} == ${ex} ]] ; then + einfo " Skipping ${patchname} due to EPATCH_EXCLUDE ..." + eshopts_pop + continue 2 + fi + done + + for ex in ${EPATCH_USER_EXCLUDE} ; do + if [[ ${patchname} == ${ex} ]] ; then + einfo " Skipping ${patchname} due to EPATCH_USER_EXCLUDE ..." + eshopts_pop + continue 2 + fi + done + + eshopts_pop + fi + + if [[ ${SINGLE_PATCH} == "yes" ]] ; then + if [[ -n ${EPATCH_SINGLE_MSG} ]] ; then + einfo "${EPATCH_SINGLE_MSG}" + else + einfo "Applying ${patchname} ..." + fi + else + einfo " ${patchname} ..." + fi + + # Handle aliased patch command #404447 #461568 + local patch="patch" + eval $(alias patch 2>/dev/null | sed 's:^alias ::') + + # most of the time, there will only be one run per unique name, + # but if there are more, make sure we get unique log filenames + local STDERR_TARGET="${T}/${patchname}.out" + if [[ -e ${STDERR_TARGET} ]] ; then + STDERR_TARGET="${T}/${patchname}-$$.out" + fi + + printf "***** %s *****\nPWD: %s\nPATCH TOOL: %s -> %s\nVERSION INFO:\n%s\n\n" \ + "${patchname}" \ + "${PWD}" \ + "${patch}" \ + "$(type -P "${patch}")" \ + "$(${patch} --version)" \ + > "${STDERR_TARGET}" + + # Decompress the patch if need be + local count=0 + local PATCH_TARGET + if [[ -n ${PIPE_CMD} ]] ; then + PATCH_TARGET="${T}/$$.patch" + echo "PIPE_COMMAND: ${PIPE_CMD} ${x} > ${PATCH_TARGET}" >> "${STDERR_TARGET}" + + if ! (${PIPE_CMD} "${x}" > "${PATCH_TARGET}") >> "${STDERR_TARGET}" 2>&1 ; then + echo + eerror "Could not extract patch!" + #die "Could not extract patch!" + count=5 + break + fi + else + PATCH_TARGET=${x} + fi + + # Check for absolute paths in patches. If sandbox is disabled, + # people could (accidently) patch files in the root filesystem. + # Or trigger other unpleasantries #237667. So disallow -p0 on + # such patches. + local abs_paths=$(egrep -n '^[-+]{3} /' "${PATCH_TARGET}" | awk '$2 != "/dev/null" { print }') + if [[ -n ${abs_paths} ]] ; then + count=1 + printf "NOTE: skipping -p0 due to absolute paths in patch:\n%s\n" "${abs_paths}" >> "${STDERR_TARGET}" + fi + # Similar reason, but with relative paths. + local rel_paths=$(egrep -n '^[-+]{3} [^ ]*[.][.]/' "${PATCH_TARGET}") + if [[ -n ${rel_paths} ]] ; then + echo + eerror "Rejected Patch: ${patchname} !" + eerror " ( ${PATCH_TARGET} )" + eerror + eerror "Your patch uses relative paths '../':" + eerror "${rel_paths}" + echo + die "you need to fix the relative paths in patch" + fi + + # Dynamically detect the correct -p# ... i'm lazy, so shoot me :/ + local patch_cmd + while [[ ${count} -lt 5 ]] ; do + patch_cmd="${patch} -p${count} ${EPATCH_OPTS}" + + # Generate some useful debug info ... + ( + _epatch_draw_line "***** ${patchname} *****" + echo + echo "PATCH COMMAND: ${patch_cmd} --dry-run -f < '${PATCH_TARGET}'" + echo + _epatch_draw_line "***** ${patchname} *****" + ${patch_cmd} --dry-run -f < "${PATCH_TARGET}" 2>&1 + ret=$? + echo + echo "patch program exited with status ${ret}" + exit ${ret} + ) >> "${STDERR_TARGET}" + + if [ $? -eq 0 ] ; then + ( + _epatch_draw_line "***** ${patchname} *****" + echo + echo "ACTUALLY APPLYING ${patchname} ..." + echo "PATCH COMMAND: ${patch_cmd} < '${PATCH_TARGET}'" + echo + _epatch_draw_line "***** ${patchname} *****" + ${patch_cmd} < "${PATCH_TARGET}" 2>&1 + ret=$? + echo + echo "patch program exited with status ${ret}" + exit ${ret} + ) >> "${STDERR_TARGET}" + + if [ $? -ne 0 ] ; then + echo + eerror "A dry-run of patch command succeeded, but actually" + eerror "applying the patch failed!" + #die "Real world sux compared to the dreamworld!" + count=5 + fi + break + fi + + : $(( count++ )) + done + + (( EPATCH_N_APPLIED_PATCHES++ )) + + # if we had to decompress the patch, delete the temp one + if [[ -n ${PIPE_CMD} ]] ; then + rm -f "${PATCH_TARGET}" + fi + + if [[ ${count} -ge 5 ]] ; then + echo + eerror "Failed Patch: ${patchname} !" + eerror " ( ${PATCH_TARGET} )" + eerror + eerror "Include in your bugreport the contents of:" + eerror + eerror " ${STDERR_TARGET}" + echo + die "Failed Patch: ${patchname}!" + fi + + # if everything worked, delete the full debug patch log + rm -f "${STDERR_TARGET}" + + # then log away the exact stuff for people to review later + cat <<-EOF >> "${T}/epatch.log" + PATCH: ${x} + CMD: ${patch_cmd} + PWD: ${PWD} + + EOF + eend 0 + done + + [[ ${SINGLE_PATCH} == "no" ]] && einfo "Done with patching" + : # everything worked +} + +case ${EAPI:-0} in +0|1|2|3|4|5) + +# @VARIABLE: EPATCH_USER_SOURCE +# @DESCRIPTION: +# Location for user patches, see the epatch_user function. +# Should be set by the user. Don't set this in ebuilds. +: ${EPATCH_USER_SOURCE:=${PORTAGE_CONFIGROOT%/}/etc/portage/patches} + +# @FUNCTION: epatch_user +# @USAGE: +# @DESCRIPTION: +# Applies user-provided patches to the source tree. The patches are +# taken from /etc/portage/patches//[:SLOT]/, where the first +# of these three directories to exist will be the one to use, ignoring +# any more general directories which might exist as well. They must end +# in ".patch" to be applied. +# +# User patches are intended for quick testing of patches without ebuild +# modifications, as well as for permanent customizations a user might +# desire. Obviously, there can be no official support for arbitrarily +# patched ebuilds. So whenever a build log in a bug report mentions that +# user patches were applied, the user should be asked to reproduce the +# problem without these. +# +# Not all ebuilds do call this function, so placing patches in the +# stated directory might or might not work, depending on the package and +# the eclasses it inherits and uses. It is safe to call the function +# repeatedly, so it is always possible to add a call at the ebuild +# level. The first call is the time when the patches will be +# applied. +# +# Ideally, this function should be called after gentoo-specific patches +# have been applied, so that their code can be modified as well, but +# before calls to e.g. eautoreconf, as the user patches might affect +# autotool input files as well. +epatch_user() { + [[ $# -ne 0 ]] && die "epatch_user takes no options" + + # Allow multiple calls to this function; ignore all but the first + local applied="${T}/epatch_user.log" + [[ -e ${applied} ]] && return 2 + + # don't clobber any EPATCH vars that the parent might want + local EPATCH_SOURCE check + for check in ${CATEGORY}/{${P}-${PR},${P},${PN}}{,:${SLOT%/*}}; do + EPATCH_SOURCE=${EPATCH_USER_SOURCE}/${CTARGET}/${check} + [[ -r ${EPATCH_SOURCE} ]] || EPATCH_SOURCE=${EPATCH_USER_SOURCE}/${CHOST}/${check} + [[ -r ${EPATCH_SOURCE} ]] || EPATCH_SOURCE=${EPATCH_USER_SOURCE}/${check} + if [[ -d ${EPATCH_SOURCE} ]] ; then + local old_n_applied_patches=${EPATCH_N_APPLIED_PATCHES:-0} + EPATCH_SOURCE=${EPATCH_SOURCE} \ + EPATCH_SUFFIX="patch" \ + EPATCH_FORCE="yes" \ + EPATCH_MULTI_MSG="Applying user patches from ${EPATCH_SOURCE} ..." \ + epatch + echo "${EPATCH_SOURCE}" > "${applied}" + if [[ ${old_n_applied_patches} -lt ${EPATCH_N_APPLIED_PATCHES} ]]; then + has epatch_user_death_notice ${EBUILD_DEATH_HOOKS} || \ + EBUILD_DEATH_HOOKS+=" epatch_user_death_notice" + fi + return 0 + fi + done + echo "none" > "${applied}" + return 1 +} + +# @FUNCTION: epatch_user_death_notice +# @INTERNAL +# @DESCRIPTION: +# Include an explicit notice in the die message itself that user patches were +# applied to this build. +epatch_user_death_notice() { + ewarn "!!! User patches were applied to this build!" +} + +esac + +_EPATCH_ECLASS=1 +fi #_EPATCH_ECLASS -- cgit v1.2.3-65-gdbad