prepare_gaudi_release.py
Go to the documentation of this file.
1 #!/usr/bin.env python
2 '''
3 Script to prepare the release of Gaudi.
4 
5 @author Marco Clemencic
6 '''
7 
8 import os
9 import sys
10 import logging
11 import re
12 import ConfigParser
13 from subprocess import Popen, PIPE
14 
15 _VK_RE = re.compile(r'(\d+|\D+)')
16 def versionKey(x):
17  '''
18  Key function to be passes to list.sort() to sort strings
19  as version numbers.
20  '''
21  return [int(i) if i[0] in '0123456789' else i
22  for i in _VK_RE.split('v10r31')
23  if i]
24 
26  '''
27  Return the latest Gaudi tag (of the format "GAUDI/GAUDI_*").
28  '''
29  logging.info('looking for latest tag')
30  cmd = ['git', 'tag']
31  logging.debug('using command %r', cmd)
32  proc = Popen(cmd, stdout=PIPE)
33  output = proc.communicate()[0]
34  tags = [tag
35  for tag in output.splitlines()
36  if tag.startswith('GAUDI/GAUDI_')]
37  if tags:
38  tags.sort(key=versionKey)
39  logging.info('found %s', tags[-1])
40  return tags[-1]
41  logging.info('no valid tag found')
42 
43 def releaseNotes(path=os.curdir, from_tag=None, branch=None):
44  '''
45  Return the release notes (in the old LHCb format) extracted from git
46  commits for a given path.
47  '''
48  cmd = ['git', 'log',
49  '--first-parent', '--date=short',
50  '--pretty=format:! %ad - commit %h%n%n%w(80,1,3)- %s%n%n%b%n']
51  if from_tag:
52  cmd.append('{0}..{1}'.format(from_tag, branch or ''))
53  elif branch:
54  cmd.append(branch)
55  cmd.append('--')
56  cmd.append(path)
57  logging.info('preparing release notes for %s%s', path,
58  ' since ' + from_tag if from_tag else '')
59  logging.debug('using command %r', cmd)
60  proc = Popen(cmd, stdout=PIPE)
61  return proc.communicate()[0]
62 
63 def updateReleaseNotes(path, notes):
64  '''
65  Smartly prepend the content of notes to the release.notes file in path.
66  '''
67  notes_filename = os.path.join(path, 'doc', 'release.notes')
68  logging.info('updating %s', notes_filename)
69  from itertools import takewhile, dropwhile
70  def dropuntil(predicate, iterable):
71  return dropwhile(lambda x: not predicate(x), iterable)
72  with open(notes_filename) as notes_file:
73  orig_data = iter(list(notes_file))
74  header = takewhile(str.strip, orig_data)
75  with open(notes_filename, 'w') as notes_file:
76  notes_file.writelines(header)
77  notes_file.write('\n')
78  notes_file.writelines(l.rstrip() + '\n' for l in notes.splitlines())
79  notes_file.writelines(dropuntil(re.compile(r'^!?============').match, orig_data))
80 
81 # guess current version
82 _req_version_pattern = re.compile(r"^\s*version\s*(v[0-9]+r[0-9]+(?:p[0-9]+)?)\s*$")
83 _cml_version_pattern = re.compile(r"^\s*gaudi_subdir\s*\(\s*\S+\s+(v[0-9]+r[0-9]+(?:p[0-9]+)?)\)\s*$")
84 def extract_version(path):
85  """
86  Find the version number of a subdirectory.
87  """
88  global _cml_version_pattern
89  for l in open(os.path.join(path, 'CMakeLists.txt')):
90  m = _cml_version_pattern.match(l)
91  if m:
92  return m.group(1)
93  return None
94 
95 def change_cml_version(cml, newversion):
96  if os.path.exists(cml):
97  out = []
98  changed = False
99  for l in open(cml):
100  m = _cml_version_pattern.match(l)
101  if m and m.group(1) != newversion:
102  logging.debug('%s: %s -> %s', cml, m.group(1), newversion)
103  l = l.replace(m.group(1), newversion)
104  changed = True
105  out.append(l)
106  if changed:
107  open(cml, "w").writelines(out)
108 
109 def change_version(packagedir, newversion):
110  """
111  Compare the version of the package with the new one and update the package if
112  needed.
113 
114  Returns true if the package have been modified.
115  """
116  global _req_version_pattern
117  changed = False
118  out = []
119  req = os.path.join(packagedir, 'cmt', 'requirements')
120  for l in open(req):
121  m = _req_version_pattern.match(l)
122  if m:
123  if m.group(1) != newversion:
124  logging.debug('%s: %s -> %s', packagedir, m.group(1), newversion)
125  l = l.replace(m.group(1),newversion)
126  changed = True
127  out.append(l)
128  if changed:
129  open(req,"w").writelines(out)
130  # verify the version.cmt file
131  ver = os.path.join(packagedir,"version.cmt")
132  if os.path.exists(ver):
133  current = open(ver).read().strip()
134  if current != newversion:
135  open(ver,"w").write(newversion + "\n")
136  # update CMakeLists.txt
137  cml = os.path.normpath(os.path.join(packagedir, 'CMakeLists.txt'))
138  change_cml_version(cml, newversion)
139  if 'GaudiKernel' in packagedir:
140  cml = os.path.normpath(os.path.join(packagedir, 'src', 'Util', 'CMakeLists.txt'))
141  change_cml_version(cml, newversion)
142  return changed
143 
144 def tag_bar(pkg, version=None):
145  title = ' %s %s ' % (pkg, version)
146  letf_chars = (78 - len(title)) / 2
147  right_chars = 78 - letf_chars - len(title)
148  separator = ('=' * letf_chars) + title + ('=' * right_chars)
149  return separator
150 
151 def newmain():
152  logging.basicConfig(level=logging.DEBUG)
153  latest_tag = findLatestTag()
154  for path in [d for d in os.listdir(os.curdir)
155  if os.path.exists(os.path.join(d, 'doc', 'release.notes'))]:
156  updateReleaseNotes(path,
157  (tag_bar(path, new_versions[path]) + '\n\n'
158  if path in new_versions
159  else '') +
160  releaseNotes(path, latest_tag, 'master'))
161 
162 
163 def main():
164  logging.basicConfig(level=logging.DEBUG)
165 
166  os.chdir(os.path.dirname(os.path.dirname(__file__)))
167 
168  # Find the version of HEPTools (LCG)
169  for l in open('toolchain.cmake'):
170  m = re.match(r'^\s*set\(\s*heptools_version\s+(\S*)\s*\)', l)
171  if m:
172  HEPToolsVers = m.group(1)
173  print "Using HEPTools", HEPToolsVers
174  break
175  else:
176  logging.error('Cannot find HEPTools version')
177  sys.exit(1)
178 
179  # Collect all the packages in the project with their directory
180  def all_subdirs():
181  for dirpath, dirnames, filenames in os.walk(os.curdir):
182  if 'CMakeLists.txt' in filenames and dirpath != os.curdir:
183  dirnames[:] = []
184  yield dirpath
185  else:
186  dirnames[:] = [dirname for dirname in dirnames
187  if not dirname.startswith('build.') and
188  dirname != 'cmake']
189 
190  # Packages which version must match the version of the project
191  special_subdirs = ["Gaudi", "GaudiExamples", "GaudiSys", "GaudiRelease"]
192 
193  # Ask for the version of the project
194  latest_tag = findLatestTag()
195 
196  old_version = latest_tag.split('_')[-1]
197  new_version = raw_input("The old version of the project is %s, which is the new one? " % old_version)
198 
199  old_versions = {}
200  release_notes = {}
201  new_versions = {}
202  # for each package in the project check if there were changes and ask for the new version number
203  for pkgdir in all_subdirs():
204  cmlfile = os.path.join(pkgdir, 'CMakeLists.txt')
205  reqfile = os.path.join(pkgdir, 'cmt', 'requirements')
206 
207  pkg = os.path.basename(pkgdir)
208  old_vers = extract_version(pkgdir)
209 
210  vers = None
211  if pkg in special_subdirs:
212  vers = new_version
213  else:
214  git_log = Popen(['git', 'log', '-m', '--first-parent',
215  '--stat', latest_tag + '..master', pkgdir],
216  stdout=PIPE).communicate()[0].strip()
217  if git_log:
218  msg = ('\nThe old version of {0} is {1}, these are the changes:\n'
219  '{2}\n'
220  'Which version you want (old is {1})? ').format(pkg,
221  old_vers,
222  git_log)
223  vers = raw_input(msg)
224  while not vers or vers == old_vers:
225  vers = raw_input('give me a version')
226  if vers:
227  change_version(pkgdir, vers)
228  updateReleaseNotes(pkgdir,
229  tag_bar(pkg, vers) + '\n\n' +
230  releaseNotes(pkgdir, latest_tag, 'master'))
231  new_versions[pkg] = vers or old_vers
232  print "=" * 80
233 
234  # The changes in the GaudiRelease requirements for the other packages can be postponed to now
235  reqfile = os.path.join('GaudiRelease', 'cmt', 'requirements')
236  out = []
237  for l in open(reqfile):
238  sl = l.strip().split()
239  if sl and sl[0] == "use":
240  if sl[1] in new_versions:
241  if sl[2] != new_versions[sl[1]]:
242  l = l.replace(sl[2], new_versions[sl[1]])
243  out.append(l)
244  open(reqfile, "w").writelines(out)
245  # Update project.info
246  config = ConfigParser.ConfigParser()
247  config.optionxform = str # make the options case sensitive
248  if os.path.exists('project.info'):
249  config.read('project.info')
250  if not config.has_section('Packages'):
251  config.add_section('Packages')
252  for pack_vers in sorted(new_versions.items()):
253  config.set('Packages', *pack_vers)
254  config.write(open('project.info', 'wb'))
255 
256  # update the global CMakeLists.txt
257  out = []
258  for l in open('CMakeLists.txt'):
259  if l.strip().startswith('gaudi_project'):
260  l = 'gaudi_project(Gaudi %s)\n' % new_version
261  out.append(l)
262  open('CMakeLists.txt', "w").writelines(out)
263 
264 
265 if __name__ == '__main__':
266  main()
267 
def updateReleaseNotes(path, notes)
def change_cml_version(cml, newversion)
def change_version(packagedir, newversion)
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:120