pyxbuild.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """Build a Pyrex file from .pyx source to .so loadable module using
  2. the installed distutils infrastructure. Call:
  3. out_fname = pyx_to_dll("foo.pyx")
  4. """
  5. import os
  6. import sys
  7. from distutils.errors import DistutilsArgError, DistutilsError, CCompilerError
  8. from distutils.extension import Extension
  9. from distutils.util import grok_environment_error
  10. try:
  11. from Cython.Distutils.old_build_ext import old_build_ext as build_ext
  12. HAS_CYTHON = True
  13. except ImportError:
  14. HAS_CYTHON = False
  15. DEBUG = 0
  16. _reloads={}
  17. def pyx_to_dll(filename, ext=None, force_rebuild=0, build_in_temp=False, pyxbuild_dir=None,
  18. setup_args=None, reload_support=False, inplace=False):
  19. """Compile a PYX file to a DLL and return the name of the generated .so
  20. or .dll ."""
  21. assert os.path.exists(filename), "Could not find %s" % os.path.abspath(filename)
  22. path, name = os.path.split(os.path.abspath(filename))
  23. if not ext:
  24. modname, extension = os.path.splitext(name)
  25. assert extension in (".pyx", ".py"), extension
  26. if not HAS_CYTHON:
  27. filename = filename[:-len(extension)] + '.c'
  28. ext = Extension(name=modname, sources=[filename])
  29. if setup_args is None:
  30. setup_args = {}
  31. if not pyxbuild_dir:
  32. pyxbuild_dir = os.path.join(path, "_pyxbld")
  33. package_base_dir = path
  34. for package_name in ext.name.split('.')[-2::-1]:
  35. package_base_dir, pname = os.path.split(package_base_dir)
  36. if pname != package_name:
  37. # something is wrong - package path doesn't match file path
  38. package_base_dir = None
  39. break
  40. script_args=setup_args.get("script_args",[])
  41. if DEBUG or "--verbose" in script_args:
  42. quiet = "--verbose"
  43. else:
  44. quiet = "--quiet"
  45. args = [quiet, "build_ext"]
  46. if force_rebuild:
  47. args.append("--force")
  48. if inplace and package_base_dir:
  49. args.extend(['--build-lib', package_base_dir])
  50. if ext.name == '__init__' or ext.name.endswith('.__init__'):
  51. # package => provide __path__ early
  52. if not hasattr(ext, 'cython_directives'):
  53. ext.cython_directives = {'set_initial_path' : 'SOURCEFILE'}
  54. elif 'set_initial_path' not in ext.cython_directives:
  55. ext.cython_directives['set_initial_path'] = 'SOURCEFILE'
  56. if HAS_CYTHON and build_in_temp:
  57. args.append("--pyrex-c-in-temp")
  58. sargs = setup_args.copy()
  59. sargs.update({
  60. "script_name": None,
  61. "script_args": args + script_args,
  62. })
  63. # late import, in case setuptools replaced it
  64. from distutils.dist import Distribution
  65. dist = Distribution(sargs)
  66. if not dist.ext_modules:
  67. dist.ext_modules = []
  68. dist.ext_modules.append(ext)
  69. if HAS_CYTHON:
  70. dist.cmdclass = {'build_ext': build_ext}
  71. build = dist.get_command_obj('build')
  72. build.build_base = pyxbuild_dir
  73. cfgfiles = dist.find_config_files()
  74. dist.parse_config_files(cfgfiles)
  75. try:
  76. ok = dist.parse_command_line()
  77. except DistutilsArgError:
  78. raise
  79. if DEBUG:
  80. print("options (after parsing command line):")
  81. dist.dump_option_dicts()
  82. assert ok
  83. try:
  84. obj_build_ext = dist.get_command_obj("build_ext")
  85. dist.run_commands()
  86. so_path = obj_build_ext.get_outputs()[0]
  87. if obj_build_ext.inplace:
  88. # Python distutils get_outputs()[ returns a wrong so_path
  89. # when --inplace ; see http://bugs.python.org/issue5977
  90. # workaround:
  91. so_path = os.path.join(os.path.dirname(filename),
  92. os.path.basename(so_path))
  93. if reload_support:
  94. org_path = so_path
  95. timestamp = os.path.getmtime(org_path)
  96. global _reloads
  97. last_timestamp, last_path, count = _reloads.get(org_path, (None,None,0) )
  98. if last_timestamp == timestamp:
  99. so_path = last_path
  100. else:
  101. basename = os.path.basename(org_path)
  102. while count < 100:
  103. count += 1
  104. r_path = os.path.join(obj_build_ext.build_lib,
  105. basename + '.reload%s'%count)
  106. try:
  107. import shutil # late import / reload_support is: debugging
  108. try:
  109. # Try to unlink first --- if the .so file
  110. # is mmapped by another process,
  111. # overwriting its contents corrupts the
  112. # loaded image (on Linux) and crashes the
  113. # other process. On Windows, unlinking an
  114. # open file just fails.
  115. if os.path.isfile(r_path):
  116. os.unlink(r_path)
  117. except OSError:
  118. continue
  119. shutil.copy2(org_path, r_path)
  120. so_path = r_path
  121. except IOError:
  122. continue
  123. break
  124. else:
  125. # used up all 100 slots
  126. raise ImportError("reload count for %s reached maximum"%org_path)
  127. _reloads[org_path]=(timestamp, so_path, count)
  128. return so_path
  129. except KeyboardInterrupt:
  130. sys.exit(1)
  131. except (IOError, os.error):
  132. exc = sys.exc_info()[1]
  133. error = grok_environment_error(exc)
  134. if DEBUG:
  135. sys.stderr.write(error + "\n")
  136. raise
  137. if __name__=="__main__":
  138. pyx_to_dll("dummy.pyx")
  139. from . import test