buildrecord.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. #
  2. # Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
  3. # All rights reserved.
  4. # This component and the accompanying materials are made available
  5. # under the terms of the License "Eclipse Public License v1.0"
  6. # which accompanies this distribution, and is available
  7. # at the URL "http://www.eclipse.org/legal/epl-v10.html".
  8. #
  9. # Initial Contributors:
  10. # Nokia Corporation - initial contribution.
  11. #
  12. # Contributors:
  13. #
  14. # Description:
  15. """ Module for creating/manipulating an "audit trail" of builds """
  16. import raptor_makefile
  17. import os
  18. import stat
  19. import json
  20. class BuildRecord(object):
  21. """Information about a build which can be used, amongst other purposes,
  22. to assist incremental makefile generation.
  23. A new file is created in every build with the name #MAKEFILE#.buildrecord.
  24. This file records of the names of the makefiles involved in a build and
  25. the corresponding commandline that was given to raptor when it created
  26. them.
  27. Raptor uses these records to determine if the current build can reuse
  28. existing makefiles. If the current build attempt's commandline and
  29. 'environment' match those of an existing record then the makefiles
  30. referenced by the record can be reused.
  31. Each layer in a build can produce a 'makefileset' with makefiles for
  32. all the stages of a build (export,bitmap,resource_deps,resource,default).
  33. Hence buildrecords contain a list with one 'set' for each layer and
  34. each set is itself a list of makefiles, one per stage.
  35. Build records also store the filenames of 'metadeps' files (short for
  36. "Metadata Dependencies") which list the metadata that each makefileset
  37. depends on (e.g. the bld.infs and mmps that correspond to that makefileset).
  38. """
  39. stored_attrs = ['commandline', 'environment', 'topmakefilename']
  40. sensed_environment_variables = ["EPOCROOT","PATH"]
  41. history_size = 10
  42. parsefails = []
  43. def __init__(self, commandline=None, environment=None, topmakefilename=None, makefilesets=None):
  44. """ Create a new record of a build that is about to happen (in which case the default parameters
  45. may be used) or a build that is complete. Parameters must all be strings.
  46. """
  47. self.commandline = commandline
  48. self.environment = environment
  49. self.topmakefilename = topmakefilename
  50. self.uptodate = False # Do we need to regenerate the makefiles to reuse this build?
  51. self.makefilesets=makefilesets # an array of raptor_makefile.BaseMakefileset Object
  52. self.filename = self.topmakefilename + ".buildrecord"
  53. self.reused = False
  54. self.new_metadata = [] # record what was out of date
  55. def to_file(self):
  56. """ Write out the build record so that we can find it in future builds"""
  57. with open(self.filename,"w") as f:
  58. json_br = {}
  59. for a in BuildRecord.stored_attrs:
  60. json_br[a] = self.__dict__[a]
  61. json_makefilesets = []
  62. for ms in self.makefilesets:
  63. json_makefilesets.append(ms.json())
  64. json_br['makefilesets'] = json_makefilesets
  65. json_structure = {'buildrecord': json_br }
  66. json.dump(json_structure,f, indent = 4)
  67. def record_makefileset(self, makefileset):
  68. """ Add a makefileset to the list of sets "executed" in the build.
  69. This can be called repeatedly - e.g. once to record
  70. the makefiles for each layer in an ordered layers build.
  71. """
  72. self.makefilesets.append(makefileset)
  73. @classmethod
  74. def from_file(cls, filename):
  75. """ Create a build record from a .buildrecord file"""
  76. with open(filename,"r") as f:
  77. try:
  78. json_structure = json.load(f)
  79. except Exception,e:
  80. raise Exception("Bad build record format - json deserialisation failed.")
  81. try:
  82. kargs = json_structure['buildrecord']
  83. except Exception,e:
  84. raise Exception("Bad build record format - buildrecord property not found in buildrecord file.")
  85. # the json structure matches what must be passed into the constructor
  86. # quite well except for the makefilesets
  87. try:
  88. makefilesets = [ raptor_makefile.BaseMakefileSet.from_json(json_makefileset)
  89. for json_makefileset in kargs['makefilesets']]
  90. except raptor_makefile.JsonMakefileDecodeError,e:
  91. raise Exception("Bad build record format: makefilesets element did not decode: {0}".format(str(e)))
  92. # replace the json structure for makefilesets with real BaseMakefileSet() instances
  93. kargs['makefilesets'] = makefilesets
  94. try:
  95. br = BuildRecord(**kargs)
  96. except TypeError,e:
  97. raise Exception("Bad build record format: settings must be present for {0} but they were {1}: {2}: {3}".format(BuildRecord.stored_attrs, str(kargs), filename, str(e)))
  98. br.filename = filename
  99. return br
  100. def _commandline_key(self):
  101. """Strip a commandline of things that don't affect the
  102. makefiles e.g. the name of the logfile. This is used
  103. to decide if we need new makefiles or if the old ones
  104. are ok. Obviously this kind of method is "approximate" and
  105. will make some commandlines that are really equivalent seem
  106. different e.g. options in a different order. The main thing
  107. though is that it is a kind of "overcautious" mechanism
  108. which is ok for an initial implementation of incremental
  109. makefile generation."""
  110. sc = self.commandline.split(" ")
  111. # cut out non-relevant stuff
  112. skipcount = 0
  113. nsc = []
  114. for s in sc:
  115. if skipcount > 0:
  116. skipcount -= 1
  117. continue
  118. elif s == "--ip=on" or s == "clean":
  119. continue
  120. elif s == "-f": # skip logfilenames
  121. skipcount = 1
  122. continue
  123. nsc.append(s)
  124. return " ".join(nsc)
  125. def __eq__(self, other):
  126. """ Were the two builds done in a compatible
  127. environment, similar targets and for the same platforms?
  128. i.e. should the makefiles be interchangeable?
  129. """
  130. sc = self._commandline_key()
  131. oc = other._commandline_key()
  132. if sc == oc:
  133. if self.environment == other.environment:
  134. return True
  135. return False
  136. @classmethod
  137. def matching_records(cls, adir, matching):
  138. """Find records of previous builds that are equivalent to this one,
  139. sort them according to time order and yield first X of them to
  140. the caller """
  141. brfiles = []
  142. for b in os.listdir(adir):
  143. if b.endswith(".buildrecord"):
  144. brf = os.path.join(adir,b)
  145. try:
  146. brfiles.append((brf, os.stat(brf)[stat.ST_MTIME]))
  147. except OSError, e:
  148. pass
  149. # sort so newest are first
  150. brfiles_s = sorted(brfiles,key=lambda f:f[1], reverse=True)
  151. # yield up build records if they are "equal". Don't
  152. # look infinitely far back as it might take a long time
  153. rcount = 0
  154. for brt in brfiles_s:
  155. b = brt[0]
  156. try:
  157. br = cls.from_file(os.path.join(adir,b))
  158. if br == matching:
  159. yield br
  160. rcount += 1
  161. if rcount > BuildRecord.history_size:
  162. break
  163. except Exception,e:
  164. print(e)
  165. BuildRecord.parsefails.append(e) # parse errors should not be fatal - just means that the build record is from an old version of raptor. There is no way to report the fact that they happened though and that's not so nice. This exception list just makes it feasible to debug a problem if one occurs.
  166. @classmethod
  167. def from_old(cls, adir, commandline, environment, topmakefile):
  168. """Create a build record for this build. Try to make it from an older one
  169. and use its existing makefiles if they are up-to-date."""
  170. newbr = cls(commandline, environment, topmakefile)
  171. # See if there is an old build record to reuse
  172. for oldbr in cls.matching_records(adir, newbr):
  173. if oldbr.check_uptodate(newbr.new_metadata):
  174. newbr.topmakefilename = oldbr.topmakefilename
  175. newbr.makefilesets = oldbr.makefilesets
  176. newbr.uptodate = True
  177. newbr.reused = True
  178. return newbr
  179. def check_uptodate(self,triggers=[]):
  180. """
  181. Return False if any of the metadata is out of date.
  182. triggers -- a list which will be extended with
  183. some number of metadata filenames that are out of date.
  184. The list will be unaltered if all metadata is up-to-date.
  185. Any additions to the list cannot be considered to
  186. be a comprehensive list of what's out-of-date. The list
  187. could conceivably become very long so care should be
  188. taken to ensure that a user is not overwhelmed with
  189. any reporting based on this list.
  190. """
  191. try:
  192. # Loop gives a chance for exception to be thrown
  193. for mset in self.makefilesets:
  194. mset.check_uptodate()
  195. # No exception so all must be up-to-date
  196. return True
  197. except raptor_makefile.OutOfDateException,e:
  198. triggers.extend(e.items)
  199. return False