00001
00002
00003 import os, sys, re
00004 from subprocess import Popen, PIPE, STDOUT
00005 from fnmatch import fnmatch
00006 import logging
00007
00008 def command(cmd, *args, **kwargs):
00009 """
00010 Simple wrapper to execute a command and return standard output and standard error.
00011 """
00012 d = {"stdout": PIPE, "stderr": PIPE}
00013 d.update(kwargs)
00014 cmd = [cmd] + list(args)
00015 logging.debug("Execute command: %r %r", " ".join(cmd), kwargs)
00016 proc = apply(Popen, (cmd,), d)
00017 return proc.communicate()
00018
00019 cmt = lambda *args, **kwargs: apply(command, ("cmt",) + args, kwargs)
00020 cvs = lambda *args, **kwargs: apply(command, ("cvs",) + args, kwargs)
00021 svn = lambda *args, **kwargs: apply(command, ("svn",) + args, kwargs)
00022
00023 def broadcast_packages():
00024 """
00025 Find the local packages the current one depends on (using 'cmt broadcast').
00026 Returns a list of pairs ("package name","path to the cmt directory").
00027 """
00028
00029 if not sys.platform.startswith("win"):
00030 pkg_dirs = "[\n" + cmt("broadcast",r'echo "(\"<package>\", \"$PWD\"),"')[0] + ']'
00031 else:
00032 pkg_dirs = "[\n" + cmt("broadcast",r'echo ("<package>", r"%<package>root%\cmt"),')[0] + ']'
00033
00034 pkg_dirs = "\n".join([l.strip() for l in pkg_dirs.splitlines() if not l.startswith("#")])
00035 return eval(pkg_dirs)
00036
00037 def matches(filename, patterns):
00038 """
00039 Returns True if any of the specified glob patterns (list of strings) matched
00040 the string 'filename'.
00041 """
00042 for p in patterns:
00043 if fnmatch(filename, p):
00044 logging.debug("Excluding file: %r", filename)
00045 return True
00046 return False
00047
00048 def expand_dirs(files, basepath = ""):
00049 """
00050 Replace the entries in files that correspond to directories with the list of
00051 files in those directories.
00052 """
00053 if basepath:
00054 lb = len(basepath)+1
00055 else:
00056 lb = 0
00057 newlist = []
00058 for f in files:
00059 base = os.path.join(basepath, f)
00060 if os.path.isdir(base):
00061 for root, ds, fs in os.walk(base):
00062 for ff in fs:
00063 newlist.append(os.path.join(root,ff)[lb:])
00064 else:
00065 newlist.append(f)
00066 return newlist
00067
00068 def revision_diff_cmd(cwd):
00069 if os.path.isdir(os.path.join(cwd, "CVS")):
00070 return cvs("diff", "-upN", cwd = cwd)
00071 else:
00072
00073 out, err = svn("status", cwd = cwd)
00074
00075 newfiles = [ l
00076 for l in out.splitlines()
00077 if l.startswith("? ") ]
00078 out, err = svn("diff", cwd = cwd)
00079 if newfiles:
00080 out = "\n".join(newfiles) + "\n" + out
00081 return out, err
00082
00083 def diff_pkg(name, cmtdir, exclusions = []):
00084 """
00085 Return the patch data for a package.
00086 """
00087 rootdir = os.path.dirname(cmtdir)
00088 out, err = revision_diff_cmd(cwd = rootdir)
00089
00090 new_files = [ l.split(None,1)[1]
00091 for l in out.splitlines()
00092 if l.startswith("? ") ]
00093 new_files = expand_dirs(new_files, rootdir)
00094 new_files = [ f
00095 for f in new_files
00096 if not matches(f, exclusions) ]
00097
00098 for f in new_files:
00099 logging.info("Added file %r", f)
00100
00101
00102
00103 out += "Index: %s\n" % f
00104 out += "===================================================================\n"
00105 out += command("diff", "-upN", "/dev/null", f,
00106 cwd = rootdir)[0]
00107
00108 removed_files = [ l.split()[-1]
00109 for l in err.splitlines()
00110 if "cannot find" in l ]
00111 removed_files = [ f
00112 for f in removed_files
00113 if not matches(f, exclusions) ]
00114
00115 for f in removed_files:
00116 logging.info("Removed file %r", f)
00117
00118 orig = cvs("up", "-p", f,
00119 cwd = rootdir)[0]
00120 out += "diff -u -p -N %s\n" % os.path.basename(f)
00121 out += "--- %s\t1 Jan 1970 00:00:00 -0000\n" % f
00122 out += "+++ /dev/null\t1 Jan 1970 00:00:00 -0000\n"
00123 lines = orig.splitlines()
00124 out += "@@ -1,%d +0,0 @@\n" % len(lines)
00125 for l in lines:
00126 out += '-%s\n' % l
00127
00128 rex = re.compile(r"^(Index: |\? |\+\+\+ |--- (?!/dev/null))", re.MULTILINE)
00129 out = rex.sub(r"\1%s/" % name, out)
00130 return out
00131
00132 def main():
00133 from optparse import OptionParser
00134 parser = OptionParser(description = "Produce a patch file from a CMT project. "
00135 "The patch contains the changes with respect "
00136 "to the CVS repository, including new files "
00137 "that are present only locally. Run the script "
00138 "from the cmt directory of a package." )
00139 parser.add_option("-x", "--exclude", action="append", type="string",
00140 metavar="PATTERN", dest="exclusions",
00141 help="Pattern to exclude new files from the patch")
00142 parser.add_option("-o", "--output", action="store", type="string",
00143 help="Name of the file to send the output to. Standard "
00144 "output is used if not specified")
00145 parser.add_option("-v", "--verbose", action="store_true",
00146 help="Print some progress information on standard error")
00147 parser.add_option("--debug", action="store_true",
00148 help="Print debug information on standard error")
00149 parser.set_defaults(exclusions = [])
00150
00151 opts, args = parser.parse_args()
00152
00153 if opts.debug:
00154 logging.basicConfig(level = logging.DEBUG)
00155 elif opts.verbose:
00156 logging.basicConfig(level = logging.INFO)
00157
00158
00159 opts.exclusions += [ "*.py[co]",
00160 "*.patch",
00161 "cmt/cleanup.*",
00162 "cmt/setup.*",
00163 "cmt/*.make",
00164 "cmt/Makefile",
00165 "cmt/*.nmake",
00166 "cmt/*.nmakesav",
00167 "cmt/NMake",
00168 "cmt/install*.history",
00169 "cmt/build.*.log",
00170 "cmt/version.cmt",
00171 "genConf",
00172 "slc3_ia32_gcc323*",
00173 "slc4_ia32_gcc34*",
00174 "slc4_amd64_gcc34*",
00175 "slc4_amd64_gcc43*",
00176 "win32_vc71*",
00177 "i686-slc3-gcc323*",
00178 "i686-slc4-gcc34*",
00179 "i686-slc4-gcc41*",
00180 "x86_64-slc4-gcc34*",
00181 "x86_64-slc4-gcc41*",
00182 "i686-slc5-gcc43*",
00183 "x86_64-slc5-gcc43*",
00184 "x86_64-slc5-icc*",
00185 ]
00186 if "CMTCONFIG" in os.environ:
00187 opts.exclusions.append(os.environ["CMTCONFIG"])
00188
00189
00190 if not (os.path.basename(os.getcwd()) == "cmt" and os.path.exists("requirements")):
00191 logging.error("This script must be executed from the cmt directory of a package.")
00192 return 1
00193
00194 pkgs = broadcast_packages()
00195 num_pkgs = len(pkgs)
00196 count = 0
00197
00198 patch = ""
00199 for name, path in pkgs:
00200 count += 1
00201 logging.info("Processing %s from %s (%d/%d)",
00202 name, os.path.dirname(path), count, num_pkgs)
00203 patch += diff_pkg(name, path, opts.exclusions)
00204
00205 if sys.platform.startswith("win"):
00206
00207 patch = patch.replace("\r","")
00208
00209 if opts.output:
00210 logging.info("Writing patch file %r", opts.output)
00211 open(opts.output,"w").write(patch)
00212 else:
00213 sys.stdout.write(patch)
00214 return 0
00215
00216 if __name__ == "__main__":
00217 sys.exit(main())