summaryrefslogtreecommitdiff
path: root/Tools
diff options
context:
space:
mode:
authorMaxim Sobolev <sobomax@FreeBSD.org>2001-03-19 15:33:33 +0000
committerMaxim Sobolev <sobomax@FreeBSD.org>2001-03-19 15:33:33 +0000
commitba1fa488d42419e768eb41e7b9fb033ac597432d (patch)
tree2a1c1c0fa017ddea7f532db334650ac12542aada /Tools
parentLook into arch-specific distinfo's as well. (diff)
Add patchtool - a tool to automate generation/updating of patchfiles. This
tool has something in common with update-patches shell script, but has more features and is more intelligent. See README.patchtool or source code for details.
Notes
Notes: svn path=/head/; revision=40054
Diffstat (limited to 'Tools')
-rw-r--r--Tools/scripts/README.patchtool136
-rwxr-xr-xTools/scripts/patchtool.py639
2 files changed, 775 insertions, 0 deletions
diff --git a/Tools/scripts/README.patchtool b/Tools/scripts/README.patchtool
new file mode 100644
index 000000000000..20388c2fde23
--- /dev/null
+++ b/Tools/scripts/README.patchtool
@@ -0,0 +1,136 @@
+INTRODUCTION
+------------
+Patchtool is a tool aimed to simplify common operations with patchfiles. It
+was designed using real world's experience with the subject and expected to be
+very handy for an active porter.
+
+
+MODES OF OPERATION
+------------------
+The tool has the following two basic modes of operation:
+o generation/update of patchfiles. In this mode user provides list of working
+ files for which patchfiles are to be generated and the tool generates or
+ updates corresponding patches automatically guessing if there is an original
+ version (.orig file, rcs(1)) or it is a completely new file;
+
+o automatic update of the existing patchfiles. In this mode user provides a
+ list of patchfiles or directories containing patchfiles and the tool
+ re-generates specified patches. In this mode the tool tries hard to guess
+ whether the patchfile in question is already up to date or not and updates
+ only those patchfiles, which are found to be outdated.
+
+The following options are supported:
+ -a -- automatically save resulting diff into a patchfile;
+ -f -- don't ask any question if target patchfile already exists and is to be
+ replaced;
+ -u -- run tool in the "update" mode (see above);
+ -i -- perform requiested operation (generate or update) even if the
+ patchfile seems to be up-to-date based on last modification time of
+ all files involved.
+
+The tool supports dozen environment variables that can be used to override
+default settings. You can find complete list at the top of patchtool.py,
+following are the most useful ones:
+PT_CVS_ID -- CVS id to be added at the top of each patchfile generated
+ ("FreeBSD");
+PT_DIFF_ARGS -- diff(1) args used to generate patchfile ("-du");
+PT_DIFF_SUFX -- suffix used to save original version of the file (".orig");
+
+
+KNOWN BUGS AND LIMITATIONS
+--------------------------
+o It is assumed that each patchfile contains exactly one diff, so the tool
+ may remove useful diffs when there are several diffs merged into one
+ patchfile. Actually I don't think that it is a bug, because Porter's
+ Handbook clearly demands to follow a "one diff - one patchfile" rule.
+ Perhaps portlint(1) should be extended to warn about such abuses;
+
+o only '+++' supported as the prefix for the name of target file in the
+ patchfile. Neither '***' nor 'Index:' are not recognised/supported yet;
+
+o please keep in mind that when you are trying to do automatic update and
+ some of the patches are for auto-generated/mangled files (e.g. configure
+ script in the situation when USE_LIBTOOL is used) then you would end up
+ with some of patchfiles containing bogus hunks;
+
+o by default the tool tries to recognise saved original version on the file
+ first by probing file with '.orig' suffix added and if it fails then by
+ probing file with ',v' suffix added. If you use other suffix to save
+ original version, for example '~', then set PT_DIFF_SUFX environment
+ variable to match your settings, otherwise the tool will not function
+ properly.
+
+
+REPORTING BUGS AND PROPOSING ENHANCEMENTS
+-----------------------------------------
+The author of the tool is Maxim Sobolev <sobomax@FreeBSD.org>. Any bug
+reports, patches, proposals or suggestions are encouraged. You can do it
+either contacting author directly at the e-mail above or submitting a FreeBSD
+problem report.
+
+
+EXAMPLES
+--------
+Following are several sample sessions which show common usage patterns for
+this tool.
+
+1. Generation mode (usefull when creating new port).
+$ cd /somewhere/foo ; make
+[compilation blows with error in src/bar/baz.c]
+$ cd work/foo-1.0/src/bar
+[digged here and there and finally have found a solution]
+$ vi baz.c
+[fixing it]
+$ patchtool baz.c
+[reading diff]
+$ patchtool -a baz.c
+Generating patchfile: patch-src_bar_baz.c...ok
+$ cd ../../../../
+$ make
+[works as expected, wow!]
+$ make install clean
+$ send-pr
+[...]
+
+2. Generation mode when target patchfile already exists (Minor port update).
+$ cd /somewhere/foo ; make
+[...]
+1 out of 4 hunks failed--saving rejects to Makefile.rej
+>> Patch patch-aa failed to apply cleanly.
+*** Error code 1
+$ cd work/foo-1.0
+[examining Makefile.rej]
+$ vi Makefile
+[merging changes by hand]
+$ patchtool Makefile
+[reading diff]
+$ patchtool -a Makefile
+Target patchfile "patch-aa" already exists, do you want to replace it? [y/N]: y
+Generating patchfile: patch-aa...ok
+$ cd ../../
+$ make clean
+$ make install clean
+$ send-pr
+[...]
+
+3. "Gross" update mode (Major update, when several existing patches do not apply
+ cleanly).
+$ cd /somewhere/foo
+$ vi Makefile
+[increase PORTVERSION]
+$ make fetch makesum patch
+[several patches are failing to apply cleanly]
+$ cd work/foo-1.0
+[doing merging work, finally all changes are merged]
+$ cd ../../ ; make all install
+[compile and works like a charm]
+$ pwd
+/somewhere/foo
+$ patchtool -u ./
+Updating patchfile: patch-aa
+Updating patchfile: patch-as
+Updating patchfile: patch-foo.c
+Updating patchfile: patch-foo_bar.c
+$ make clean
+$ send-pr
+[...]
diff --git a/Tools/scripts/patchtool.py b/Tools/scripts/patchtool.py
new file mode 100755
index 000000000000..08f8f528851d
--- /dev/null
+++ b/Tools/scripts/patchtool.py
@@ -0,0 +1,639 @@
+#!/usr/local/bin/python
+#
+# patchtool.py - a tool to automate common operation with patchfiles in the
+# FreeBSD Ports Collection.
+#
+# ----------------------------------------------------------------------------
+# "THE BEER-WARE LICENSE" (Revision 42, (c) Poul-Henning Kamp):
+# Maxim Sobolev <sobomax@FreeBSD.org> wrote this file. As long as you retain
+# this notice you can do whatever you want with this stuff. If we meet some
+# day, and you think this stuff is worth it, you can buy me a beer in return.
+#
+# Maxim Sobolev
+# ----------------------------------------------------------------------------
+#
+# $FreeBSD$
+#
+# MAINTAINER= sobomax@FreeBSD.org <- any unapproved commits to this file are
+# highly discouraged!!!
+#
+
+import os, os.path, popen2, sys, getopt, glob, errno, types
+
+# Some global variables used as constants
+True = 1
+False = 0
+
+
+# Tweakable global variables. User is able to override any of these by setting
+# appropriate environment variable prefixed by `PT_', eg:
+# $ export PT_CVS_ID="FooOS"
+# $ export PT_DIFF_CMD="/usr/local/bin/mydiff"
+# will force script to use "FooOS" as a CVS_ID instead of "FreeBSD" and
+# "/usr/local/bin/mydiff" as a command to generate diffs.
+class Vars:
+ CVS_ID = 'FreeBSD'
+
+ DIFF_ARGS = '-du'
+ DIFF_SUFX = '.orig'
+ PATCH_PREFIX = 'patch-'
+ RCSDIFF_SUFX = ',v'
+
+ CD_CMD = 'cd'
+ DIFF_CMD = '/usr/bin/diff'
+ MAKE_CMD = '/usr/bin/make'
+ PRINTF_CMD = '/usr/bin/printf'
+ RCSDIFF_CMD = '/usr/bin/rcsdiff'
+
+ DEFAULT_MAKEFILE = 'Makefile'
+ DEV_NULL = '/dev/null'
+ ETC_MAKE_CONF = '/etc/make.conf'
+
+ SLASH_REPL_SYMBOL = '_' # The sysmbol to replace '/' when auto-generating
+ # patchnames
+
+
+#
+# Check if the supplied patch refers to a port's directory.
+#
+def isportdir(path, soft = False):
+ REQ_FILES = ('Makefile', 'pkg-comment', 'pkg-descr', 'pkg-plist', \
+ 'distinfo')
+ if not os.path.isdir(path) and soft != True:
+ raise IOError(errno.ENOENT, path)
+ # Not reached #
+
+ try:
+ content = os.listdir(path)
+ except OSError:
+ return False
+
+ for file in REQ_FILES:
+ if file not in content:
+ return False
+ return True
+
+
+#
+# Traverse directory tree up from the path pointed by argument and return if
+# root directory of a port is found.
+#
+def locateportdir(path, wrkdirprefix= '', strict = False):
+ # Flag to relax error checking in isportdir() function. It required when
+ # WRKDIRPREFIX is defined.
+ softisport = False
+
+ path = os.path.abspath(path)
+
+ if wrkdirprefix != '':
+ wrkdirprefix= os.path.abspath(wrkdirprefix)
+ commonprefix = os.path.commonprefix((path, wrkdirprefix))
+ if commonprefix != wrkdirprefix:
+ return ''
+ path = path[len(wrkdirprefix):]
+ softisport = True
+
+ while path != '/':
+ if isportdir(path, softisport) == True:
+ return path
+ path = os.path.abspath(os.path.join(path, '..'))
+
+ if strict == True:
+ raise LocatePDirError(path)
+ # Not reached #
+ else:
+ return ''
+
+
+#
+# Get value of a make(1) variable called varname. Optionally maintain a cache
+# for resolved varname:makepath pairs to speed-up operation if the same variable
+# from the exactly same file is requiested repeatedly (invocation of make(1) is
+# very expensive operation...)
+#
+def querymakevar(varname, path = 'Makefile', strict = False, cache = {}):
+ path = os.path.abspath(path)
+
+ if cache.has_key((varname, path)) == 1:
+ return cache[(varname, path)]
+
+ origpath = path
+ if os.path.isdir(path):
+ path = os.path.join(path, Vars.DEFAULT_MAKEFILE)
+ if not os.path.isfile(path):
+ raise IOError(errno.ENOENT, path)
+ # Not reached #
+
+ dir = os.path.dirname(path)
+ CMDLINE = '%s %s && %s -f %s -V %s' % (Vars.CD_CMD, dir, Vars.MAKE_CMD, \
+ path, varname)
+ pipe = popen2.popen3(CMDLINE)
+ retval = ''
+ for line in pipe[0].readlines():
+ retval = retval + line.strip() + ' '
+ for fd in pipe:
+ fd.close()
+ retval = retval[:-1]
+ if strict == True and retval.strip() == '':
+ raise MakeVarError(path, varname)
+ # Not reached #
+
+ cache[(varname, origpath)] = retval
+ return retval
+
+
+#
+# Get a path of `path' relatively to wrksrc. For example:
+# path: /foo/bar
+# wrksrc: /foo/bar/baz/somefile.c
+# getrelpath: baz/somefile.c
+# Most of the code here is to handle cases when ../ operation is required to
+# reach wrksrc from path, for example:
+# path: /foo/bar
+# wrksrc: /foo/baz/somefile.c
+# getrelpath: ../baz/somefile.c
+#
+def getrelpath(path, wrksrc):
+ path = os.path.abspath(path)
+ wrksrc = os.path.abspath(wrksrc)
+ commonpart = os.path.commonprefix((path, wrksrc))
+ path = path[len(commonpart):]
+ wrksrc = wrksrc[len(commonpart):]
+ if wrksrc == '':
+ path = path[1:]
+ adjust = ''
+ while os.path.normpath(os.path.join(wrksrc, adjust)) != '.':
+ adjust = os.path.join(adjust, '..')
+ relpath = os.path.join(adjust, path)
+ return relpath
+
+
+#
+# Generare a diff between saved and current versions of the file pointed by the
+# wrksrc+path. Apply heuristics to locate saved version of the file in question
+# and if it fails assume that file is new, so /dev/null is to be used as
+# original file. Optionally save generated patch into `outfile' instead of
+# dumping it to stdout. Generated patches automatically being tagged with
+# "FreeBSD" cvs id.
+#
+def gendiff(path, wrksrc, outfile = ''):
+ IDGEN_CMD = '%s "\\n\\$%s\\$\\n\\n"' % (Vars.PRINTF_CMD, Vars.CVS_ID)
+
+ fullpath = os.path.join(wrksrc, path)
+ if not os.path.isfile(fullpath):
+ raise IOError(errno.ENOENT, fullpath)
+ # Not reached #
+
+ cmdline = ''
+ if os.path.isfile(fullpath + Vars.DIFF_SUFX): # Normal diff
+ cmdline = '%s %s %s%s %s' % (Vars.DIFF_CMD, Vars.DIFF_ARGS, path, \
+ Vars.DIFF_SUFX, path)
+ elif os.path.isfile(fullpath + Vars.RCSDIFF_SUFX): # RCS diff
+ cmdline = '%s %s %s' % (Vars.RCSDIFF_CMD, Vars.DIFF_ARGS, path)
+ else: # New file
+ cmdline = '%s %s %s %s' % (Vars.DIFF_CMD, Vars.DIFF_ARGS, \
+ Vars.DEV_NULL, path)
+
+ if outfile != '':
+ cmdline = '( %s && %s ) 1>%s 2>%s' % (IDGEN_CMD, cmdline, outfile, \
+ Vars.DEV_NULL)
+ savedir = os.getcwd()
+ os.chdir(wrksrc)
+ exitstat = os.system(cmdline)
+ if os.WIFEXITED(exitstat):
+ exitval = os.WEXITSTATUS(exitstat)
+ if exitval == 0: # No differences were found
+ if outfile != '':
+ os.unlink(outfile)
+ retval = False
+ retmsg = 'no differencies found between original and current ' \
+ 'version of "%s"' % fullpath
+ elif exitval == 1: # Some differences were found
+ retval = True
+ retmsg = ''
+ else: # Error occured
+ raise ECmdError('"%s"' % cmdline, \
+ 'external command returned non-zero error code')
+ # Not reached #
+
+ os.chdir(savedir)
+ return (retval, retmsg)
+
+
+#
+# Automatically generate a name for a patch based on its path relative to
+# wrksrc. Use simple scheme to ensute 1-to-1 mapping between path and
+# patchname - replace all '_' with '__' and all '/' with '_'.
+#
+def makepatchname(path, patchdir = ''):
+ SRS = Vars.SLASH_REPL_SYMBOL
+ retval = Vars.PATCH_PREFIX + \
+ path.replace(SRS, SRS + SRS).replace('/', SRS)
+ retval = os.path.join(patchdir, retval)
+ return retval
+
+
+#
+# Write a specified message to stderr.
+#
+def write_msg(message):
+ if type(message) == types.StringType:
+ message = message,
+ sys.stderr.writelines(message)
+
+
+#
+# Print specified message to stdout and ask user [y/N]?. Optionally allow
+# specify default answer, i.e. return value if user typed only <cr>
+#
+def query_yn(message, default = False):
+ while True:
+ if default == True:
+ yn = 'Y/n'
+ elif default == False:
+ yn = 'y/N'
+ else:
+ yn = 'Y/N'
+
+ reply = raw_input('%s [%s]: ' % (message, yn))
+
+ if reply == 'y' or reply == 'Y':
+ return True
+ elif reply == 'n' or reply == 'N':
+ return False
+ elif reply == '' and default in (True, False):
+ return default
+ print 'Wrong answer "%s", please try again' % reply
+
+
+#
+# Print optional message and usage information and exit with specified exit
+# code.
+#
+def usage(code, msg = ''):
+ myname = os.path.basename(sys.argv[0])
+ write_msg((str(msg), """
+Usage: %s [-afi] file ...
+ %s -u [-i] [patchfile|patchdir ...]
+""" % (myname, myname)))
+ sys.exit(code)
+
+
+#
+# Simple custom exception
+#
+class MyError:
+ msg = 'error'
+
+ def __init__(self, file, msg=''):
+ self.file = file
+ if msg != '':
+ self.msg = msg
+
+ def __str__(self):
+ return '%s: %s' % (self.file, self.msg)
+
+
+#
+# Error parsing patchfile
+#
+class PatchError(MyError):
+ msg = 'corrupt patchfile, or not patchfile at all'
+
+
+#
+# Error executing external command
+#
+class ECmdError(MyError):
+ pass
+
+
+#
+# Error getting value of makefile variable
+#
+class MakeVarError(MyError):
+ def __init__(self, file, makevar, msg=''):
+ self.file = file
+ if msg != '':
+ self.msg = msg
+ else:
+ self.msg = 'can\'t get %s value' % makevar
+
+
+#
+# Error locating portdir
+#
+class LocatePDirError(MyError):
+ msg = 'can\'t locate portdir'
+
+
+class Patch:
+ fullpath = ''
+ minus3file = ''
+ plus3file = ''
+ wrksrc = ''
+ patchmtime = 0
+ targetmtime = 0
+
+ def __init__(self, path, wrksrc):
+ MINUS3_DELIM = '--- '
+ PLUS3_DELIM = '+++ '
+
+ path = os.path.abspath(path)
+ if not os.path.isfile(path):
+ raise IOError(errno.ENOENT, path)
+ # Not reached #
+
+ self.fullpath = path
+ file = open(path)
+
+ for line in file.readlines():
+ if self.minus3file == '':
+ if line[:len(MINUS3_DELIM)] == MINUS3_DELIM:
+ lineparts = line.split()
+ try:
+ self.minus3file = lineparts[1]
+ except IndexError:
+ raise PatchError(path)
+ # Not reached #
+ continue
+ elif line[:len(PLUS3_DELIM)] == PLUS3_DELIM:
+ lineparts = line.split()
+ try:
+ self.plus3file = lineparts[1]
+ except IndexError:
+ raise PatchError(path)
+ # Not reached #
+ break
+
+ file.close()
+
+ if self.minus3file == '' or self.plus3file == '':
+ raise PatchError(path)
+ # Not reached #
+
+ self.wrksrc = os.path.abspath(wrksrc)
+ self.patchmtime = os.path.getmtime(self.fullpath)
+ plus3file = os.path.join(self.wrksrc, self.plus3file)
+ if os.path.isfile(plus3file):
+ self.targetmtime = os.path.getmtime(plus3file)
+ else:
+ self.targetmtime = 0
+
+ def update(self, patch_cookiemtime = 0, ignoremtime = False):
+ targetfile = os.path.join(self.wrksrc, self.plus3file)
+ if not os.path.isfile(targetfile):
+ raise IOError(errno.ENOENT, targetfile)
+ # Not reached #
+
+ patchdir = os.path.dirname(self.fullpath)
+ if not os.path.isdir(patchdir):
+ os.mkdir(patchdir)
+
+ if ignoremtime == True or self.patchmtime == 0 or \
+ self.targetmtime == 0 or \
+ (self.patchmtime < self.targetmtime and \
+ patch_cookiemtime < self.targetmtime):
+ retval = gendiff(self.plus3file, self.wrksrc, self.fullpath)
+ if retval[0] == True:
+ self.patchmtime = os.path.getmtime(self.fullpath)
+ else:
+ retval = (False, 'patch is already up to date')
+ return retval
+
+
+class NewPatch(Patch):
+ def __init__(self, patchdir, wrksrc, relpath):
+ self.fullpath = makepatchname(relpath, os.path.abspath(patchdir))
+ self.wrksrc = os.path.abspath(wrksrc)
+ self.plus3file = relpath
+ self.minus3file = relpath
+ self.patchmtime = 0
+ plus3file = os.path.join(self.wrksrc, self.plus3file)
+ if os.path.isfile(plus3file):
+ self.targetmtime = os.path.getmtime(plus3file)
+ else:
+ self.targetmtime = 0
+
+
+class PatchesCollection:
+ patches = {}
+
+ def __init__(self):
+ self.patches = {}
+ pass
+
+ def adddir(self, patchdir, wrksrc):
+ if not os.path.isdir(patchdir):
+ raise IOError(errno.ENOENT, patchdir)
+ # Not reached #
+
+ for file in glob.glob(os.path.join(patchdir, Vars.PATCH_PREFIX + '*')):
+ self.addpatchfile(file, wrksrc)
+
+ def addpatchfile(self, path, wrksrc):
+ path = os.path.abspath(path)
+ if not self.patches.has_key(path):
+ self.addpatchobj(Patch(path, wrksrc))
+
+ def addpatchobj(self, patchobj):
+ self.patches[patchobj.fullpath] = patchobj
+
+ def lookupbyname(self, path):
+ path = os.path.abspath(path)
+ if self.patches.has_key(path):
+ return self.patches[path]
+ return None
+
+ def lookupbytarget(self, wrksrc, relpath):
+ wrksrc = os.path.abspath(wrksrc)
+ for patch in self.patches.values():
+ if wrksrc == patch.wrksrc and relpath == patch.plus3file:
+ return patch
+ return None
+
+ def getpatchobjs(self):
+ return self.patches.values()
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'afui')
+ except getopt.GetoptError, msg:
+ usage(2, msg)
+
+ automatic = False
+ force = False
+ mode = generate
+ ignoremtime = False
+
+ for o, a in opts:
+ if o == '-a':
+ automatic = True
+ elif o == '-f':
+ force = True
+ elif o == '-u':
+ mode = update
+ elif o == '-i':
+ ignoremtime = True
+ else:
+ usage(2)
+
+ # Allow user to override internal constants
+ for varname in dir(Vars):
+ if varname[:2] == '__' and varname[-2:] == '__':
+ continue
+ try:
+ value = os.environ['PT_' + varname]
+ setattr(Vars, varname, value)
+ except KeyError:
+ pass
+
+ mode(args, automatic, force, ignoremtime)
+
+ sys.exit(0)
+
+
+#
+# Display a diff or generate patchfile for the files pointed out by args.
+#
+def generate(args, automatic, force, ignoremtime):
+ if len(args) == 0:
+ usage(2, "ERROR: no input files specified")
+
+ patches = PatchesCollection()
+
+ for filepath in args:
+ if not os.path.isfile(filepath):
+ raise IOError(errno.ENOENT, filepath)
+ # Not reached #
+
+ wrkdirprefix = querymakevar('WRKDIRPREFIX', Vars.ETC_MAKE_CONF, False)
+ portdir = locateportdir(os.path.dirname(filepath), wrkdirprefix, True)
+ wrksrc = querymakevar('WRKSRC', portdir, True)
+
+ relpath = getrelpath(filepath, wrksrc)
+
+ if automatic == True:
+ patchdir = querymakevar('PATCHDIR', portdir, True)
+
+ if os.path.isdir(patchdir):
+ patches.adddir(patchdir, wrksrc)
+
+ patchobj = patches.lookupbytarget(wrksrc, relpath)
+ if patchobj == None:
+ patchobj = NewPatch(patchdir, wrksrc, relpath)
+ patches.addpatchobj(patchobj)
+
+ if not force and os.path.exists(patchobj.fullpath) and \
+ os.path.getsize(patchobj.fullpath) > 0:
+ try:
+ retval = query_yn('Target patchfile "%s" already ' \
+ 'exists, do you want to replace it?' % \
+ os.path.basename(patchobj.fullpath))
+ except KeyboardInterrupt:
+ sys.exit('\nAction aborted')
+ # Not reached #
+ if retval == False:
+ continue
+
+ write_msg('Generating patchfile: %s...' % \
+ os.path.basename(patchobj.fullpath))
+
+ try:
+ retval = None
+ retval = patchobj.update(ignoremtime = ignoremtime)
+ finally:
+ # Following tricky magic intended to let us append \n even if
+ # we are going to die due to unhandled exception
+ if retval == None:
+ write_msg('OUCH!\n')
+
+ if retval[0] == False:
+ write_msg('skipped (%s)\n' % retval[1])
+ else:
+ write_msg('ok\n')
+
+ else: # automatic != True
+ retval = gendiff(relpath, wrksrc)
+ if retval[0] == False:
+ write_msg('WARNING: %s\n' % retval[1])
+
+
+#
+# Atomatically update all patches pointed by args (may be individual
+# patchfiles, patchdirs or any directories in a portdirs). If directory argument
+# is encountered, all patches that belong to the port are updated. If no
+# arguments are supplied - current directory is assumed.
+#
+# The procedure homours last modification times of the patchfile, file from
+# which diff to be generated and `EXTRACT_COOKIE' file (usually
+# ${WRKDIR}/.extract_cookie) to update only those patches that are really need
+# to be updated.
+#
+def update(args, automatic, force, ignoremtime):
+ if len(args) == 0:
+ args = './',
+
+ for path in args:
+ if not os.path.exists(path):
+ raise IOError(errno.ENOENT, path)
+ # Not reached #
+
+ patches = PatchesCollection()
+
+ if os.path.isdir(path):
+ for wrkdirprefix in (querymakevar('WRKDIRPREFIX', \
+ Vars.ETC_MAKE_CONF, False), ''):
+ portdir = locateportdir(path, wrkdirprefix, False)
+ if portdir != '':
+ break
+ if portdir == '':
+ raise LocatePDirError(os.path.abspath(path))
+ # Not reached #
+
+ wrksrc = querymakevar('WRKSRC', portdir, True)
+ patchdir = querymakevar('PATCHDIR', portdir, True)
+
+ if os.path.isdir(patchdir):
+ patches.adddir(patchdir, wrksrc)
+ else:
+ continue
+
+ elif os.path.isfile(path):
+ portdir = locateportdir(os.path.dirname(path), '' , True)
+ wrksrc = querymakevar('WRKSRC', portdir, True)
+ patches.addpatchfile(path, wrksrc)
+
+ patch_cookie = querymakevar('PATCH_COOKIE', portdir, True)
+ if os.path.isfile(patch_cookie):
+ patch_cookiemtime = os.path.getmtime(patch_cookie)
+ else:
+ patch_cookiemtime = 0
+
+ for patchobj in patches.getpatchobjs():
+ write_msg('Updating patchfile: %s...' % \
+ os.path.basename(patchobj.fullpath))
+
+ try:
+ retval = None
+ retval = patchobj.update(patch_cookiemtime, \
+ ignoremtime)
+ finally:
+ if retval == None:
+ write_msg('OUCH!\n')
+
+ if retval[0] == False:
+ write_msg('skipped (%s)\n' % retval[1])
+ else:
+ write_msg('ok\n')
+
+
+if __name__ == '__main__':
+ try:
+ main()
+ except (PatchError, ECmdError, MakeVarError, LocatePDirError), msg:
+ sys.exit('ERROR: ' + str(msg))
+ except IOError, (code, msg):
+ sys.exit('ERROR: %s: %s' % (str(msg), os.strerror(code)))
+