DoGenPyCode.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. """ This module implements genPyCode, which is itself a generated
  2. script with a few default parameters filled in. This module allows
  3. the user to specify alternate parameters on the command line. """
  4. import getopt
  5. import sys
  6. import os
  7. import time
  8. from direct.ffi import FFIConstants
  9. # Define a help string for the user
  10. helpString ="""
  11. genPyCode -h
  12. genPyCode
  13. genPyCode [opts] -i libdtoolconfig libcode1 libcode2 ...
  14. This script generates Python wrappers to interface with the C++
  15. libraries that have already been run through interrogate. It is
  16. necessary to run this script after building the Panda tools for the
  17. first time, or after any major change in which some of the interface
  18. may have changed.
  19. The default options are baked into genPyCode by ppremake and need not
  20. be specified. However, it is possible to override these on the
  21. command line if you need to fine-tune the behavior of genPyCode for
  22. some reason. Most often, the only needed change will be to add one or
  23. more additional libraries to the list of libraries instrumented by
  24. default.
  25. Options:
  26. -h print this message
  27. -v verbose
  28. -d generate HTML documentation too
  29. -C dir directory to write output code
  30. -H dir directory to write output HTML
  31. -x dir directory to pull extension code from
  32. -i lib interrogate library
  33. -e dir directory to search for *.in files (may be repeated)
  34. -p dir directory to search for Python source files (may be repeated)
  35. -r remove the default library list; instrument only named libraries
  36. -O no C++ comments or assertion statements
  37. -n Don't use squeezeTool to squeeze the result into one .pyz file
  38. -s Don't delete source files after squeezing
  39. Any additional names listed on the command line are taken to be names
  40. of libraries that are to be instrumented.
  41. """
  42. HTMLHeader = """
  43. <html>
  44. <head>
  45. <title>Panda3D documentation generated %s</title>
  46. </head>
  47. <body>
  48. """
  49. HTMLFooter = """
  50. </body>
  51. </html>
  52. """
  53. # Initialize variables
  54. outputCodeDir = ''
  55. outputHTMLDir = ''
  56. directDir = ''
  57. extensionsDir = ''
  58. interrogateLib = ''
  59. codeLibs = []
  60. etcPath = []
  61. pythonSourcePath = []
  62. doSqueeze = True
  63. deleteSourceAfterSqueeze = True
  64. doHTML = False
  65. native = False # This is set by genPyCode.py
  66. def doGetopts():
  67. global outputCodeDir
  68. global outputHTMLDir
  69. global extensionsDir
  70. global interrogateLib
  71. global codeLibs
  72. global doSqueeze
  73. global deleteSourceAfterSqueeze
  74. global doHTML
  75. global etcPath
  76. global pythonSourcePath
  77. # These options are allowed but are flagged as warnings (they are
  78. # deprecated with the new genPyCode script):
  79. # -g adds libgateway
  80. # -t adds libtoontown
  81. # -p adds libpirates
  82. # -o adds libopt
  83. FFIConstants.notify.setDebug(0)
  84. FFIConstants.notify.setInfo(0)
  85. # Extract the args the user passed in
  86. try:
  87. opts, pargs = getopt.getopt(sys.argv[1:], 'hvdOC:H:x:Ni:e:p:rns')
  88. except e:
  89. # User passed in a bad option, print the error and the help, then exit
  90. print(e)
  91. print(helpString)
  92. sys.exit()
  93. # Store the option values into our variables
  94. for opt in opts:
  95. flag, value = opt
  96. if (flag == '-h'):
  97. print(helpString)
  98. sys.exit()
  99. elif (flag == '-v'):
  100. if not FFIConstants.notify.getInfo():
  101. FFIConstants.notify.setInfo(1)
  102. else:
  103. FFIConstants.notify.setDebug(1)
  104. elif (flag == '-d'):
  105. doHTML = True
  106. elif (flag == '-C'):
  107. outputCodeDir = value
  108. elif (flag == '-H'):
  109. outputHTMLDir = value
  110. elif (flag == '-x'):
  111. extensionsDir = value
  112. elif (flag == '-i'):
  113. interrogateLib = value
  114. elif (flag == '-e'):
  115. etcPath.append(value)
  116. elif (flag == '-p'):
  117. pythonSourcePath.append(value)
  118. elif (flag == '-r'):
  119. codeLibs = []
  120. elif (flag == '-O'):
  121. FFIConstants.wantComments = 0
  122. FFIConstants.wantTypeChecking = 0
  123. elif (flag == '-n'):
  124. doSqueeze = False
  125. elif (flag == '-s'):
  126. deleteSourceAfterSqueeze = False
  127. else:
  128. FFIConstants.notify.error('illegal option: ' + flag)
  129. # Check for old, no-longer-used parameter:
  130. invalidParameters = [
  131. 'linux', 'win-debug', 'win-release', 'win-publish',
  132. 'install', 'release'
  133. ]
  134. if pargs and pargs[0] in invalidParameters:
  135. FFIConstants.notify.warning("parameter is deprecated: %s" % (pargs[0]))
  136. del pargs[0]
  137. # Store the program arguments into the codeLibs
  138. for arg in pargs:
  139. arg = arg.strip()
  140. if arg:
  141. codeLibs.append(arg)
  142. # Make sure each name appears on codeLibs exactly once.
  143. newLibs = []
  144. for codeLib in codeLibs:
  145. if codeLib not in newLibs:
  146. newLibs.append(codeLib)
  147. codeLibs = newLibs
  148. def doErrorCheck():
  149. global outputCodeDir
  150. global outputHTMLDir
  151. global extensionsDir
  152. global interrogateLib
  153. global codeLibs
  154. global doSqueeze
  155. global etcPath
  156. # Now do some error checking and verbose output
  157. if (not interrogateLib):
  158. FFIConstants.notify.error('You must specify an interrogate library (-i lib)')
  159. else:
  160. FFIConstants.notify.debug('Setting interrogate library to: ' + interrogateLib)
  161. FFIConstants.InterrogateModuleName = interrogateLib
  162. if (not outputCodeDir):
  163. FFIConstants.notify.info('Setting output code directory to current directory')
  164. outputCodeDir = '.'
  165. elif (not os.path.exists(outputCodeDir)):
  166. FFIConstants.notify.info('Directory does not exist, creating: ' + outputCodeDir)
  167. os.mkdir(outputCodeDir)
  168. FFIConstants.notify.info('Setting output code directory to: ' + outputCodeDir)
  169. else:
  170. FFIConstants.notify.info('Setting output code directory to: ' + outputCodeDir)
  171. if doHTML:
  172. if (not outputHTMLDir):
  173. FFIConstants.notify.info('Setting output HTML directory to current directory')
  174. outputHTMLDir = '.'
  175. elif (not os.path.exists(outputHTMLDir)):
  176. FFIConstants.notify.info('Directory does not exist, creating: ' + outputHTMLDir)
  177. os.makedirs(outputHTMLDir)
  178. FFIConstants.notify.info('Setting output HTML directory to: ' + outputHTMLDir)
  179. else:
  180. FFIConstants.notify.info('Setting output HTML directory to: ' + outputHTMLDir)
  181. if (not extensionsDir):
  182. FFIConstants.notify.debug('Setting extensions directory to current directory')
  183. extensionsDir = '.'
  184. elif (not os.path.exists(extensionsDir)):
  185. FFIConstants.notify.error('Directory does not exist: ' + extensionsDir)
  186. else:
  187. FFIConstants.notify.debug('Setting extensions directory to: ' + extensionsDir)
  188. if (not codeLibs):
  189. FFIConstants.notify.error('You must specify one or more libraries to generate code from')
  190. else:
  191. FFIConstants.notify.debug('Generating code for: ' + repr(codeLibs))
  192. FFIConstants.CodeModuleNameList = codeLibs
  193. def generateNativeWrappers():
  194. from direct.extensions_native.extension_native_helpers import Dtool_PreloadDLL
  195. # Empty out the output directories of unnecessary crud from
  196. # previous runs before we begin.
  197. for file in os.listdir(outputCodeDir):
  198. pathname = os.path.join(outputCodeDir, file)
  199. if not os.path.isdir(pathname):
  200. os.unlink(pathname)
  201. # Generate __init__.py
  202. initFilename = os.path.join(outputCodeDir, '__init__.py')
  203. init = open(initFilename, 'w')
  204. # Generate PandaModules.py
  205. pandaModulesFilename = os.path.join(outputCodeDir, 'PandaModules.py')
  206. pandaModules = open(pandaModulesFilename, 'w')
  207. # Copy in any helper classes from the extensions_native directory
  208. extensionHelperFiles = ['extension_native_helpers.py']
  209. for name in extensionHelperFiles:
  210. inFilename = os.path.join(extensionsDir, name)
  211. outFilename = os.path.join(outputCodeDir, name)
  212. if os.path.exists(inFilename):
  213. inFile = open(inFilename, 'r')
  214. outFile = open(outFilename, 'w')
  215. outFile.write(inFile.read())
  216. # Generate a series of "libpandaModules.py" etc. files, one for
  217. # each named module.
  218. for moduleName in FFIConstants.CodeModuleNameList:
  219. print('Importing code library: ' + moduleName)
  220. Dtool_PreloadDLL(moduleName)
  221. __import__(moduleName)
  222. module = sys.modules[moduleName]
  223. # Make a suitable meta module name
  224. metaModuleName = ""
  225. nextCap = False
  226. for ch in moduleName:
  227. if ch == '.':
  228. nextCap = True
  229. elif nextCap:
  230. metaModuleName += ch.upper()
  231. nextCap = False
  232. else:
  233. metaModuleName += ch
  234. metaModuleName += "Modules"
  235. # Wrap the import in a try..except so that we can continue if
  236. # the library isn't present. This is particularly necessary
  237. # in the runtime (plugin) environment, where all libraries are
  238. # not necessarily downloaded.
  239. if sys.version_info >= (3, 0):
  240. pandaModules.write('try:\n from .%s import *\nexcept ImportError as err:\n if "DLL loader cannot find" not in str(err):\n raise\n' % (metaModuleName))
  241. else:
  242. pandaModules.write('try:\n from %s import *\nexcept ImportError, err:\n if "DLL loader cannot find" not in str(err):\n raise\n' % (metaModuleName))
  243. # Not sure if this message is helpful or annoying.
  244. #pandaModules.write(' print("Failed to import %s")\n' % (moduleName))
  245. pandaModules.write('\n')
  246. moduleModulesFilename = os.path.join(outputCodeDir, '%s.py' % (metaModuleName))
  247. moduleModules = open(moduleModulesFilename, 'w')
  248. if sys.version_info >= (3, 0):
  249. moduleModules.write('from .extension_native_helpers import *\n')
  250. else:
  251. moduleModules.write('from extension_native_helpers import *\n')
  252. moduleModules.write('Dtool_PreloadDLL("%s")\n' % (moduleName))
  253. moduleModules.write('from %s import *\n\n' % (moduleName))
  254. # Now look for extensions
  255. for className, classDef in module.__dict__.items():
  256. if isinstance(classDef, type):
  257. extensionFilename = os.path.join(extensionsDir, '%s_extensions.py' % (className))
  258. if os.path.exists(extensionFilename):
  259. print(' Found extensions for class: %s' % (className))
  260. extension = open(extensionFilename, 'r')
  261. moduleModules.write(extension.read())
  262. moduleModules.write('\n')
  263. def run():
  264. global outputCodeDir
  265. global outputHTMLDir
  266. global directDir
  267. global extensionsDir
  268. global interrogateLib
  269. global codeLibs
  270. global doSqueeze
  271. global deleteSourceAfterSqueeze
  272. global etcPath
  273. global pythonSourcePath
  274. doGetopts()
  275. doErrorCheck()
  276. # Ok, now we can start generating code
  277. if native:
  278. generateNativeWrappers()
  279. else:
  280. from direct.ffi import FFIInterrogateDatabase
  281. db = FFIInterrogateDatabase.FFIInterrogateDatabase(etcPath = etcPath)
  282. db.generateCode(outputCodeDir, extensionsDir)
  283. if doSqueeze:
  284. db.squeezeGeneratedCode(outputCodeDir, deleteSourceAfterSqueeze)
  285. if doHTML:
  286. from direct.directscripts import gendocs
  287. from panda3d.core import PandaSystem
  288. versionString = '%s %s' % (
  289. PandaSystem.getDistributor(), PandaSystem.getVersionString())
  290. gendocs.generate(versionString, etcPath, pythonSourcePath,
  291. outputHTMLDir, HTMLHeader % time.asctime(),
  292. HTMLFooter, '', '.html')