process_jam_log.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. #!/usr/bin/python
  2. # Copyright 2008 Rene Rivera
  3. # Distributed under the Boost Software License, Version 1.0.
  4. # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
  5. import re
  6. import optparse
  7. import time
  8. import xml.dom.minidom
  9. from xml.sax.saxutils import unescape, escape
  10. import os.path
  11. #~ Process a bjam XML log into the XML log format for Boost result processing.
  12. class BJamLog2Results:
  13. def __init__(self):
  14. opt = optparse.OptionParser(
  15. usage="%prog [options] input")
  16. opt.add_option( '--output',
  17. help="output file" )
  18. opt.add_option( '--runner',
  19. help="runner ID (e.g. 'Metacomm')" )
  20. opt.add_option( '--comment',
  21. help="an HTML comment file to be inserted in the reports" )
  22. opt.add_option( '--tag',
  23. help="the tag for the results" )
  24. opt.add_option( '--incremental',
  25. help="do incremental run (do not remove previous binaries)",
  26. action='store_true' )
  27. opt.add_option( '--platform' )
  28. opt.add_option( '--source' )
  29. opt.add_option( '--revision' )
  30. self.output = None
  31. self.runner = None
  32. self.comment='comment.html'
  33. self.tag='trunk'
  34. self.incremental=False
  35. self.platform=''
  36. self.source='SVN'
  37. self.revision=None
  38. self.input = []
  39. ( _opt_, self.input ) = opt.parse_args(None,self)
  40. self.results = xml.dom.minidom.parseString('''<?xml version="1.0" encoding="UTF-8"?>
  41. <test-run
  42. source="%(source)s"
  43. runner="%(runner)s"
  44. timestamp=""
  45. platform="%(platform)s"
  46. tag="%(tag)s"
  47. run-type="%(run-type)s"
  48. revision="%(revision)s">
  49. </test-run>
  50. ''' % {
  51. 'source' : self.source,
  52. 'runner' : self.runner,
  53. 'platform' : self.platform,
  54. 'tag' : self.tag,
  55. 'run-type' : 'incremental' if self.incremental else 'full',
  56. 'revision' : self.revision,
  57. } )
  58. self.test = {}
  59. self.target = {}
  60. self.child = {}
  61. self.parent = {}
  62. self.log = {}
  63. self.add_log()
  64. self.gen_output()
  65. #~ print self.test
  66. #~ print self.target
  67. def add_log(self):
  68. bjam_log = xml.dom.minidom.parse(self.input[0])
  69. self.x(bjam_log.documentElement)
  70. def gen_output(self):
  71. if self.output:
  72. out = open(self.output,'w')
  73. else:
  74. out = sys.stdout
  75. if out:
  76. self.results.writexml(out,encoding='utf-8')
  77. def tostring(self):
  78. return self.results.toxml('utf-8')
  79. def x(self, *context, **kwargs):
  80. node = None
  81. names = [ ]
  82. for c in context:
  83. if c:
  84. if not isinstance(c,xml.dom.Node):
  85. suffix = '_'+c.replace('-','_').replace('#','_')
  86. else:
  87. suffix = '_'+c.nodeName.replace('-','_').replace('#','_')
  88. node = c
  89. names.append('x')
  90. names = map(lambda x: x+suffix,names)
  91. if node:
  92. for name in names:
  93. if hasattr(self,name):
  94. return getattr(self,name)(node,**kwargs)
  95. else:
  96. assert False, 'Unknown node type %s'%(name)
  97. return None
  98. #~ The single top-level build element...
  99. def x_build( self, node ):
  100. test_run = self.results.documentElement
  101. #~ Iterate over the sub-sections in a specific order to build up the
  102. #~ cross-reference information and the XML output.
  103. for type in ('timestamp','comment','test','targets','action'):
  104. items = self.x(node,type)
  105. #~ Any items generated by the processing are inteserted into the results.
  106. if items:
  107. for item in items:
  108. if item:
  109. test_run.appendChild(self.results.createTextNode("\n"))
  110. test_run.appendChild(item)
  111. return None
  112. #~ The timestamp goes to the corresponding attribute in the result.
  113. def x_build_timestamp( self, node ):
  114. test_run = self.results.documentElement
  115. timestamp = self.get_child(self.get_child(node,tag='timestamp'),tag='#cdata-section').data.strip()
  116. test_run.setAttribute('timestamp',timestamp)
  117. return None
  118. #~ Comment file becomes a comment node.
  119. def x_build_comment( self, node ):
  120. comment = None
  121. if self.comment:
  122. comment_f = open(self.comment)
  123. if comment_f:
  124. comment = comment_f.read()
  125. comment_f.close()
  126. if not comment:
  127. comment = ''
  128. return [self.new_text('comment',comment)]
  129. #~ Tests are remembered for future reference.
  130. def x_build_test( self, node ):
  131. test_run = self.results.documentElement
  132. test_node = self.get_child(node,tag='test')
  133. while test_node:
  134. test_name = test_node.getAttribute('name')
  135. self.test[test_name] = {
  136. 'library' : test_name.split('/',1)[0],
  137. 'test-name' : test_name.split('/',1)[1],
  138. 'test-type' : test_node.getAttribute('type').lower(),
  139. 'test-program' : self.get_child_data(test_node,tag='source').strip(),
  140. 'target' : self.get_child_data(test_node,tag='target').strip(),
  141. 'info' : self.get_child_data(test_node,tag='info',strip=True)
  142. }
  143. #~ Add a lookup for the test given the test target.
  144. self.target[self.test[test_name]['target']] = test_name
  145. test_node = self.get_sibling(test_node.nextSibling,tag='test')
  146. return None
  147. #~ Process the target dependency DAG into an ancestry tree so we can look up
  148. #~ which top-level library and test targets specific build actions correspond to.
  149. def x_build_targets( self, node ):
  150. test_run = self.results.documentElement
  151. target_node = self.get_child(self.get_child(node,tag='targets'),tag='target')
  152. while target_node:
  153. name = self.get_child_data(target_node,tag='name').strip()
  154. #~ Map for jam targets to virtual targets.
  155. self.target[self.get_child_data(target_node,tag='jam-target').strip()] = {
  156. 'name' : name,
  157. 'path' : self.get_child_data(target_node,tag='path').strip()
  158. }
  159. #~ Create the bidirectional ancestry.
  160. self.child[name] = []
  161. dep_node = self.get_child(self.get_child(target_node,tag='dependencies'),tag='dependency')
  162. while dep_node:
  163. child = self.get_data(dep_node).strip()
  164. self.child[name].append(child)
  165. self.parent[child] = name
  166. dep_node = self.get_sibling(dep_node.nextSibling,tag='dependency')
  167. target_node = self.get_sibling(target_node.nextSibling,tag='target')
  168. return None
  169. #~ Given a build action log, process into the corresponding test log and
  170. #~ specific test log sub-part.
  171. def x_build_action( self, node ):
  172. test_run = self.results.documentElement
  173. action_node = self.get_child(node,tag='action')
  174. while action_node:
  175. name = self.get_child(action_node,tag='name')
  176. if name:
  177. name = self.get_data(name)
  178. #~ Based on the action, we decide what sub-section the log
  179. #~ should go into.
  180. action_type = None
  181. if re.match('[^%]+%[^.]+[.](compile)',name):
  182. action_type = 'compile'
  183. elif re.match('[^%]+%[^.]+[.](link|archive)',name):
  184. action_type = 'link'
  185. elif re.match('[^%]+%testing[.](capture-output)',name):
  186. action_type = 'run'
  187. if action_type:
  188. #~ Get the corresponding test.
  189. (target,test) = self.get_test(action_node,type=action_type)
  190. #~ And the log node, which we will add the results to.
  191. log = self.get_log(action_node,test)
  192. #~ print "--- [%s] %s %s :: %s" %(action_type,name,target,log)
  193. #~ Collect some basic info about the action.
  194. result_data = "%(info)s\n\n%(command)s\n%(output)s\n" % {
  195. 'command' : self.get_action_command(action_node,action_type),
  196. 'output' : self.get_action_output(action_node,action_type),
  197. 'info' : self.get_action_info(action_node,action_type)
  198. }
  199. #~ The result sub-part we will add this result to.
  200. result_node = self.get_child(log,tag=action_type)
  201. if not result_node:
  202. #~ If we don't have one already, create it and add the result.
  203. result_node = self.new_text(action_type,result_data,
  204. result='succeed' if action_node.getAttribute('status') == '0' else 'fail',
  205. timestamp=action_node.getAttribute('start'))
  206. log.appendChild(self.results.createTextNode("\n"))
  207. log.appendChild(result_node)
  208. else:
  209. #~ For an existing result node we set the status to fail
  210. #~ when any of the individual actions fail.
  211. result = result_node.getAttribute('result')
  212. if action_node.getAttribute('status') != '0':
  213. result = 'fail'
  214. result_node.setAttribute('result',result)
  215. result_node.appendChild(self.results.createTextNode("\n"))
  216. result_node.appendChild(self.results.createTextNode(result_data))
  217. action_node = self.get_sibling(action_node.nextSibling,tag='action')
  218. return self.log.values()
  219. #~ The command executed for the action. For run actions we omit the command
  220. #~ as it's just noise.
  221. def get_action_command( self, action_node, action_type ):
  222. if action_type != 'run':
  223. return self.get_child_data(action_node,tag='command')
  224. else:
  225. return ''
  226. #~ The command output.
  227. def get_action_output( self, action_node, action_type ):
  228. return self.get_child_data(action_node,tag='output',default='')
  229. #~ Some basic info about the action.
  230. def get_action_info( self, action_node, action_type ):
  231. info = ""
  232. #~ The jam action and target.
  233. info += "%s %s\n" %(self.get_child_data(action_node,tag='name'),
  234. self.get_child_data(action_node,tag='path'))
  235. #~ The timing of the action.
  236. info += "Time: (start) %s -- (end) %s -- (user) %s -- (system) %s\n" %(
  237. action_node.getAttribute('start'), action_node.getAttribute('end'),
  238. action_node.getAttribute('user'), action_node.getAttribute('system'))
  239. #~ And for compiles some context that may be hidden if using response files.
  240. if action_type == 'compile':
  241. define = self.get_child(self.get_child(action_node,tag='properties'),name='define')
  242. while define:
  243. info += "Define: %s\n" %(self.get_data(define,strip=True))
  244. define = self.get_sibling(define.nextSibling,name='define')
  245. return info
  246. #~ Find the test corresponding to an action. For testing targets these
  247. #~ are the ones pre-declared in the --dump-test option. For libraries
  248. #~ we create a dummy test as needed.
  249. def get_test( self, node, type = None ):
  250. base = self.target[self.get_child_data(node,tag='jam-target')]['name']
  251. #~ main-target-type is a precise indicator of what the build target is
  252. #~ proginally meant to be.
  253. main_type = self.get_child_data(self.get_child(node,tag='properties'),
  254. name='main-target-type',strip=True)
  255. target = base
  256. if main_type == 'LIB' and type:
  257. if type == 'compile':
  258. target = self.parent[target]
  259. if not target in self.test:
  260. self.test[target] = {
  261. 'library' : re.search(r'libs/([^/]+)',target).group(1),
  262. 'test-name' : os.path.basename(target),
  263. 'test-type' : 'lib',
  264. 'test-program' : os.path.basename(target),
  265. 'target' : target
  266. }
  267. test = self.test[target]
  268. else:
  269. while target in self.parent:
  270. target = self.parent[target]
  271. test = self.test[self.target[target]]
  272. return (base,test)
  273. #~ Find, or create, the test-log node to add results to.
  274. def get_log( self, node, test ):
  275. target_directory = os.path.dirname(self.get_child_data(
  276. node,tag='path',strip=True))
  277. target_directory = re.sub(r'[.][.][/\\]','',target_directory)
  278. target_directory = re.sub(r'[\\]','/',target_directory)
  279. if not target_directory in self.log:
  280. self.log[target_directory] = self.new_node('test-log',
  281. library=test['library'],
  282. test_name=test['test-name'],
  283. test_type=test['test-type'],
  284. test_program=test['test-program'],
  285. toolset=self.get_toolset(node),
  286. target_directory=target_directory,
  287. show_run_output='true' if 'info' in test and test['info'] == 'always_show_run_output' else 'false')
  288. return self.log[target_directory]
  289. #~ The precise toolset from the build properties.
  290. def get_toolset( self, node ):
  291. toolset = self.get_child_data(self.get_child(node,tag='properties'),
  292. name='toolset',strip=True)
  293. toolset_version = self.get_child_data(self.get_child(node,tag='properties'),
  294. name='toolset-%s:version'%toolset,strip=True)
  295. return '%s-%s' %(toolset,toolset_version)
  296. #~ XML utilities...
  297. def get_sibling( self, sibling, tag = None, id = None, name = None ):
  298. n = sibling
  299. while n:
  300. found = True
  301. if tag and found:
  302. found = found and tag == n.nodeName
  303. if (id or name) and found:
  304. found = found and n.nodeType == xml.dom.Node.ELEMENT_NODE
  305. if id and found:
  306. if n.hasAttribute('id'):
  307. found = found and n.getAttribute('id') == id
  308. else:
  309. found = found and n.hasAttribute('id') and n.getAttribute('id') == id
  310. if name and found:
  311. found = found and n.hasAttribute('name') and n.getAttribute('name') == name
  312. if found:
  313. return n
  314. n = n.nextSibling
  315. return None
  316. def get_child( self, root, tag = None, id = None, name = None ):
  317. return self.get_sibling(root.firstChild,tag=tag,id=id,name=name)
  318. def get_data( self, node, strip = False, default = None ):
  319. data = None
  320. if node:
  321. if not data:
  322. data = self.get_child(node,tag='#text')
  323. if not data:
  324. data = self.get_child(node,tag='#cdata-section')
  325. if data:
  326. data = data.data if not strip else data.data.strip()
  327. if not data:
  328. data = default
  329. return data
  330. def get_child_data( self, root, tag = None, id = None, name = None, strip = False, default = None ):
  331. return self.get_data(self.get_child(root,tag=tag,id=id,name=name),strip=strip,default=default)
  332. def new_node( self, tag, *child, **kwargs ):
  333. result = self.results.createElement(tag)
  334. for k in kwargs.keys():
  335. if kwargs[k] != '':
  336. if k == 'id':
  337. result.setAttribute('id',kwargs[k])
  338. elif k == 'klass':
  339. result.setAttribute('class',kwargs[k])
  340. else:
  341. result.setAttribute(k.replace('_','-'),kwargs[k])
  342. for c in child:
  343. if c:
  344. result.appendChild(c)
  345. return result
  346. def new_text( self, tag, data, **kwargs ):
  347. result = self.new_node(tag,**kwargs)
  348. data = data.strip()
  349. if len(data) > 0:
  350. result.appendChild(self.results.createTextNode(data))
  351. return result
  352. BJamLog2Results()
粤ICP备19079148号