The Gaudi Framework  v36r9p1 (5c15b2bb)
make_patch.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
12 
13 import logging
14 import os
15 import re
16 import sys
17 from fnmatch import fnmatch
18 from subprocess import PIPE, STDOUT, Popen
19 
20 
21 def command(cmd, *args, **kwargs):
22  """
23  Simple wrapper to execute a command and return standard output and standard error.
24  """
25  d = {"stdout": PIPE, "stderr": PIPE}
26  d.update(kwargs)
27  cmd = [cmd] + list(args)
28  logging.debug("Execute command: %r %r", " ".join(cmd), kwargs)
29  proc = Popen(cmd, **d)
30  return proc.communicate()
31 
32 
33 cmt = lambda *args, **kwargs: command("cmt", *args, **kwargs)
34 cvs = lambda *args, **kwargs: command("cvs", *args, **kwargs)
35 svn = lambda *args, **kwargs: command("svn", *args, **kwargs)
36 
37 
39  """
40  Find the local packages the current one depends on (using 'cmt broadcast').
41  Returns a list of pairs ("package name","path to the cmt directory").
42  """
43  # make cmt print one line per package with python syntax
44  if not sys.platform.startswith("win"):
45  pkg_dirs = (
46  "[\n" + cmt("broadcast", r'echo "(\"<package>\", \"$PWD\"),"')[0] + "]"
47  )
48  else:
49  pkg_dirs = (
50  "[\n"
51  + cmt("broadcast", r'echo ("<package>", r"%<package>root%\cmt"),')[0]
52  + "]"
53  )
54  # Clean up a bit the output (actually needed only on Windows because of the newlines)
55  pkg_dirs = "\n".join(
56  [l.strip() for l in pkg_dirs.splitlines() if not l.startswith("#")]
57  )
58  return eval(pkg_dirs)
59 
60 
61 def matches(filename, patterns):
62  """
63  Returns True if any of the specified glob patterns (list of strings) matched
64  the string 'filename'.
65  """
66  for p in patterns:
67  if fnmatch(filename, p):
68  logging.debug("Excluding file: %r", filename)
69  return True
70  return False
71 
72 
73 def expand_dirs(files, basepath=""):
74  """
75  Replace the entries in files that correspond to directories with the list of
76  files in those directories.
77  """
78  if basepath:
79  lb = len(basepath) + 1
80  else:
81  lb = 0
82  newlist = []
83  for f in files:
84  base = os.path.join(basepath, f)
85  if os.path.isdir(base):
86  for root, ds, fs in os.walk(base):
87  for ff in fs:
88  newlist.append(os.path.join(root, ff)[lb:])
89  else:
90  newlist.append(f)
91  return newlist
92 
93 
95  if os.path.isdir(os.path.join(cwd, "CVS")):
96  return cvs("diff", "-upN", cwd=cwd)
97  else:
98  # special treatment to show new files in a way compatible with CVS
99  out, err = svn("status", cwd=cwd)
100 
101  newfiles = [l for l in out.splitlines() if l.startswith("? ")]
102  out, err = svn("diff", cwd=cwd)
103  if newfiles:
104  out = "\n".join(newfiles) + "\n" + out
105  return out, err
106 
107 
108 def diff_pkg(name, cmtdir, exclusions=[]):
109  """
110  Return the patch data for a package.
111  """
112  rootdir = os.path.dirname(cmtdir)
113  out, err = revision_diff_cmd(cwd=rootdir)
114  # extract new files
115  new_files = [l.split(None, 1)[1] for l in out.splitlines() if l.startswith("? ")]
116  new_files = expand_dirs(new_files, rootdir)
117  new_files = [f for f in new_files if not matches(f, exclusions)]
118  # make diff segments for added files
119  for f in new_files:
120  logging.info("Added file %r", f)
121  # out += "diff -u -p -N %s\n" % os.path.basename(f)
122  # out += command("diff", "-upN", "/dev/null", f,
123  # cwd = rootdir)[0]
124  out += "Index: %s\n" % f
125  out += "===================================================================\n"
126  out += command("diff", "-upN", "/dev/null", f, cwd=rootdir)[0]
127  # extract removed files
128  removed_files = [l.split()[-1] for l in err.splitlines() if "cannot find" in l]
129  removed_files = [f for f in removed_files if not matches(f, exclusions)]
130  # make diff segments for removed files (more tricky)
131  for f in removed_files:
132  logging.info("Removed file %r", f)
133  # retrieve the original content from CVS
134  orig = cvs("up", "-p", f, cwd=rootdir)[0]
135  out += "diff -u -p -N %s\n" % os.path.basename(f)
136  out += "--- %s\t1 Jan 1970 00:00:00 -0000\n" % f
137  out += "+++ /dev/null\t1 Jan 1970 00:00:00 -0000\n"
138  lines = orig.splitlines()
139  out += "@@ -1,%d +0,0 @@\n" % len(lines)
140  for l in lines:
141  out += "-%s\n" % l
142  # Fix the paths to have the package names
143  rex = re.compile(r"^(Index: |\? |\+\+\+ |--- (?!/dev/null))", re.MULTILINE)
144  out = rex.sub(r"\1%s/" % name, out)
145  return out
146 
147 
148 def main():
149  from optparse import OptionParser
150 
151  parser = OptionParser(
152  description="Produce a patch file from a CMT project. "
153  "The patch contains the changes with respect "
154  "to the CVS repository, including new files "
155  "that are present only locally. Run the script "
156  "from the cmt directory of a package."
157  )
158  parser.add_option(
159  "-x",
160  "--exclude",
161  action="append",
162  type="string",
163  metavar="PATTERN",
164  dest="exclusions",
165  help="Pattern to exclude new files from the patch",
166  )
167  parser.add_option(
168  "-o",
169  "--output",
170  action="store",
171  type="string",
172  help="Name of the file to send the output to. Standard "
173  "output is used if not specified",
174  )
175  parser.add_option(
176  "-v",
177  "--verbose",
178  action="store_true",
179  help="Print some progress information on standard error",
180  )
181  parser.add_option(
182  "--debug", action="store_true", help="Print debug information on standard error"
183  )
184  parser.set_defaults(exclusions=[])
185 
186  opts, args = parser.parse_args()
187 
188  if opts.debug:
189  logging.basicConfig(level=logging.DEBUG)
190  elif opts.verbose:
191  logging.basicConfig(level=logging.INFO)
192 
193  # default exclusions
194  opts.exclusions += [
195  "*.py[co]",
196  "*.patch",
197  "cmt/cleanup.*",
198  "cmt/setup.*",
199  "cmt/*.make",
200  "cmt/Makefile",
201  "cmt/*.nmake",
202  "cmt/*.nmakesav",
203  "cmt/NMake",
204  "cmt/install*.history",
205  "cmt/build.*.log",
206  "cmt/version.cmt",
207  "genConf",
208  "slc3_ia32_gcc323*",
209  "slc4_ia32_gcc34*",
210  "slc4_amd64_gcc34*",
211  "slc4_amd64_gcc43*",
212  "win32_vc71*",
213  "i686-slc[34567]-[ig]cc*",
214  "i686-slc[34567]-clang*",
215  "x86_64-slc[34567]-[ig]cc*",
216  "x86_64-slc[34567]-clang*",
217  ".eclipse",
218  ]
219  if "CMTCONFIG" in os.environ:
220  opts.exclusions.append(os.environ["CMTCONFIG"])
221 
222  # check if we are in the cmt directory before broadcasting
223  if not (os.path.basename(os.getcwd()) == "cmt" and os.path.exists("requirements")):
224  logging.error(
225  "This script must be executed from the cmt directory of a package."
226  )
227  return 1
228 
229  pkgs = broadcast_packages()
230  num_pkgs = len(pkgs)
231  count = 0
232 
233  patch = ""
234  for name, path in pkgs:
235  count += 1
236  logging.info(
237  "Processing %s from %s (%d/%d)",
238  name,
239  os.path.dirname(path),
240  count,
241  num_pkgs,
242  )
243  patch += diff_pkg(name, path, opts.exclusions)
244 
245  if sys.platform.startswith("win"):
246  # fix newlines chars
247  patch = patch.replace("\r", "")
248 
249  if opts.output:
250  logging.info("Writing patch file %r", opts.output)
251  open(opts.output, "w").write(patch)
252  else:
253  sys.stdout.write(patch)
254  return 0
255 
256 
257 if __name__ == "__main__":
258  sys.exit(main())
make_patch.diff_pkg
def diff_pkg(name, cmtdir, exclusions=[])
Definition: make_patch.py:108
make_patch.revision_diff_cmd
def revision_diff_cmd(cwd)
Definition: make_patch.py:94
make_patch.svn
def svn
Definition: make_patch.py:35
make_patch.command
def command(cmd, *args, **kwargs)
Definition: make_patch.py:21
make_patch.broadcast_packages
def broadcast_packages()
Definition: make_patch.py:38
make_patch.matches
def matches(filename, patterns)
Definition: make_patch.py:61
make_patch.expand_dirs
def expand_dirs(files, basepath="")
Definition: make_patch.py:73
make_patch.cmt
def cmt
Definition: make_patch.py:33
make_patch.cvs
def cvs
Definition: make_patch.py:34
make_patch.main
def main()
Definition: make_patch.py:148