| # Gaudi Framework, version v20r4
Generated: 8 Jan 2009

# make_patch.py

Go to the documentation of this file.

    00001 #!/usr/bin/env python
    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     # make cmt print one line per package with python syntax
    00029     pkg_dirs = "[" + cmt("broadcast",r'echo "(\"<package>\", \"$PWD\"),"')[0] + ']'
    00030     return eval(pkg_dirs)
    00031
    00032 def matches(filename, patterns):
    00033     """
    00034     Returns True if any of the specified glob patterns (list of strings) matched
    00035     the string 'filename'.
    00036     """
    00037     for p in patterns:
    00038         if fnmatch(filename, p):
    00039             logging.debug("Excluding file: %r", filename)
    00040             return True
    00041     return False
    00042
    00043 def expand_dirs(files, basepath = ""):
    00044     """
    00045     Replace the entries in files that correspond to directories with the list of
    00046     files in those directories.
    00047     """
    00048     if basepath:
    00049         lb = len(basepath)+1
    00050     else:
    00051         lb = 0
    00052     newlist = []
    00053     for f in files:
    00054         base = os.path.join(basepath, f)
    00055         if os.path.isdir(base):
    00056             for root, ds, fs in os.walk(base):
    00057                 for ff in fs:
    00058                     newlist.append(os.path.join(root,ff)[lb:])
    00059         else:
    00060             newlist.append(f)
    00061     return newlist
    00062
    00063 def revision_diff_cmd(cwd):
    00064     if os.path.isdir(os.path.join(cwd, "CVS")):
    00065         return cvs("diff", "-upN", cwd = cwd)
    00066     else:
    00067         # special treatment to show new files in a way compatible with CVS
    00068         out, err = svn("status", cwd = cwd)
    00069
    00070         newfiles = [ l
    00071                      for l in out.splitlines()
    00072                      if l.startswith("? ") ]
    00073         out, err = svn("diff", cwd = cwd)
    00074         if newfiles:
    00075             out = "\n".join(newfiles) + "\n" + out
    00076         return out, err
    00077
    00078 def diff_pkg(name, cmtdir, exclusions = []):
    00079     """
    00080     Return the patch data for a package.
    00081     """
    00082     rootdir = os.path.dirname(cmtdir)
    00083     out, err = revision_diff_cmd(cwd = rootdir)
    00084     # extract new files
    00085     new_files = [ l.split()[1]
    00086                   for l in out.splitlines()
    00087                   if l.startswith("? ") ]
    00088     new_files = expand_dirs(new_files, rootdir)
    00089     new_files = [ f
    00090                   for f in new_files
    00091                   if not matches(f, exclusions) ]
    00092     # make diff segments for added files
    00093     for f in new_files:
    00094         logging.info("Added file %r", f)
    00095         #out += "diff -u -p -N %s\n" % os.path.basename(f)
    00096         #out += command("diff", "-upN", "/dev/null", f,
    00097         #               cwd = rootdir)[0]
    00098         out += "Index: %s\n" % f
    00099         out += "===================================================================\n"
    00100         out += command("diff", "-upN", "/dev/null", f,
    00101                        cwd = rootdir)[0]
    00102     # extract removed files
    00103     removed_files = [ l.split()[-1]
    00104                       for l in err.splitlines()
    00105                       if "cannot find" in l ]
    00106     removed_files = [ f
    00107                       for f in removed_files
    00108                       if not matches(f, exclusions) ]
    00109     # make diff segments for removed files (more tricky)
    00110     for f in removed_files:
    00111         logging.info("Removed file %r", f)
    00112         # retrieve the original content from CVS
    00113         orig = cvs("up", "-p", f,
    00114                    cwd = rootdir)[0]
    00115         out += "diff -u -p -N %s\n" % os.path.basename(f)
    00116         out += "--- %s\t1 Jan 1970 00:00:00 -0000\n" % f
    00117         out += "+++ /dev/null\t1 Jan 1970 00:00:00 -0000\n"
    00118         lines = orig.splitlines()
    00119         out += "@@ -1,%d +0,0 @@\n" % len(lines)
    00120         for l in lines:
    00121             out += '-%s\n' % l
    00122     # Fix the paths to have the package names
    00123     rex = re.compile(r"^(Index: |\? |\+\+\+ |--- (?!/dev/null))", re.MULTILINE)
    00124     out = rex.sub(r"\1%s%s" % (name, os.sep), out)
    00125     return out
    00126
    00127 def main():
    00128     from optparse import OptionParser
    00129     parser = OptionParser(description = "Produce a patch file from a CMT project. "
    00130                                         "The patch contains the changes with respect "
    00131                                         "to the CVS repository, including new files "
    00132                                         "that are present only locally. Run the script "
    00133                                         "from the cmt directory of a package." )
    00134     parser.add_option("-x", "--exclude", action="append", type="string",
    00135                       metavar="PATTERN", dest="exclusions",
    00136                       help="Pattern to exclude new files from the patch")
    00137     parser.add_option("-o", "--output", action="store", type="string",
    00138                       help="Name of the file to send the output to. Standard "
    00139                            "output is used if not specified")
    00140     parser.add_option("-v", "--verbose", action="store_true",
    00141                       help="Print some progress information on standard error")
    00142     parser.add_option("--debug", action="store_true",
    00143                       help="Print debug information on standard error")
    00144     parser.set_defaults(exclusions = [])
    00145
    00146     opts, args = parser.parse_args()
    00147
    00148     if opts.debug:
    00149         logging.basicConfig(level = logging.DEBUG)
    00150     elif opts.verbose:
    00151         logging.basicConfig(level = logging.INFO)
    00152
    00153     # default exclusions
    00154     opts.exclusions += [ "*.py[co]",
    00155                          "*.patch",
    00156                          "cmt/cleanup.*",
    00157                          "cmt/setup.*",
    00158                          "cmt/*.make",
    00159                          "cmt/Makefile",
    00160                          "cmt/*.nmake",
    00161                          "cmt/*.nmakesav",
    00162                          "cmt/NMake",
    00163                          "cmt/install.history",
    00164                          "slc3_ia32_gcc323*",
    00165                          "slc4_ia32_gcc34*",
    00166                          "slc4_amd64_gcc34*",
    00167                          "slc4_amd64_gcc43*",
    00168                          "win32_vc71*",
    00169                          "i686-slc3-gcc323*",
    00170                          "i686-slc4-gcc34*",
    00171                          "i686-slc4-gcc41*",
    00172                          "x86_64-slc4-gcc34*",
    00173                          "x86_64-slc4-gcc41*",
    00174                          "genConf",
    00175                          ]
    00176
    00177     # check if we are in the cmt directory before broadcasting
    00178     if not (os.path.basename(os.getcwd()) == "cmt" and os.path.exists("requirements")):
    00179         logging.error("This script must be executed from the cmt directory of a package.")
    00180         return 1
    00181
    00182     pkgs = broadcast_packages()
    00183     num_pkgs = len(pkgs)
    00184     count = 0
    00185
    00186     patch = ""
    00187     for name, path in pkgs:
    00188         count += 1
    00189         logging.info("Processing %s from %s (%d/%d)",
    00190                      name, os.path.dirname(path), count, num_pkgs)
    00191         patch += diff_pkg(name, path, opts.exclusions)
    00192
    00193     if opts.output:
    00194         logging.info("Writing patch file %r", opts.output)
    00195         open(opts.output,"w").write(patch)
    00196     else:
    00197         sys.stdout.write(patch)
    00198     return 0
    00199
    00200 if __name__ == "__main__":
    00201     sys.exit(main())