project_manifest.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 import sys
5 import os
6 from xml.etree import ElementTree as ET
7 from subprocess import Popen, PIPE
8 import re
9 import logging
10 
11 def indent(elem, level=0):
12  '''
13  Add spaces and newlines to elements to allow pretty-printing of XML.
14 
15  http://effbot.org/zone/element-lib.htm#prettyprint
16  '''
17  i = "\n" + level*" "
18  if len(elem):
19  if not elem.text or not elem.text.strip():
20  elem.text = i + " "
21  if not elem.tail or not elem.tail.strip():
22  elem.tail = i
23  for elem in elem:
24  indent(elem, level+1)
25  if not elem.tail or not elem.tail.strip():
26  elem.tail = i
27  else:
28  if level and (not elem.tail or not elem.tail.strip()):
29  elem.tail = i
30 
31 def CMakeParseArguments(args, options=None, single_value=None, multi_value=None):
32  '''
33  Parse a list or strings using the logic of the CMake function
34  CMAKE_PARSE_ARGUMENTS.
35 
36  >>> res = CMakeParseArguments(['USE', 'Gaudi', 'v25r0', 'DATA', 'Det/SQLDDDB'],
37  ... multi_value=['USE', 'DATA'])
38  >>> res['USE']
39  ['Gaudi', 'v25r0']
40  >>> res['DATA']
41  ['Det/SQLDDDB']
42  >>> res['UNPARSED']
43  []
44  >>> res = CMakeParseArguments('a b OPTION1 c FLAG1 d OPTION2 e f'.split(),
45  ... options=['FLAG1', 'FLAG2'],
46  ... single_value=['OPTION1', 'OPTION2'])
47  >>> res['FLAG1']
48  True
49  >>> res['FLAG2']
50  False
51  >>> res['OPTION1']
52  'c'
53  >>> res['OPTION2']
54  'e'
55  >>> res['UNPARSED']
56  ['a', 'b', 'd', 'f']
57  '''
58  args = list(args)
59  options = set(options or [])
60  single_value = set(single_value or [])
61  multi_value = set(multi_value or [])
62  all_keywords = options.union(single_value).union(multi_value)
63  result = {'UNPARSED': []}
64  result.update((k, False) for k in options)
65  result.update((k, None) for k in single_value)
66  result.update((k, []) for k in multi_value)
67 
68  while args:
69  arg = args.pop(0)
70  if arg in options:
71  result[arg] = True
72  elif arg in single_value:
73  result[arg] = args.pop(0)
74  elif arg in multi_value:
75  while args and args[0] not in all_keywords:
76  result[arg].append(args.pop(0))
77  else:
78  result['UNPARSED'].append(arg)
79  return result
80 
81 if __name__ == '__main__':
82  from optparse import OptionParser
83  parser = OptionParser(usage='%prog [options] <cmake_lists> <lcg_version> <platform>')
84  parser.add_option('-o', '--output', action='store',
85  help='output filename')
86 
87  opts, args = parser.parse_args()
88 
89  if len(args) != 3:
90  parser.error('wrong number of arguments')
91 
92  cmake_lists, lcg_version, platform = args
93 
94  logging.basicConfig(level=logging.INFO)
95 
96  # look for the CMake configuration file
97  if not os.path.exists(cmake_lists):
98  print 'The project does not have a CMake configuration, I cannot produce a manifest.xml'
99  sys.exit(0)
100 
101  project_args = []
102  m = re.search(r'gaudi_project\s*\(([^)]*)\)',
103  open(cmake_lists).read(), re.MULTILINE)
104  if m:
105  project_args = m.group(1).split()
106  if len(project_args) < 2:
107  print 'error: invalid content of CMakeLists.txt'
108  sys.exit(1)
109 
110  # parse gaudi_project arguments
111  name, version = project_args[:2]
112  project_args = project_args[2:]
113 
114  parsed_args = CMakeParseArguments(project_args,
115  options=['FORTRAN'],
116  multi_value=['USE', 'DATA'])
117 
118  # set the output
119  if opts.output in ('-', None):
120  output = sys.stdout
121  else:
122  output = opts.output
123 
124  # prepare the XML content
125  manifest = ET.Element('manifest')
126  manifest.append(ET.Element('project', name=name, version=version))
127 
128  heptools = ET.Element('heptools')
129  ht_version = ET.Element('version')
130  ht_version.text = lcg_version
131  ht_bin_tag = ET.Element('binary_tag')
132  ht_bin_tag.text = platform
133  ht_system = ET.Element('lcg_system')
134  ht_system.text = '-'.join(platform.split('-')[:-1])
135  heptools.extend([ht_version, ht_bin_tag, ht_system])
136  manifest.append(heptools)
137 
138  if parsed_args['USE']:
139  used_projects = ET.Element('used_projects')
140  used_projects.extend(ET.Element('project', name=name, version=version)
141  for name, version in zip(parsed_args['USE'][::2],
142  parsed_args['USE'][1::2]))
143  manifest.append(used_projects)
144 
145  if parsed_args['DATA']:
146  used_data_pkgs = ET.Element('used_data_pkgs')
147  def data_pkgs(args):
148  '''helper to translate the list of data packages'''
149  args = list(args) # clone
150  while args:
151  pkg = args.pop(0)
152  if len(args) >= 2 and args[0] == 'VERSION':
153  args.pop(0)
154  vers = args.pop(0)
155  else:
156  vers = '*'
157  yield (pkg, vers)
158  used_data_pkgs.extend(ET.Element('package', name=pkg, version=vers)
159  for pkg, vers in data_pkgs(parsed_args['DATA']))
160  manifest.append(used_data_pkgs)
161 
162 
163  logging.debug('collecting external dependencies info (with CMT)')
164  p = Popen(['cmt', 'show', 'uses'], stdout=PIPE)
165  externals = [l.split()[1]
166  for l in p.stdout
167  if l.startswith('use') and
168  'LCG_Interfaces' in l]
169  externals.sort()
170 
171  # get the versions of the externals
172  def get_ext_vers(ext):
173  '''
174  Ask CMT the version of an external.
175  '''
176  logging.debug('getting version of %s', ext)
177  vers = Popen(['cmt', 'show', 'macro_value',
178  '%s_native_version' % ext],
179  stdout=PIPE).communicate()[0].strip()
180  logging.debug('using %s %s', ext, vers)
181  if vers == 'dummy': # special case in LCG
182  vers = ''
183  return vers
184 
185  # mapping between LCG_Interface name and RPM name for special cases
186  rpm_names = {'Expat': 'expat'}
187  fix_rpm_name = lambda n: rpm_names.get(n, n)
188 
189  packages = ET.Element('packages')
190  packages.extend([ET.Element('package', name=fix_rpm_name(ext), version=vers)
191  for ext, vers in [(ext, get_ext_vers(ext))
192  for ext in externals]
193  if vers])
194  heptools.append(packages)
195 
196  destdir = os.path.dirname(output)
197  if not os.path.exists(destdir):
198  logging.debug('creating directory %s', destdir)
199  os.makedirs(destdir)
200 
201  # finally write the produced XML
202  logging.debug('writing manifest file %s', output)
203  indent(manifest)
204  ET.ElementTree(manifest).write(output,
205  encoding="UTF-8", xml_declaration=True)
206 
207  logging.debug('%s written', output)