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