build.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import sys, os, argparse, logging, shutil, subprocess, stat,glob
  2. from os.path import isfile
  3. # TODO handle icns
  4. # TODO create dmg
  5. # TODO Add client qml files and png files
  6. # CHMOD +x the main binary
  7. logging.basicConfig(
  8. stream=sys.stdout,
  9. format='%(asctime)s : %(levelname)s\t : %(message)s',
  10. datefmt='%m/%d/%Y %I:%M:%S %p',
  11. level=logging.DEBUG
  12. )
  13. XML_PLIST = """
  14. <?xml version="1.0" encoding="UTF-8"?>
  15. <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  16. <plist version="1.0">
  17. <dict>
  18. <key>CFBundleGetInfoString</key>
  19. <string>Mist</string>
  20. <key>CFBundleExecutable</key>
  21. <string>Mist</string>
  22. <key>CFBundleIdentifier</key>
  23. <string>com.ethereum.mist</string>
  24. <key>CFBundleName</key>
  25. <string>Mist</string>
  26. <key>CFBundleIconFile</key>
  27. <string>Mist.icns</string>
  28. <key>CFBundleShortVersionString</key>
  29. <string>POC8</string>
  30. <key>CFBundleInfoDictionaryVersion</key>
  31. <string>POC8</string>
  32. <key>CFBundlePackageType</key>
  33. <string>APPL</string>
  34. <key>IFMajorVersion</key>
  35. <integer>0</integer>
  36. <key>IFMinorVersion</key>
  37. <integer>5</integer>
  38. </dict>
  39. </plist>
  40. """
  41. RUN_SCRIPT ="""
  42. #!/bin/bash
  43. cd "${0%/*}"
  44. ./go-ethereum
  45. """
  46. class AppBundler:
  47. def copytree(self, src, dst, symlinks=False, ignore=None):
  48. for item in os.listdir(src):
  49. s = os.path.join(src, item)
  50. d = os.path.join(dst, item)
  51. if os.path.isdir(s):
  52. shutil.copytree(s, d, symlinks, ignore)
  53. else:
  54. shutil.copy2(s, d)
  55. # If macdeployqt handles qmldir then runs on app
  56. def runMacDeployQT(self):
  57. exe = '/usr/local/opt/qt5/bin/macdeployqt'
  58. if not os.path.exists(exe): exe = 'macdeployqt'
  59. p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  60. handles_qml = False
  61. for line in p.stdout.readlines():
  62. if '-qmldir=<path>' in line:
  63. handles_qml = True
  64. break
  65. if handles_qml and self.go_path is not None:
  66. qml_path = os.path.join(self.go_path, 'src/github.com/ethereum/go-ethereum/cmd/mist/assets/qml/') #TODO this is terrible
  67. out = os.path.join(self.output_dir + '/Mist.app')
  68. command = exe + ' ' + out + ' -executable='+out+'/Contents/MacOS/Mist' + ' -qmldir=' + qml_path #TODO this is terrible
  69. logging.info('Running macdeployqt with options')
  70. p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  71. for line in p.stdout.readlines():
  72. logging.info('macdeployqt: ' + line.strip())
  73. else:
  74. logging.error('Your version of macdeployqt does not handle qmldir')
  75. # Add ICNS file to
  76. def insertICNS(self):
  77. path = os.path.join(self.output_dir, 'Mist.app/Contents/Resources/Mist.icns')
  78. try:
  79. shutil.copyfile('./Mist.icns',path) # TODO this is horrible
  80. logging.info('Inserted Mist.icns')
  81. except Exception as e:
  82. logging.error(str(e))
  83. def insertQMLnPNG(self):
  84. pass # TODO
  85. #def signApp(self):
  86. # after macdeployqt copy /usr/local/opt/qt5/lib/QtCore.framework/Contents/Info.plist to .app/Contents/Resources/QtCore.framework/Resources/Info.plist
  87. # codesign --verbose --force --sign "Developer ID Application: <<INSERT DETAILS HERE>>" /Users/_/Dropbox/Experiments/EthereumBuild/Ethereal.app/Contents/Frameworks/QtCore.framework
  88. # do for rest
  89. # codesign --verbose --deep --force --sign "Developer ID Application: <<INSERT DETAILS HERE>>" Ethereal.app
  90. # codesign --verify --verbose=4 Ethereal.app
  91. def insertAssets(self):
  92. asset_path = os.path.join(self.go_path, 'src/github.com/ethereum/go-ethereum/cmd/mist/assets')
  93. self.copytree(asset_path,"Mist.app/Contents/Resources/")
  94. # Copy mnemonic word list
  95. #shutil.copy(os.path.join(self.go_path, 'src/github.com/ethereum/eth-go/ethcrypto/mnemonic.words.lst'),"Mist.app/Contents/Resources/")
  96. # Insert all QML files and other resource files Mist needs
  97. def insertResources(self):
  98. qml_path = os.path.join(self.go_path, 'src/github.com/ethereum/go-ethereum/cmd/mist/assets/qml/')
  99. target_folder = "Mist.app/Contents/Resources/"
  100. target_folder_qml = target_folder + "qml/"
  101. os.makedirs(target_folder_qml)
  102. files = glob.glob(qml_path)
  103. for f in files:
  104. print "Copying %s to %s" % (f, target_folder_qml)
  105. if isfile(f):
  106. shutil.copy(f, target_folder_qml)
  107. else:
  108. self.copytree(f, target_folder_qml)
  109. files = glob.glob(os.path.join(self.go_path, 'src/github.com/ethereum/go-ethereum/cmd/mist/assets/*'))
  110. for f in files:
  111. print "Copying %s to %s" % (f, target_folder)
  112. if isfile(f):
  113. shutil.copy(f, target_folder)
  114. else:
  115. self.copytree(f, target_folder)
  116. # Finds go-etherum binary and copies to app bundle
  117. def insertGoBinary(self):
  118. if self.go_path is not None:
  119. binary = os.path.join(self.go_path, 'bin/mist')
  120. if os.path.exists(binary):
  121. try:
  122. shutil.copyfile(binary, os.path.join(self.output_dir, 'Mist.app/Contents/MacOS/Mist')) # TODO this is horrible
  123. os.chmod(os.path.join(self.output_dir, 'Mist.app/Contents/MacOS/Mist'), 0711)
  124. logging.info('Inserted go-ethereum binary')
  125. except Exception as e:
  126. logging.error(str(e))
  127. else:
  128. logging.error('Cannot find go-etherum binary')
  129. if self.handleHumanInput('Run "go get -u github.com/ethereum/go-ethereum" ?'):
  130. logging.debug('Not Implemented')
  131. pass
  132. else:
  133. logging.error('GOPATH not found, cannot continue')
  134. # Write the Info.plist
  135. def writePList(self):
  136. try:
  137. with open(os.path.join(self.output_dir, 'Mist.app/Contents/Info.plist'), 'wb') as f: # TODO this is horrible
  138. f.write(XML_PLIST)
  139. f.close()
  140. logging.info('Info.plist written')
  141. except Exception as e:
  142. logging.error(str(e))
  143. # Building out directory structure
  144. def buildStructure(self, root, structure):
  145. if root is not self.output_dir:
  146. try:
  147. os.mkdir(root)
  148. logging.info('Created ' + root)
  149. except Exception as e:
  150. logging.error(str(e))
  151. if self.handleHumanInput('Remove Directory?'):
  152. try:
  153. shutil.rmtree(root)
  154. self.buildStructure(root, structure)
  155. return
  156. except Exception as e:
  157. logging.error(str(e))
  158. for item in structure.keys():
  159. self.buildStructure(
  160. os.path.join(root, item),
  161. structure[item]
  162. )
  163. # Convert human input to boolean
  164. def handleHumanInput(self, question=''):
  165. if self.force: return True
  166. try:
  167. answer = raw_input(question + " [Y/n]: ").lower()
  168. except:
  169. return True
  170. if answer is '' or answer[0:1] == 'y': return True
  171. return False
  172. logging.info('Copying QTWebProcess')
  173. libexec_path = self.output_dir + '/Mist.app/Contents/libexec'
  174. try:
  175. os.mkdir(libexec_path)
  176. shutil.copy2(path, libexec_path)
  177. return True
  178. except OSError as e:
  179. print("Problem getting QTWebprocess on path %s. Error: %s" % (path, e))
  180. return False
  181. # Setup Variables
  182. def __init__(self, args):
  183. self.force = args['force']
  184. self.output_dir = args['output']
  185. self.app_name = "".join(x for x in args['name'] if x.isalnum()) # Simple Santize
  186. self.app_structure = {
  187. '%s.app' % self.app_name : {
  188. 'Contents' : {
  189. 'MacOS' : {},
  190. 'Resources' : {}
  191. }
  192. }
  193. }
  194. self.go_path = os.environ.get('GOPATH')
  195. self.buildStructure(self.output_dir, self.app_structure)
  196. self.writePList()
  197. self.insertICNS()
  198. self.insertGoBinary()
  199. self.insertAssets()
  200. #self.insertResources()
  201. self.runMacDeployQT()
  202. os.system("sh script.sh " + self.output_dir + "/Mist.app/Contents")
  203. os.system("appdmg dmg_spec.json Mist.dmg")
  204. logging.info("fin'")
  205. if __name__ == "__main__":
  206. parser = argparse.ArgumentParser(description='Standalone Mist Go Client App Bundler')
  207. parser.add_argument('-n','--name', help='Name of app bundle', default='Mist', required=False)
  208. parser.add_argument('-q','--qtwebpath', help='Location of QtWebProcess', default='Mist', required=False)
  209. parser.add_argument('-o','--output', help='Directory to write app bundle', default=os.getcwd(), required=False)
  210. parser.add_argument('-f','--force', help='Force Fresh Build', default=False, required=False)
  211. args = vars(parser.parse_args())
  212. AppBundler(args)