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