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