diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/ae-cvs-ci | 190 | ||||
-rwxr-xr-x | bin/ae-svn-ci | 226 | ||||
-rw-r--r-- | bin/caller-tree.py | 90 | ||||
-rw-r--r-- | bin/scons-cdist | 214 | ||||
-rw-r--r-- | bin/scons-diff.py | 189 |
5 files changed, 844 insertions, 65 deletions
diff --git a/bin/ae-cvs-ci b/bin/ae-cvs-ci new file mode 100755 index 0000000..3dcc287 --- /dev/null +++ b/bin/ae-cvs-ci @@ -0,0 +1,190 @@ +# +# aegis - project change supervisor +# Copyright (C) 2004 Peter Miller; +# All rights reserved. +# +# As a specific exception to the GPL, you are allowed to copy +# this source file into your own project and modify it, without +# releasing your project under the GPL, unless there is some other +# file or condition which would require it. +# +# MANIFEST: shell script to commit changes to CVS +# +# It is assumed that your CVSROOT and CVS_RSH environment variables have +# already been set appropriately. +# +# This script is expected to be run as by integrate_pass_notify_command +# and as such the baseline has already assumed the shape asked for by +# the change. +# +# integrate_pass_notify_command = +# "$bin/ae-cvs-ci $project $change"; +# +# Alternatively, you may wish to tailor this script to the individual +# needs of your project. Make it a source file, e.g. "etc/ae-cvs-ci.sh" +# and then use the following: +# +# integrate_pass_notify_command = +# "$sh ${s etc/ae-cvs-ci} $project $change"; +# + +USAGE="Usage: $0 <project> <change>" + +PRINT="echo" +EXECUTE="eval" + +while getopts "hnq" FLAG +do + case ${FLAG} in + h ) + echo "${USAGE}" + exit 0 + ;; + n ) + EXECUTE=":" + ;; + q ) + PRINT=":" + ;; + * ) + echo "$0: unknown option ${FLAG}" >&2 + exit 1 + ;; + esac +done + +shift `expr ${OPTIND} - 1` + +case $# in +2) + project=$1 + change=$2 + ;; +*) + echo "${USAGE}" 1>&2 + exit 1 + ;; +esac + +here=`pwd` + +AEGIS_PROJECT=$project +export AEGIS_PROJECT +AEGIS_CHANGE=$change +export AEGIS_CHANGE + +module=`echo $project | sed 's|[.].*||'` + +baseline=`aegis -cd -bl` + +if test X${TMPDIR} = X; then TMPDIR=/var/tmp; fi + +TMP=${TMPDIR}/ae-cvs-ci.$$ +mkdir ${TMP} +cd ${TMP} + +PWD=`pwd` +if test X${PWD} != X${TMP}; then + echo "$0: ended up in ${PWD}, not ${TMP}" >&2 + exit 1 +fi + +fail() +{ + set +x + cd $here + rm -rf ${TMP} + echo "FAILED" 1>&2 + exit 1 +} +trap "fail" 1 2 3 15 + +Command() +{ + ${PRINT} "$*" + ${EXECUTE} "$*" +} + +# +# Create a new CVS work area. +# +# Note: this assumes the module is checked-out into a directory of the +# same name. Is there a way to ask CVS where is is going to put a +# modules, so we can always get the "cd" right? +# +${PRINT} cvs co $module +${EXECUTE} cvs co $module > LOG 2>&1 +if test $? -ne 0; then cat LOG; fail; fi +${EXECUTE} cd $module + +# +# Now we need to extract the sources from Aegis and drop them into the +# CVS work area. There are two ways to do this. +# +# The first way is to use the generated tarball. +# This has the advantage that it has the Makefile.in file in it, and +# will work immediately. +# +# The second way is to use aetar, which will give exact sources, and +# omit all derived files. This will *not* include the Makefile.in, +# and so will not be readily compilable. +# +# gunzip < $baseline/export/${project}.tar.gz | tardy -rp ${project} | tar xf - +aetar -send -o - | tar xzf - + +# +# If any new directories have been created we will need to add them +# to CVS before we can add the new files which we know are in them, +# or they would not have been created. Do this only if the -n option +# isn't used, because if it is, we won't have actually checked out the +# source and we'd erroneously report that all of them need to be added. +# +if test "X${EXECUTE}" != "X:" +then + find . \( -name CVS -o -name Attic \) -prune -o -type d -print | + xargs --max-args=1 | + while read dir + do + if [ ! -d $dir/CVS ] + then + Command cvs add $dir + fi + done +fi + +# +# Use the Aegis meta-data to perform some CVS commands that CVS can't +# figure out for itself. +# +aegis -l cf -unf | sed 's| -> [0-9][0-9.]*||' | +while read usage action rev filename +do + if test "x$filename" = "x" + then + filename="$rev" + fi + case $action in + create) + Command cvs add $filename + ;; + remove) + Command rm -f $filename + Command cvs remove $filename + ;; + *) + ;; + esac +done + +# +# Now commit all the changes. +# +message=`aesub '${version} - ${change description}'` +Command cvs -q commit -m \"$message\" + +# +# All done. Clean up and go home. +# +cd $here +rm -rf ${TMP} +exit 0 diff --git a/bin/ae-svn-ci b/bin/ae-svn-ci new file mode 100755 index 0000000..e5b81a4 --- /dev/null +++ b/bin/ae-svn-ci @@ -0,0 +1,226 @@ +# +# aegis - project change supervisor +# Copyright (C) 2004 Peter Miller; +# All rights reserved. +# +# As a specific exception to the GPL, you are allowed to copy +# this source file into your own project and modify it, without +# releasing your project under the GPL, unless there is some other +# file or condition which would require it. +# +# MANIFEST: shell script to commit changes to Subversion +# +# This script is expected to be run by the integrate_pass_notify_command +# and as such the baseline has already assumed the shape asked for by +# the change. +# +# integrate_pass_notify_command = +# "$bin/ae-svn-ci $project $change http://svn.site.com/svn/trunk --username svn_user"; +# +# Alternatively, you may wish to tailor this script to the individual +# needs of your project. Make it a source file, e.g. "etc/ae-svn-ci.sh" +# and then use the following: +# +# integrate_pass_notify_command = +# "$sh ${s etc/ae-svn-ci} $project $change http://svn.site.com/svn/trunk --username svn_user"; +# + +USAGE="Usage: $0 [-hnq] <project> <change> <url> [<co_options>]" + +PRINT="echo" +EXECUTE="eval" + +while getopts "hnq" FLAG +do + case ${FLAG} in + h ) + echo "${USAGE}" + exit 0 + ;; + n ) + EXECUTE=":" + ;; + q ) + PRINT=":" + ;; + * ) + echo "$0: unknown option ${FLAG}" >&2 + exit 1 + ;; + esac +done + +shift `expr ${OPTIND} - 1` + +case $# in +[012]) + echo "${USAGE}" 1>&2 + exit 1 + ;; +*) + project=$1 + change=$2 + svn_url=$3 + shift 3 + svn_co_flags=$* + ;; +esac + +here=`pwd` + +AEGIS_PROJECT=$project +export AEGIS_PROJECT +AEGIS_CHANGE=$change +export AEGIS_CHANGE + +module=`echo $project | sed 's|[.].*||'` + +baseline=`aegis -cd -bl` + +if test X${TMPDIR} = X; then TMPDIR=/var/tmp; fi + +TMP=${TMPDIR}/ae-svn-ci.$$ +mkdir ${TMP} +cd ${TMP} + +PWD=`pwd` +if test X${PWD} != X${TMP}; then + echo "$0: ended up in ${PWD}, not ${TMP}" >&2 + exit 1 +fi + +fail() +{ + set +x + cd $here + rm -rf ${TMP} + echo "FAILED" 1>&2 + exit 1 +} +trap "fail" 1 2 3 15 + +Command() +{ + ${PRINT} "$*" + ${EXECUTE} "$*" +} + +# +# Create a new Subversion work area. +# +# Note: this assumes the module is checked-out into a directory of the +# same name. Is there a way to ask Subversion where it is going to put a +# module, so we can always get the "cd" right? +# +${PRINT} svn co $svn_url $module $svn_co_flags +${EXECUTE} svn co $svn_url $module $svn_co_flags > LOG 2>&1 +if test $? -ne 0; then cat LOG; fail; fi +${EXECUTE} cd $module + +# +# Now we need to extract the sources from Aegis and drop them into the +# Subversion work area. There are two ways to do this. +# +# The first way is to use the generated tarball. +# This has the advantage that it has the Makefile.in file in it, and +# will work immediately. +# +# The second way is to use aetar, which will give exact sources, and +# omit all derived files. This will *not* include the Makefile.in, +# and so will not be readily compilable. +# +# gunzip < $baseline/export/${project}.tar.gz | tardy -rp ${project} | tar xf - +aetar -send -o - | tar xzf - + +# +# If any new directories have been created we will need to add them +# to Subversion before we can add the new files which we know are in them, +# or they would not have been created. Do this only if the -n option +# isn't used, because if it is, we won't have actually checked out the +# source and we'd erroneously report that all of them need to be added. +# +if test "X${EXECUTE}" != "X:" +then + find . -name .svn -prune -o -type d -print | + xargs --max-args=1 | + while read dir + do + if [ ! -d $dir/.svn ] + then + Command svn add -N $dir + fi + done +fi + +# +# Use the Aegis meta-data to perform some commands that Subversion can't +# figure out for itself. We use an inline "aer" report script to identify +# when a remove-create pair are actually due to a move. +# +aegis -rpt -nph -f - <<_EOF_ | +auto cs; +cs = project[project_name()].state.branch.change[change_number()]; + +columns({width = 1000;}); + +auto file, moved; +for (file in cs.src) +{ + if (file.move != "") + moved[file.move] = 1; +} + +auto action; +for (file in cs.src) +{ + if (file.action == "remove" && file.move != "") + action = "move"; + else + action = file.action; + /* + * Suppress printing of any files created as the result of a move. + * These are printed as the destination when printing the line for + * the file that was *removed* as a result of the move. + */ + if (action != "create" || ! moved[file.file_name]) + print(sprintf("%s %s \\"%s\\" \\"%s\\"", file.usage, action, file.file_name, file.move)); +} +_EOF_ +while read line +do + eval set -- "$line" + usage="$1" + action="$2" + srcfile="$3" + dstfile="$4" + case $action in + create) + Command svn add $srcfile + ;; + remove) + Command rm -f $srcfile + Command svn remove $srcfile + ;; + move) + Command mv $dstfile $dstfile.move + Command svn move $srcfile $dstfile + Command cp $dstfile.move $dstfile + Command rm -f $dstfile.move + ;; + *) + ;; + esac +done + +# +# Now commit all the changes. +# +message=`aesub '${version} - ${change description}'` +Command svn commit -m \"$message\" + +# +# All done. Clean up and go home. +# +cd $here +rm -rf ${TMP} +exit 0 diff --git a/bin/caller-tree.py b/bin/caller-tree.py new file mode 100644 index 0000000..5d907b8 --- /dev/null +++ b/bin/caller-tree.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# +# Quick script to process the *summary* output from SCons.Debug.caller() +# and print indented calling trees with call counts. +# +# The way to use this is to add something like the following to a function +# for which you want information about who calls it and how many times: +# +# from SCons.Debug import caller +# caller(0, 1, 2, 3, 4, 5) +# +# Each integer represents how many stack frames back SCons will go +# and capture the calling information, so in the above example it will +# capture the calls six levels up the stack in a central dictionary. +# +# At the end of any run where SCons.Debug.caller() is used, SCons will +# print a summary of the calls and counts that looks like the following: +# +# Callers of Node/__init__.py:629(calc_signature): +# 1 Node/__init__.py:683(calc_signature) +# Callers of Node/__init__.py:676(gen_binfo): +# 6 Node/FS.py:2035(current) +# 1 Node/__init__.py:722(get_bsig) +# +# If you cut-and-paste that summary output and feed it to this script +# on standard input, it will figure out how these entries hook up and +# print a calling tree for each one looking something like: +# +# Node/__init__.py:676(gen_binfo) +# Node/FS.py:2035(current) 6 +# Taskmaster.py:253(make_ready_current) 18 +# Script/Main.py:201(make_ready) 18 +# +# Note that you should *not* look at the call-count numbers in the right +# hand column as the actual number of times each line *was called by* +# the function on the next line. Rather, it's the *total* number +# of times each function was found in the call chain for any of the +# calls to SCons.Debug.caller(). If you're looking at more than one +# function at the same time, for example, their counts will intermix. +# So use this to get a *general* idea of who's calling what, not for +# fine-grained performance tuning. + +import sys + +class Entry: + def __init__(self, file_line_func): + self.file_line_func = file_line_func + self.called_by = [] + self.calls = [] + +AllCalls = {} + +def get_call(flf): + try: + e = AllCalls[flf] + except KeyError: + e = AllCalls[flf] = Entry(flf) + return e + +prefix = 'Callers of ' + +c = None +for line in sys.stdin.readlines(): + if line[0] == '#': + pass + elif line[:len(prefix)] == prefix: + c = get_call(line[len(prefix):-2]) + else: + num_calls, flf = line.strip().split() + e = get_call(flf) + c.called_by.append((e, num_calls)) + e.calls.append(c) + +stack = [] + +def print_entry(e, level, calls): + print '%-72s%6s' % ((' '*2*level) + e.file_line_func, calls) + if e in stack: + print (' '*2*(level+1))+'RECURSION' + print + elif e.called_by: + stack.append(e) + for c in e.called_by: + print_entry(c[0], level+1, c[1]) + stack.pop() + else: + print + +for e in [ e for e in AllCalls.values() if not e.calls ]: + print_entry(e, 0, '') diff --git a/bin/scons-cdist b/bin/scons-cdist index 425e430..58b1bae 100644 --- a/bin/scons-cdist +++ b/bin/scons-cdist @@ -22,45 +22,58 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PROG=`basename $0` -FLAGS="ahnqrstz" -USAGE="Usage: ${PROG} [-${FLAGS}] change" +NOARGFLAGS="afhlnqrstz" +ARGFLAGS="p:" +ALLFLAGS="${NOARGFLAGS}${ARGFLAGS}" +USAGE="Usage: ${PROG} [-${NOARGFLAGS}] [-p project] change" HELP="$USAGE - -a Update the latest Aegis baseline (aedist) file. - -h Print this help message and exit. - -n Don't execute, just echo commands. - -q Quiet, don't print commands before executing them. - -r Rsync the Aegis repository to SourceForge. - -s Update the sourceforge.net CVS repository. - -t Update the tigris.org CVS repository. - -z Update the latest .zip file. + -a Update the latest Aegis baseline (aedist) file. + -f Force update, skipping up-front sanity check. + -h Print this help message and exit. + -l Update the local CVS repository. + -n Don't execute, just echo commands. + -p project Set the Aegis project. + -q Quiet, don't print commands before executing them. + -r Rsync the Aegis repository to SourceForge. + -s Update the sourceforge.net CVS repository. + -t Update the tigris.org CVS repository. + -z Update the latest .tar.gz and .zip files. " DO="" PRINT="echo" EXECUTE="eval" +SANITY_CHECK="yes" -while getopts $FLAGS FLAG; do - case $FLAG in - a | r | s | t | z ) - DO="${DO}${FLAG}" - ;; - h ) - echo "${HELP}" - exit 0 - ;; - n ) - EXECUTE=":" - ;; - q ) - PRINT=":" - ;; - * ) - echo "${USAGE}" >&2 - exit 1 - ;; - esac +while getopts $ALLFLAGS FLAG; do + case $FLAG in + a | l | r | s | t | z ) + DO="${DO}${FLAG}" + ;; + f ) + SANITY_CHECK="no" + ;; + h ) + echo "${HELP}" + exit 0 + ;; + n ) + EXECUTE=":" + ;; + p ) + AEGIS_PROJECT="${OPTARG}" + ;; + q ) + PRINT=":" + ;; + * ) + echo "FLAG = ${FLAG}" >&2 + echo "${USAGE}" >&2 + exit 1 + ;; + esac done shift `expr ${OPTIND} - 1` @@ -70,8 +83,14 @@ if test "X$1" = "X"; then exit 1 fi +if test "X${AEGIS_PROJECT}" = "X"; then + echo "$PROG: No AEGIS_PROJECT set." >&2 + echo "${USAGE}" >&2 + exit 1 +fi + if test "X$DO" = "X"; then - DO="arstz" + DO="alrstz" fi cmd() @@ -82,6 +101,27 @@ cmd() CHANGE=$1 +if test "X${SANITY_CHECK}" = "Xyes"; then + SCM="cvs" + SCMROOT="/home/scons/CVSROOT/scons" + DELTA=`aegis -l -ter cd ${CHANGE} | sed -n 's/.*, Delta \([0-9]*\)\./\1/p'` + if test "x${DELTA}" = "x"; then + echo "${PROG}: Could not find delta for change ${CHANGE}." >&2 + echo "Has this finished integrating? Change ${CHANGE} not distributed." >&2 + exit 1 + fi + PREV_DELTA=`expr ${DELTA} - 1` + COMMAND="scons-scmcheck -D ${PREV_DELTA} -d q -p ${AEGIS_PROJECT} -s ${SCM} ${SCMROOT}" + $PRINT "${COMMAND}" + OUTPUT=`${COMMAND}` + if test "X${OUTPUT}" != "X"; then + echo "${PROG}: ${SCMROOT} is not up to date:" >&2 + echo "${OUTPUT}" >& 2 + echo "Did you skip any changes? Change ${CHANGE} not distributed." >&2 + exit 1 + fi +fi + if test X$EXECUTE != "X:" -a "X$SSH_AGENT_PID" = "X"; then eval `ssh-agent` ssh-add @@ -95,29 +135,38 @@ BASELINE=`aesub -p ${AEGIS_PROJECT} -c ${CHANGE} '${Project trunk_name}'` TMPBLAE="/tmp/${BASELINE}.ae" TMPCAE="/tmp/${AEGIS_PROJECT}.C${CHANGE}.ae" -SFLOGIN="stevenknight" -SFHOST="scons.sourceforge.net" -SFDEST="/home/groups/s/sc/scons/htdocs" +# Original values for SourceForge. +#SFLOGIN="stevenknight" +#SFHOST="scons.sourceforge.net" +#SFDEST="/home/groups/s/sc/scons/htdocs" + +SCONSLOGIN="scons" +SCONSHOST="manam.pair.com" +#SCONSDEST="public_html/production" +SCONSDEST="public_ftp" # # Copy the baseline .ae to the constant location on SourceForge. # case "${DO}" in - *a* ) - cmd "aedist -s -bl -p ${AEGIS_PROJECT} > ${TMPBLAE}" - cmd "scp ${TMPBLAE} ${SFLOGIN}@${SFHOST}:${SFDEST}/${BASELINE}.ae" - cmd "rm ${TMPBLAE}" - ;; +*a* ) + cmd "aedist -s -bl -p ${AEGIS_PROJECT} > ${TMPBLAE}" + cmd "scp ${TMPBLAE} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/${BASELINE}.ae" + cmd "rm ${TMPBLAE}" + ;; esac # -# Copy the latest .zip file to the constant location on SourceForge. +# Copy the latest .tar.gz and .zip files to the constant location on +# SourceForge. # case "${DO}" in - *z* ) - BUILD_DIST=`aegis -p ${AEGIS_PROJECT} -cd -bl`/build/dist - SCONS_SRC=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`.zip - cmd "scp ${BUILD_DIST}/${SCONS_SRC} ${SFLOGIN}@${SFHOST}:${SFDEST}/scons-src-latest.zip" +*z* ) + BUILD_DIST=`aegis -p ${AEGIS_PROJECT} -cd -bl`/build/dist + SCONS_SRC_TAR_GZ=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.tar.gz + SCONS_SRC_ZIP=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.zip + cmd "scp ${BUILD_DIST}/${SCONS_SRC_TAR_GZ} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.tar.gz" + cmd "scp ${BUILD_DIST}/${SCONS_SRC_ZIP} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.zip" esac # @@ -144,38 +193,73 @@ esac # We no longer use the --stats option. # case "${DO}" in - *r* ) - LOCAL=/home/scons/scons - REMOTE=/home/groups/s/sc/scons/scons - cmd "/usr/bin/rsync --rsh=ssh -l -p -r -t -z \ - --exclude build \ - --exclude '*,D' \ - --exclude '*.pyc' \ - --exclude aegis.log \ - --exclude '.sconsign*' \ - --delete --delete-excluded \ - --progress -v \ - ${LOCAL}/. scons.sourceforge.net:${REMOTE}/." - ;; +*r* ) + LOCAL=/home/scons/scons + REMOTE=/home/groups/s/sc/scons/scons + cmd "/usr/bin/rsync --rsh='ssh -l stevenknight' \ + -l -p -r -t -z \ + --exclude build \ + --exclude '*,D' \ + --exclude '*.pyc' \ + --exclude aegis.log \ + --exclude '.sconsign*' \ + --delete --delete-excluded \ + --progress -v \ + ${LOCAL}/. scons.sourceforge.net:${REMOTE}/." + ;; esac # -# Sync the CVS tree with Tigris.org. +# Sync the CVS tree with the local repository. # case "${DO}" in - *t* ) - cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/tigris.org/scons" - ;; +*l* ) + ( + export CVSROOT=/home/scons/CVSROOT/scons + #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/baldmt.com/scons" + cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}" + ) + ;; +esac + +# +# Sync the Subversion tree with Tigris.org. +# +case "${DO}" in +*t* ) + ( + SVN=http://scons.tigris.org/svn/scons + case ${AEGIS_PROJECT} in + scons.0.96 ) + SVN_URL=${SVN}/branches/core + ;; + scons.0.96.513 ) + SVN_URL=${SVN}/branches/sigrefactor + ;; + * ) + echo "$PROG: Don't know SVN branch for '${AEGIS_PROJECT}'" >&2 + exit 1 + ;; + esac + SVN_CO_FLAGS="--username stevenknight" + #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/tigris.org/scons" + cmd "ae-svn-ci ${AEGIS_PROJECT} ${CHANGE} ${SVN_URL} ${SVN_CO_FLAGS}" + ) + ;; esac # # Sync the CVS tree with SourceForge. # case "${DO}" in - *s* ) +*s* ) + ( export CVS_RSH=ssh - cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/sourceforge.net/scons" - ;; + export CVSROOT=:ext:stevenknight@scons.cvs.sourceforge.net:/cvsroot/scons + #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/sourceforge.net/scons" + cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}" + ) + ;; esac # @@ -185,4 +269,4 @@ esac # #aedist -s -p ${AEGIS_PROJECT} ${CHANGE} > ${TMPCAE} #aegis -l -p ${AEGIS_PROJECT} -c ${CHANGE} cd | -# pine -attach_and_delete ${TMPCAE} scons-aedist@lists.sourceforge.net +# pine -attach_and_delete ${TMPCAE} scons-aedist@lists.sourceforge.net diff --git a/bin/scons-diff.py b/bin/scons-diff.py new file mode 100644 index 0000000..6cfe25a --- /dev/null +++ b/bin/scons-diff.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +# +# scons-diff.py - diff-like utility for comparing SCons trees +# +# This supports most common diff options (with some quirks, like you can't +# just say -c and have it use a default value), but canonicalizes the +# various version strings within the file like __revision__, __build__, +# etc. so that you can diff trees without having to ignore changes in +# version lines. +# + +import difflib +import getopt +import os.path +import re +import sys + +Usage = """\ +Usage: scons-diff.py [OPTIONS] dir1 dir2 +Options: + -c NUM, --context=NUM Print NUM lines of copied context. + -h, --help Print this message and exit. + -n Don't canonicalize SCons lines. + -q, --quiet Print only whether files differ. + -r, --recursive Recursively compare found subdirectories. + -s Report when two files are the same. + -u NUM, --unified=NUM Print NUM lines of unified context. +""" + +opts, args = getopt.getopt(sys.argv[1:], + 'c:dhnqrsu:', + ['context=', 'help', 'recursive', 'unified=']) + +diff_type = None +edit_type = None +context = 2 +recursive = False +report_same = False +diff_options = [] + +def diff_line(left, right): + if diff_options: + opts = ' ' + ' '.join(diff_options) + else: + opts = '' + print 'diff%s %s %s' % (opts, left, right) + +for o, a in opts: + if o in ('-c', '-u'): + diff_type = o + context = int(a) + diff_options.append(o) + elif o in ('-h', '--help'): + print Usage + sys.exit(0) + elif o in ('-n'): + diff_options.append(o) + edit_type = o + elif o in ('-q'): + diff_type = o + diff_line = lambda l, r: None + elif o in ('-r', '--recursive'): + recursive = True + diff_options.append(o) + elif o in ('-s'): + report_same = True + +try: + left, right = args +except ValueError: + sys.stderr.write(Usage) + sys.exit(1) + +def quiet_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A function with the same calling signature as difflib.context_diff + (diff -c) and difflib.unified_diff (diff -u) but which prints + output like the simple, unadorned 'diff" command. + """ + if a == b: + return [] + else: + return ['Files %s and %s differ\n' % (fromfile, tofile)] + +def simple_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A function with the same calling signature as difflib.context_diff + (diff -c) and difflib.unified_diff (diff -u) but which prints + output like the simple, unadorned 'diff" command. + """ + sm = difflib.SequenceMatcher(None, a, b) + def comma(x1, x2): + return x1+1 == x2 and str(x2) or '%s,%s' % (x1+1, x2) + result = [] + for op, a1, a2, b1, b2 in sm.get_opcodes(): + if op == 'delete': + result.append("%sd%d\n" % (comma(a1, a2), b1)) + result.extend(map(lambda l: '< ' + l, a[a1:a2])) + elif op == 'insert': + result.append("%da%s\n" % (a1, comma(b1, b2))) + result.extend(map(lambda l: '> ' + l, b[b1:b2])) + elif op == 'replace': + result.append("%sc%s\n" % (comma(a1, a2), comma(b1, b2))) + result.extend(map(lambda l: '< ' + l, a[a1:a2])) + result.append('---\n') + result.extend(map(lambda l: '> ' + l, b[b1:b2])) + return result + +diff_map = { + '-c' : difflib.context_diff, + '-q' : quiet_diff, + '-u' : difflib.unified_diff, +} + +diff_function = diff_map.get(diff_type, simple_diff) + +baseline_re = re.compile('(# |@REM )/home/\S+/baseline/') +comment_rev_re = re.compile('(# |@REM )(\S+) 0.96.[CD]\d+ \S+ \S+( knight)') +revision_re = re.compile('__revision__ = "[^"]*"') +build_re = re.compile('__build__ = "[^"]*"') +date_re = re.compile('__date__ = "[^"]*"') + +def lines_read(file): + return open(file).readlines() + +def lines_massage(file): + text = open(file).read() + text = baseline_re.sub('\\1', text) + text = comment_rev_re.sub('\\1\\2\\3', text) + text = revision_re.sub('__revision__ = "__FILE__"', text) + text = build_re.sub('__build__ = "0.96.92.DXXX"', text) + text = date_re.sub('__date__ = "2006/08/25 02:59:00"', text) + return text.splitlines(1) + +lines_map = { + '-n' : lines_read, +} + +lines_function = lines_map.get(edit_type, lines_massage) + +def do_diff(left, right, diff_subdirs): + if os.path.isfile(left) and os.path.isfile(right): + diff_file(left, right) + elif not os.path.isdir(left): + diff_file(left, os.path.join(right, os.path.split(left)[1])) + elif not os.path.isdir(right): + diff_file(os.path.join(left, os.path.split(right)[1]), right) + elif diff_subdirs: + diff_dir(left, right) + +def diff_file(left, right): + l = lines_function(left) + r = lines_function(right) + d = diff_function(l, r, left, right, context) + try: + text = ''.join(d) + except IndexError: + sys.stderr.write('IndexError diffing %s and %s\n' % (left, right)) + else: + if text: + diff_line(left, right) + print text, + elif report_same: + print 'Files %s and %s are identical' % (left, right) + +def diff_dir(left, right): + llist = os.listdir(left) + rlist = os.listdir(right) + u = {} + for l in llist: + u[l] = 1 + for r in rlist: + u[r] = 1 + clist = [ x for x in u.keys() if x[-4:] != '.pyc' ] + clist.sort() + for x in clist: + if x in llist: + if x in rlist: + do_diff(os.path.join(left, x), + os.path.join(right, x), + recursive) + else: + print 'Only in %s: %s' % (left, x) + else: + print 'Only in %s: %s' % (right, x) + +do_diff(left, right, True) |