harvest.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # Generates Windows resource header.
  2. from optparse import OptionParser
  3. from os import walk
  4. from os.path import (
  5. basename, dirname, isfile, join as joinpath, relpath, split as splitpath
  6. )
  7. from uuid import uuid4
  8. indentSize = 2
  9. excludedDirectories = ['.svn', 'CVS']
  10. excludedFiles = ['node.mk']
  11. # Bit of a hack, but it works
  12. def isParentDir(child, parent):
  13. return '..' not in relpath(child, parent)
  14. def makeFileId(guid):
  15. return 'file_' + guid
  16. def makeDirectoryId(guid):
  17. return 'directory_' + guid
  18. def makeComponentId(guid):
  19. return 'component_' + guid
  20. def newGuid():
  21. return str(uuid4()).replace('-', '')
  22. def walkPath(sourcePath):
  23. if isfile(sourcePath):
  24. yield dirname(sourcePath), [], [ basename(sourcePath) ]
  25. else:
  26. for dirpath, dirnames, filenames in walk(sourcePath):
  27. yield dirpath, dirnames, filenames
  28. class WixFragment(object):
  29. def __init__(self, fileGenerator, componentGroup, directoryRef, virtualDir,
  30. excludedFile, win64):
  31. self.fileGenerator = fileGenerator
  32. self.componentGroup = componentGroup
  33. self.directoryRef = directoryRef
  34. self.virtualDir = virtualDir
  35. self.indentLevel = 0
  36. self.win64 = 'yes' if win64 else 'no'
  37. if excludedFile:
  38. # TODO: Modifying this global variable is an unexpected side effect.
  39. excludedFiles.append(excludedFile)
  40. def incrementIndent(self):
  41. self.indentLevel += indentSize
  42. def decrementIndent(self):
  43. self.indentLevel -= indentSize
  44. def indent(self, line):
  45. return ' ' * self.indentLevel + line
  46. def startElement(self, elementName, **args):
  47. line = self.indent(
  48. '<%s %s>' % (
  49. elementName,
  50. ' '.join('%s="%s"' % item for item in args.items())
  51. )
  52. )
  53. self.incrementIndent()
  54. return line
  55. def endElement(self, elementName):
  56. self.decrementIndent()
  57. return self.indent('</%s>' % elementName)
  58. def yieldFragment(self):
  59. # List that stores the components we've added
  60. components = []
  61. # Stack that stores directories we've already visited
  62. stack = []
  63. # List that stores the virtual directories we added
  64. virtualstack = []
  65. yield self.indent(
  66. '<?xml version="1.0" encoding="utf-8"?>'
  67. )
  68. yield self.startElement(
  69. 'Wix', xmlns = 'http://schemas.microsoft.com/wix/2006/wi'
  70. )
  71. yield self.startElement('Fragment')
  72. yield self.startElement('DirectoryRef', Id = self.directoryRef)
  73. # Add virtual directories
  74. if self.virtualDir:
  75. head = self.virtualDir
  76. while head:
  77. joinedPath = head
  78. head, tail_ = splitpath(head)
  79. virtualstack.insert(0, joinedPath)
  80. for path in virtualstack:
  81. componentId = 'directory_' + str(uuid4()).replace('-', '_')
  82. yield self.startElement(
  83. 'Directory', Id = componentId, Name = basename(path)
  84. )
  85. # Walk the provided file list
  86. firstDir = True
  87. for dirpath, dirnames, filenames in self.fileGenerator:
  88. # Remove excluded sub-directories
  89. for exclusion in excludedDirectories:
  90. if exclusion in dirnames:
  91. dirnames.remove(exclusion)
  92. if firstDir:
  93. firstDir = False
  94. else:
  95. # Handle directory hierarchy appropriately
  96. while stack:
  97. popped = stack.pop()
  98. if isParentDir(dirpath, popped):
  99. stack.append(popped)
  100. break
  101. else:
  102. yield self.endElement('Directory')
  103. # Enter new directory
  104. stack.append(dirpath)
  105. yield self.startElement(
  106. 'Directory', Id = makeDirectoryId(newGuid()),
  107. Name = basename(dirpath)
  108. )
  109. # Remove excluded files
  110. for exclusion in excludedFiles:
  111. if exclusion in filenames:
  112. filenames.remove(exclusion)
  113. # Process files
  114. for filename in filenames:
  115. guid = newGuid()
  116. componentId = makeComponentId(guid)
  117. sourcePath = joinpath(dirpath, filename)
  118. yield self.startElement(
  119. 'Component', Id = componentId, Guid = guid,
  120. DiskId = '1', Win64 = self.win64
  121. )
  122. yield self.startElement(
  123. 'File', Id = makeFileId(guid), Name = filename,
  124. Source = sourcePath
  125. )
  126. yield self.endElement('File')
  127. yield self.endElement('Component')
  128. components.append(componentId)
  129. # Drain pushed physical directories
  130. while stack:
  131. popped = stack.pop()
  132. yield self.endElement('Directory')
  133. # Drain pushed virtual directories
  134. while virtualstack:
  135. popped = virtualstack.pop()
  136. yield self.endElement('Directory')
  137. yield self.endElement('DirectoryRef')
  138. yield self.endElement('Fragment')
  139. # Emit ComponentGroup
  140. yield self.startElement('Fragment')
  141. yield self.startElement('ComponentGroup', Id = self.componentGroup)
  142. for component in components:
  143. yield self.startElement('ComponentRef', Id = component)
  144. yield self.endElement('ComponentRef')
  145. yield self.endElement('ComponentGroup')
  146. yield self.endElement('Fragment')
  147. yield self.endElement('Wix')
  148. def generateWixFragment(
  149. sourcePath, componentGroup, directoryRef, virtualDir, excludedFile, win64
  150. ):
  151. fileGenerator = walkPath(sourcePath)
  152. wf = WixFragment(
  153. fileGenerator, componentGroup, directoryRef, virtualDir, excludedFile,
  154. win64
  155. )
  156. return wf.yieldFragment()
  157. def run():
  158. parser = OptionParser()
  159. parser.add_option(
  160. '-c', '--componentGroup', type = 'string', dest = 'componentGroup'
  161. )
  162. parser.add_option(
  163. '-r', '--directoryRef', type = 'string', dest = 'directoryRef'
  164. )
  165. parser.add_option(
  166. '-s', '--sourcePath', type = 'string', dest = 'sourcePath'
  167. )
  168. parser.add_option(
  169. '-v', '--virtualDir', type = 'string', dest = 'virtualDir'
  170. )
  171. parser.add_option(
  172. '-x', '--excludedFile', type = 'string', dest = 'excludedFile'
  173. )
  174. parser.add_option(
  175. '-w', '--win64', type = 'string', dest = 'win64'
  176. )
  177. options, args_ = parser.parse_args()
  178. for line in generateWixFragment(
  179. options.sourcePath, options.componentGroup, options.directoryRef,
  180. options.virtualDir, options.excludedFile, options.win64
  181. ):
  182. print(line)
  183. if __name__ == '__main__':
  184. run()