#!/bin/sh -e # # rmport - remove port(s) from the FreeBSD Ports Collection. # # Copyright 2006-2007 Vasil Dimov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # Authors: # Originally written by Vasil Dimov # Others: # # $FreeBSD$ # # MAINTAINER= vd@FreeBSD.org # PORTSDIR=${PORTSDIR:-/usr/ports} INDEX=${PORTSDIR}/`make -C ${PORTSDIR} -V INDEXFILE` TODAY=`date -u -v+0d +%Y-%m-%d` SED="sed -i .orig -E" # use ~/.ssh/config to set up the desired username if different than $LOGNAME PCVS=${PCVS:-cvs -d pcvs.freebsd.org:/home/pcvs} log() { echo "==> $*" >&2 } escape() { # escape characters that may appear in ports' names and # break regular expressions echo "${1}" |sed -E 's/(\+|\.)/\\\1/g' } pkgname() { make -C ${PORTSDIR}/${1} -V PKGNAME } ask() { question=${1} answer=x while [ "${answer}" != "y" -a "${answer}" != "n" ] ; do read -p "${question} [yn] " answer done echo ${answer} } # return category/port if arg is directly port's directory on the filesystem find_catport() { arg=${1} if [ -d "${PORTSDIR}/${arg}" ] ; then # arg is category/port echo ${arg} elif [ -d "${arg}" ] ; then # arg is the port's directory somewhere in the filesystem # either absolute or relative # get the full path rp=`realpath ${arg}` category=`basename \`dirname ${rp}\`` port=`basename ${rp}` echo ${category}/${port} else echo "What do you mean by \`${arg}'?" >&2 exit 1 fi } find_expired() { EXPVAR=EXPIRATION_DATE find ${PORTSDIR} -mindepth 3 -maxdepth 3 -name "Makefile*" \ |xargs grep -H ${EXPVAR} \ |sed -E "s|${PORTSDIR}/?([^/]+/[^/]+)/Makefile:${EXPVAR}=[[:space:]]*([0-9-]{10})$|\2 \1|g" \ |perl -ne "if ((substr(\$_, 0, 10) cmp '${TODAY}') <= 0) { print(\$_); }" \ |while read expdate catport ; do \ echo -n "${expdate} ${catport}: " ; \ make -C ${PORTSDIR}/${catport} -V DEPRECATED ; \ done } # create temporary checkout directory mkcodir() { log "creating temporary directory" d=`mktemp -d -t rmport` mkdir ${d}/CVS cat > ${d}/CVS/Repository <&2 err=1 fi # check if some Makefiles mention the port to be deleted portdir_grep="^[^#].*/`basename ${catport}`([[:space:]]|/|$)" r="`find ${PORTSDIR} -mindepth 2 -maxdepth 3 \ \( -name "Makefile*" -or -path "*Mk/*.mk" \) \ |xargs grep -EH "${portdir_grep}" \ |grep -vE "^(${rmcatports})" || :`" if [ -n "${r}" ] ; then if [ ${err} -eq 1 ] ; then echo >&2 fi log "${catport}: some Makefiles mention ${portdir_grep}:" echo "${r}" >&2 err=1 fi return ${err} } check_dep() { catport=${1} persist=${2} alltorm=${3} while : ; do log "${catport}: checking dependencies" err=0 res="`check_dep_core ${catport} "${alltorm}" 2>&1`" || err=1 if [ ${err} -eq 0 ] ; then break fi echo "${res}" |${PAGER:-less} if [ ${persist} -eq 0 ] ; then break fi echo "" >&2 echo "you can ignore the above issues and proceed anyway." >&2 echo "if this is the case, then either:" >&2 echo " * these are false positives" >&2 echo " * you want to break something" >&2 echo " * your ${PORTSDIR} is out of date, consider setting PORTSDIR in environment" >&2 echo " to point to a newer instance of the ports tree" >&2 echo "or you can hit \`n' to repeat the check" >&2 answer=`ask "ignore the above issues"` if [ "${answer}" = "y" ] ; then break fi done } # query GNATS via query-pr-summary.cgi, format and return the result get_PRs_www() { catport=${1} synopsis=${2} date_re='[0-9]{4}/[0-9]{2}/[0-9]{2}' prnum_re='.+/[0-9]{5,6}' synopsis_re='.{10,}' log "${catport}: getting PRs having ${synopsis} in the synopsis" url="http://www.freebsd.org/cgi/query-pr-summary.cgi?text=${synopsis}" raw="`fetch -q -T 20 -o - "${url}"`" if [ -z "${raw}" ] ; then log "${catport}: empty result from URL: ${url}" exit 1 fi printf "%s" "${raw}" \ |sed -nE "s|.*>(${date_re})<.*>(${prnum_re})<.*>(${synopsis_re})&2 echo "you can skip ${catport} and continue with the rest or remove it anyway" >&2 answer=`ask "do you want to skip ${catport}?"` if [ "${answer}" = "y" ] ; then return 1 else return 0 fi fi return 0 } # checkout port's specific files from the repository co_port() { cat=${1} port=${2} log "${cat}/${port}: getting ${cat}/Makefile and port's files from repository" ${PCVS} co ports/${cat}/Makefile ports/${cat}/${port} } # check if anything about the port is mentioned in ports/LEGAL check_LEGAL() { catport=${1} pkgname=${2} for checkstr in ${pkgname} ${catport} ; do msg="${catport}: checking if ${checkstr} is in ports/LEGAL" log "${msg}" while grep -i ${checkstr} ports/LEGAL ; do echo "" >&2 echo "${checkstr} is in ${PWD}/ports/LEGAL" >&2 echo "remove it and hit when ready" >&2 echo "or hit \`s' to skip this issue and continue anyway" >&2 read answer if [ "${answer}" = "s" ] ; then break fi log "${msg}" done done } # add port's entry to ports/MOVED edit_MOVED() { catport=${1} DEPRECATED="`make -C ${PORTSDIR}/${catport} -V DEPRECATED`" DEPRECATED=${DEPRECATED:+: ${DEPRECATED}} if [ -n "`make -C ${PORTSDIR}/${catport} -V EXPIRATION_DATE`" ] ; then REASON="Has expired${DEPRECATED}" else REASON="Removed${DEPRECATED}" fi log "${catport}: adding entry to ports/MOVED" echo "${catport}||${TODAY}|${REASON}" >> ports/MOVED } # remove port from category/Makefile edit_Makefile() { cat=${1} port=${2} log "${cat}/${port}: removing from ${cat}/Makefile" portesc=`escape ${port}` ${SED} -e "/^[[:space:]]*SUBDIR[[:space:]]*\+=[[:space:]]*${portesc}([[:space:]]+#.*)?$/d" \ ports/${cat}/Makefile } # remove port's files rm_port() { catport=${1} log "${catport}: removing port's files" ${PCVS} rm `find ports/${catport} -type f -not -path "*/CVS/*" -delete -print` } append_Template() { catport=${1} msg=${catport} EXPIRATION_DATE=`make -C ${PORTSDIR}/${catport} -V EXPIRATION_DATE` if [ -n "${EXPIRATION_DATE}" ] ; then msg="${EXPIRATION_DATE} ${msg}" fi DEPRECATED="`make -C ${PORTSDIR}/${catport} -V DEPRECATED`" if [ -n "${DEPRECATED}" ] ; then msg="${msg}: ${DEPRECATED}" fi log "${catport}: adding entry to commit message template" echo "${msg}" >> ./CVS/Template } # diff diff() { log "creating diff" diffout=${codir}/diff ${PCVS} diff -u ports > ${diffout} 2>&1 || : read -p "hit to view cvs diff output" dummy # give this to the outside world so it can be showed to the committer # and removed when we are done echo ${diffout} } # update, ask for confirmation and commit commit() { log "running cvs update" ${PCVS} -fnq up ports 2>&1 |${PAGER:-less} answer=`ask "do you want to commit?"` if [ "${answer}" = "y" ] ; then ${PCVS} ci ports fi } cleanup() { diffout=${1} codir=${2} log "cleaning up" rm ${diffout} rm CVS/Entries.Log CVS/Repository CVS/Template rmdir CVS # release cvs directories ${PCVS} rel -d CVSROOT ports cd / rmdir ${codir} } usage() { echo "Usage:" >&2 echo "" >&2 echo "find expired ports:" >&2 echo "${0} -F" >&2 echo "" >&2 echo "remove port(s):" >&2 echo "${0} category1/port1 [ category2/port2 ... ]" >&2 echo "" >&2 echo "remove all expired ports (as returned by -F):" >&2 echo "${0} -a" >&2 echo "" >&2 echo "just check dependencies:" >&2 echo "${0} -d category/port" >&2 echo "" >&2 echo "just check if any related PRs exist:" >&2 echo "${0} -p synopsis" >&2 exit 64 } # main if [ ${#} -eq 0 -o "${1}" = "-h" -o "${1}" = "--help" ] ; then usage fi if [ ${1} = "-d" ] ; then if [ ${#} -ne 2 ] ; then usage fi catport=`find_catport ${2}` check_dep ${catport} 0 ${catport} exit fi if [ ${1} = "-p" ] ; then if [ ${#} -ne 2 ] ; then usage fi get_PRs "dummy" ${2} exit fi if [ ${1} = "-F" ] ; then if [ ${#} -ne 1 ] ; then usage fi find_expired exit fi if [ ${1} = "-a" ] ; then if [ ${#} -ne 1 ] ; then usage fi ${0} `find_expired |cut -f 2 -d ' ' |cut -f 1 -d :` exit fi codir=`mkcodir` cd ${codir} co_common for catport in $* ; do # convert to category/port catport=`find_catport ${catport}` cat=`dirname ${catport}` port=`basename ${catport}` # remove any trailing slashes catport="${cat}/${port}" pkgname=`pkgname ${catport}` check_dep ${catport} 1 "${*}" if ! check_PRs ${catport} ${port} ; then continue fi co_port ${cat} ${port} check_LEGAL ${catport} ${pkgname} # everything seems ok, edit the files edit_MOVED ${catport} edit_Makefile ${cat} ${port} rm_port ${catport} append_Template ${catport} done # give a chance to the committer to edit files by hand and recreate/review # the diff afterwards answer=y while [ "${answer}" = "y" ] ; do diffout=diff # EDITOR instead of PAGER because vim has nice syntax highlighting ;-) ${EDITOR} ${diffout} echo "" >&2 echo "you can now edit files under ${codir}/ by hand" >&2 answer=`ask "do you want to recreate the diff?"` done commit cleanup ${diffout} ${codir} # EOF