m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

622 lines
20 KiB

6 months ago
  1. import glob
  2. import os
  3. import shutil
  4. import subprocess
  5. import sys
  6. import tempfile
  7. import warnings
  8. from sysconfig import get_config_var, get_config_vars, get_path
  9. from .runners import (
  10. CCompilerRunner,
  11. CppCompilerRunner,
  12. FortranCompilerRunner
  13. )
  14. from .util import (
  15. get_abspath, make_dirs, copy, Glob, ArbitraryDepthGlob,
  16. glob_at_depth, import_module_from_file, pyx_is_cplus,
  17. sha256_of_string, sha256_of_file, CompileError
  18. )
  19. if os.name == 'posix':
  20. objext = '.o'
  21. elif os.name == 'nt':
  22. objext = '.obj'
  23. else:
  24. warnings.warn("Unknown os.name: {}".format(os.name))
  25. objext = '.o'
  26. def compile_sources(files, Runner=None, destdir=None, cwd=None, keep_dir_struct=False,
  27. per_file_kwargs=None, **kwargs):
  28. """ Compile source code files to object files.
  29. Parameters
  30. ==========
  31. files : iterable of str
  32. Paths to source files, if ``cwd`` is given, the paths are taken as relative.
  33. Runner: CompilerRunner subclass (optional)
  34. Could be e.g. ``FortranCompilerRunner``. Will be inferred from filename
  35. extensions if missing.
  36. destdir: str
  37. Output directory, if cwd is given, the path is taken as relative.
  38. cwd: str
  39. Working directory. Specify to have compiler run in other directory.
  40. also used as root of relative paths.
  41. keep_dir_struct: bool
  42. Reproduce directory structure in `destdir`. default: ``False``
  43. per_file_kwargs: dict
  44. Dict mapping instances in ``files`` to keyword arguments.
  45. \\*\\*kwargs: dict
  46. Default keyword arguments to pass to ``Runner``.
  47. """
  48. _per_file_kwargs = {}
  49. if per_file_kwargs is not None:
  50. for k, v in per_file_kwargs.items():
  51. if isinstance(k, Glob):
  52. for path in glob.glob(k.pathname):
  53. _per_file_kwargs[path] = v
  54. elif isinstance(k, ArbitraryDepthGlob):
  55. for path in glob_at_depth(k.filename, cwd):
  56. _per_file_kwargs[path] = v
  57. else:
  58. _per_file_kwargs[k] = v
  59. # Set up destination directory
  60. destdir = destdir or '.'
  61. if not os.path.isdir(destdir):
  62. if os.path.exists(destdir):
  63. raise OSError("{} is not a directory".format(destdir))
  64. else:
  65. make_dirs(destdir)
  66. if cwd is None:
  67. cwd = '.'
  68. for f in files:
  69. copy(f, destdir, only_update=True, dest_is_dir=True)
  70. # Compile files and return list of paths to the objects
  71. dstpaths = []
  72. for f in files:
  73. if keep_dir_struct:
  74. name, ext = os.path.splitext(f)
  75. else:
  76. name, ext = os.path.splitext(os.path.basename(f))
  77. file_kwargs = kwargs.copy()
  78. file_kwargs.update(_per_file_kwargs.get(f, {}))
  79. dstpaths.append(src2obj(f, Runner, cwd=cwd, **file_kwargs))
  80. return dstpaths
  81. def get_mixed_fort_c_linker(vendor=None, cplus=False, cwd=None):
  82. vendor = vendor or os.environ.get('SYMPY_COMPILER_VENDOR', 'gnu')
  83. if vendor.lower() == 'intel':
  84. if cplus:
  85. return (FortranCompilerRunner,
  86. {'flags': ['-nofor_main', '-cxxlib']}, vendor)
  87. else:
  88. return (FortranCompilerRunner,
  89. {'flags': ['-nofor_main']}, vendor)
  90. elif vendor.lower() == 'gnu' or 'llvm':
  91. if cplus:
  92. return (CppCompilerRunner,
  93. {'lib_options': ['fortran']}, vendor)
  94. else:
  95. return (FortranCompilerRunner,
  96. {}, vendor)
  97. else:
  98. raise ValueError("No vendor found.")
  99. def link(obj_files, out_file=None, shared=False, Runner=None,
  100. cwd=None, cplus=False, fort=False, **kwargs):
  101. """ Link object files.
  102. Parameters
  103. ==========
  104. obj_files: iterable of str
  105. Paths to object files.
  106. out_file: str (optional)
  107. Path to executable/shared library, if ``None`` it will be
  108. deduced from the last item in obj_files.
  109. shared: bool
  110. Generate a shared library?
  111. Runner: CompilerRunner subclass (optional)
  112. If not given the ``cplus`` and ``fort`` flags will be inspected
  113. (fallback is the C compiler).
  114. cwd: str
  115. Path to the root of relative paths and working directory for compiler.
  116. cplus: bool
  117. C++ objects? default: ``False``.
  118. fort: bool
  119. Fortran objects? default: ``False``.
  120. \\*\\*kwargs: dict
  121. Keyword arguments passed to ``Runner``.
  122. Returns
  123. =======
  124. The absolute path to the generated shared object / executable.
  125. """
  126. if out_file is None:
  127. out_file, ext = os.path.splitext(os.path.basename(obj_files[-1]))
  128. if shared:
  129. out_file += get_config_var('EXT_SUFFIX')
  130. if not Runner:
  131. if fort:
  132. Runner, extra_kwargs, vendor = \
  133. get_mixed_fort_c_linker(
  134. vendor=kwargs.get('vendor', None),
  135. cplus=cplus,
  136. cwd=cwd,
  137. )
  138. for k, v in extra_kwargs.items():
  139. if k in kwargs:
  140. kwargs[k].expand(v)
  141. else:
  142. kwargs[k] = v
  143. else:
  144. if cplus:
  145. Runner = CppCompilerRunner
  146. else:
  147. Runner = CCompilerRunner
  148. flags = kwargs.pop('flags', [])
  149. if shared:
  150. if '-shared' not in flags:
  151. flags.append('-shared')
  152. run_linker = kwargs.pop('run_linker', True)
  153. if not run_linker:
  154. raise ValueError("run_linker was set to False (nonsensical).")
  155. out_file = get_abspath(out_file, cwd=cwd)
  156. runner = Runner(obj_files, out_file, flags, cwd=cwd, **kwargs)
  157. runner.run()
  158. return out_file
  159. def link_py_so(obj_files, so_file=None, cwd=None, libraries=None,
  160. cplus=False, fort=False, **kwargs):
  161. """ Link Python extension module (shared object) for importing
  162. Parameters
  163. ==========
  164. obj_files: iterable of str
  165. Paths to object files to be linked.
  166. so_file: str
  167. Name (path) of shared object file to create. If not specified it will
  168. have the basname of the last object file in `obj_files` but with the
  169. extension '.so' (Unix).
  170. cwd: path string
  171. Root of relative paths and working directory of linker.
  172. libraries: iterable of strings
  173. Libraries to link against, e.g. ['m'].
  174. cplus: bool
  175. Any C++ objects? default: ``False``.
  176. fort: bool
  177. Any Fortran objects? default: ``False``.
  178. kwargs**: dict
  179. Keyword arguments passed to ``link(...)``.
  180. Returns
  181. =======
  182. Absolute path to the generate shared object.
  183. """
  184. libraries = libraries or []
  185. include_dirs = kwargs.pop('include_dirs', [])
  186. library_dirs = kwargs.pop('library_dirs', [])
  187. # from distutils/command/build_ext.py:
  188. if sys.platform == "win32":
  189. warnings.warn("Windows not yet supported.")
  190. elif sys.platform == 'darwin':
  191. # Don't use the default code below
  192. pass
  193. elif sys.platform[:3] == 'aix':
  194. # Don't use the default code below
  195. pass
  196. else:
  197. if get_config_var('Py_ENABLE_SHARED'):
  198. cfgDict = get_config_vars()
  199. kwargs['linkline'] = kwargs.get('linkline', []) + [cfgDict['PY_LDFLAGS']] # PY_LDFLAGS or just LDFLAGS?
  200. library_dirs += [cfgDict['LIBDIR']]
  201. for opt in cfgDict['BLDLIBRARY'].split():
  202. if opt.startswith('-l'):
  203. libraries += [opt[2:]]
  204. else:
  205. pass
  206. flags = kwargs.pop('flags', [])
  207. needed_flags = ('-pthread',)
  208. for flag in needed_flags:
  209. if flag not in flags:
  210. flags.append(flag)
  211. return link(obj_files, shared=True, flags=flags, cwd=cwd,
  212. cplus=cplus, fort=fort, include_dirs=include_dirs,
  213. libraries=libraries, library_dirs=library_dirs, **kwargs)
  214. def simple_cythonize(src, destdir=None, cwd=None, **cy_kwargs):
  215. """ Generates a C file from a Cython source file.
  216. Parameters
  217. ==========
  218. src: str
  219. Path to Cython source.
  220. destdir: str (optional)
  221. Path to output directory (default: '.').
  222. cwd: path string (optional)
  223. Root of relative paths (default: '.').
  224. **cy_kwargs:
  225. Second argument passed to cy_compile. Generates a .cpp file if ``cplus=True`` in ``cy_kwargs``,
  226. else a .c file.
  227. """
  228. from Cython.Compiler.Main import (
  229. default_options, CompilationOptions
  230. )
  231. from Cython.Compiler.Main import compile as cy_compile
  232. assert src.lower().endswith('.pyx') or src.lower().endswith('.py')
  233. cwd = cwd or '.'
  234. destdir = destdir or '.'
  235. ext = '.cpp' if cy_kwargs.get('cplus', False) else '.c'
  236. c_name = os.path.splitext(os.path.basename(src))[0] + ext
  237. dstfile = os.path.join(destdir, c_name)
  238. if cwd:
  239. ori_dir = os.getcwd()
  240. else:
  241. ori_dir = '.'
  242. os.chdir(cwd)
  243. try:
  244. cy_options = CompilationOptions(default_options)
  245. cy_options.__dict__.update(cy_kwargs)
  246. # Set language_level if not set by cy_kwargs
  247. # as not setting it is deprecated
  248. if 'language_level' not in cy_kwargs:
  249. cy_options.__dict__['language_level'] = 3
  250. cy_result = cy_compile([src], cy_options)
  251. if cy_result.num_errors > 0:
  252. raise ValueError("Cython compilation failed.")
  253. if os.path.realpath(os.path.dirname(src)) != os.path.realpath(destdir):
  254. if os.path.exists(dstfile):
  255. os.unlink(dstfile)
  256. shutil.move(os.path.join(os.path.dirname(src), c_name), destdir)
  257. finally:
  258. os.chdir(ori_dir)
  259. return dstfile
  260. extension_mapping = {
  261. '.c': (CCompilerRunner, None),
  262. '.cpp': (CppCompilerRunner, None),
  263. '.cxx': (CppCompilerRunner, None),
  264. '.f': (FortranCompilerRunner, None),
  265. '.for': (FortranCompilerRunner, None),
  266. '.ftn': (FortranCompilerRunner, None),
  267. '.f90': (FortranCompilerRunner, None), # ifort only knows about .f90
  268. '.f95': (FortranCompilerRunner, 'f95'),
  269. '.f03': (FortranCompilerRunner, 'f2003'),
  270. '.f08': (FortranCompilerRunner, 'f2008'),
  271. }
  272. def src2obj(srcpath, Runner=None, objpath=None, cwd=None, inc_py=False, **kwargs):
  273. """ Compiles a source code file to an object file.
  274. Files ending with '.pyx' assumed to be cython files and
  275. are dispatched to pyx2obj.
  276. Parameters
  277. ==========
  278. srcpath: str
  279. Path to source file.
  280. Runner: CompilerRunner subclass (optional)
  281. If ``None``: deduced from extension of srcpath.
  282. objpath : str (optional)
  283. Path to generated object. If ``None``: deduced from ``srcpath``.
  284. cwd: str (optional)
  285. Working directory and root of relative paths. If ``None``: current dir.
  286. inc_py: bool
  287. Add Python include path to kwarg "include_dirs". Default: False
  288. \\*\\*kwargs: dict
  289. keyword arguments passed to Runner or pyx2obj
  290. """
  291. name, ext = os.path.splitext(os.path.basename(srcpath))
  292. if objpath is None:
  293. if os.path.isabs(srcpath):
  294. objpath = '.'
  295. else:
  296. objpath = os.path.dirname(srcpath)
  297. objpath = objpath or '.' # avoid objpath == ''
  298. if os.path.isdir(objpath):
  299. objpath = os.path.join(objpath, name + objext)
  300. include_dirs = kwargs.pop('include_dirs', [])
  301. if inc_py:
  302. py_inc_dir = get_path('include')
  303. if py_inc_dir not in include_dirs:
  304. include_dirs.append(py_inc_dir)
  305. if ext.lower() == '.pyx':
  306. return pyx2obj(srcpath, objpath=objpath, include_dirs=include_dirs, cwd=cwd,
  307. **kwargs)
  308. if Runner is None:
  309. Runner, std = extension_mapping[ext.lower()]
  310. if 'std' not in kwargs:
  311. kwargs['std'] = std
  312. flags = kwargs.pop('flags', [])
  313. needed_flags = ('-fPIC',)
  314. for flag in needed_flags:
  315. if flag not in flags:
  316. flags.append(flag)
  317. # src2obj implies not running the linker...
  318. run_linker = kwargs.pop('run_linker', False)
  319. if run_linker:
  320. raise CompileError("src2obj called with run_linker=True")
  321. runner = Runner([srcpath], objpath, include_dirs=include_dirs,
  322. run_linker=run_linker, cwd=cwd, flags=flags, **kwargs)
  323. runner.run()
  324. return objpath
  325. def pyx2obj(pyxpath, objpath=None, destdir=None, cwd=None,
  326. include_dirs=None, cy_kwargs=None, cplus=None, **kwargs):
  327. """
  328. Convenience function
  329. If cwd is specified, pyxpath and dst are taken to be relative
  330. If only_update is set to `True` the modification time is checked
  331. and compilation is only run if the source is newer than the
  332. destination
  333. Parameters
  334. ==========
  335. pyxpath: str
  336. Path to Cython source file.
  337. objpath: str (optional)
  338. Path to object file to generate.
  339. destdir: str (optional)
  340. Directory to put generated C file. When ``None``: directory of ``objpath``.
  341. cwd: str (optional)
  342. Working directory and root of relative paths.
  343. include_dirs: iterable of path strings (optional)
  344. Passed onto src2obj and via cy_kwargs['include_path']
  345. to simple_cythonize.
  346. cy_kwargs: dict (optional)
  347. Keyword arguments passed onto `simple_cythonize`
  348. cplus: bool (optional)
  349. Indicate whether C++ is used. default: auto-detect using ``.util.pyx_is_cplus``.
  350. compile_kwargs: dict
  351. keyword arguments passed onto src2obj
  352. Returns
  353. =======
  354. Absolute path of generated object file.
  355. """
  356. assert pyxpath.endswith('.pyx')
  357. cwd = cwd or '.'
  358. objpath = objpath or '.'
  359. destdir = destdir or os.path.dirname(objpath)
  360. abs_objpath = get_abspath(objpath, cwd=cwd)
  361. if os.path.isdir(abs_objpath):
  362. pyx_fname = os.path.basename(pyxpath)
  363. name, ext = os.path.splitext(pyx_fname)
  364. objpath = os.path.join(objpath, name + objext)
  365. cy_kwargs = cy_kwargs or {}
  366. cy_kwargs['output_dir'] = cwd
  367. if cplus is None:
  368. cplus = pyx_is_cplus(pyxpath)
  369. cy_kwargs['cplus'] = cplus
  370. interm_c_file = simple_cythonize(pyxpath, destdir=destdir, cwd=cwd, **cy_kwargs)
  371. include_dirs = include_dirs or []
  372. flags = kwargs.pop('flags', [])
  373. needed_flags = ('-fwrapv', '-pthread', '-fPIC')
  374. for flag in needed_flags:
  375. if flag not in flags:
  376. flags.append(flag)
  377. options = kwargs.pop('options', [])
  378. if kwargs.pop('strict_aliasing', False):
  379. raise CompileError("Cython requires strict aliasing to be disabled.")
  380. # Let's be explicit about standard
  381. if cplus:
  382. std = kwargs.pop('std', 'c++98')
  383. else:
  384. std = kwargs.pop('std', 'c99')
  385. return src2obj(interm_c_file, objpath=objpath, cwd=cwd,
  386. include_dirs=include_dirs, flags=flags, std=std,
  387. options=options, inc_py=True, strict_aliasing=False,
  388. **kwargs)
  389. def _any_X(srcs, cls):
  390. for src in srcs:
  391. name, ext = os.path.splitext(src)
  392. key = ext.lower()
  393. if key in extension_mapping:
  394. if extension_mapping[key][0] == cls:
  395. return True
  396. return False
  397. def any_fortran_src(srcs):
  398. return _any_X(srcs, FortranCompilerRunner)
  399. def any_cplus_src(srcs):
  400. return _any_X(srcs, CppCompilerRunner)
  401. def compile_link_import_py_ext(sources, extname=None, build_dir='.', compile_kwargs=None,
  402. link_kwargs=None):
  403. """ Compiles sources to a shared object (Python extension) and imports it
  404. Sources in ``sources`` which is imported. If shared object is newer than the sources, they
  405. are not recompiled but instead it is imported.
  406. Parameters
  407. ==========
  408. sources : string
  409. List of paths to sources.
  410. extname : string
  411. Name of extension (default: ``None``).
  412. If ``None``: taken from the last file in ``sources`` without extension.
  413. build_dir: str
  414. Path to directory in which objects files etc. are generated.
  415. compile_kwargs: dict
  416. keyword arguments passed to ``compile_sources``
  417. link_kwargs: dict
  418. keyword arguments passed to ``link_py_so``
  419. Returns
  420. =======
  421. The imported module from of the Python extension.
  422. """
  423. if extname is None:
  424. extname = os.path.splitext(os.path.basename(sources[-1]))[0]
  425. compile_kwargs = compile_kwargs or {}
  426. link_kwargs = link_kwargs or {}
  427. try:
  428. mod = import_module_from_file(os.path.join(build_dir, extname), sources)
  429. except ImportError:
  430. objs = compile_sources(list(map(get_abspath, sources)), destdir=build_dir,
  431. cwd=build_dir, **compile_kwargs)
  432. so = link_py_so(objs, cwd=build_dir, fort=any_fortran_src(sources),
  433. cplus=any_cplus_src(sources), **link_kwargs)
  434. mod = import_module_from_file(so)
  435. return mod
  436. def _write_sources_to_build_dir(sources, build_dir):
  437. build_dir = build_dir or tempfile.mkdtemp()
  438. if not os.path.isdir(build_dir):
  439. raise OSError("Non-existent directory: ", build_dir)
  440. source_files = []
  441. for name, src in sources:
  442. dest = os.path.join(build_dir, name)
  443. differs = True
  444. sha256_in_mem = sha256_of_string(src.encode('utf-8')).hexdigest()
  445. if os.path.exists(dest):
  446. if os.path.exists(dest + '.sha256'):
  447. sha256_on_disk = open(dest + '.sha256').read()
  448. else:
  449. sha256_on_disk = sha256_of_file(dest).hexdigest()
  450. differs = sha256_on_disk != sha256_in_mem
  451. if differs:
  452. with open(dest, 'wt') as fh:
  453. fh.write(src)
  454. open(dest + '.sha256', 'wt').write(sha256_in_mem)
  455. source_files.append(dest)
  456. return source_files, build_dir
  457. def compile_link_import_strings(sources, build_dir=None, **kwargs):
  458. """ Compiles, links and imports extension module from source.
  459. Parameters
  460. ==========
  461. sources : iterable of name/source pair tuples
  462. build_dir : string (default: None)
  463. Path. ``None`` implies use a temporary directory.
  464. **kwargs:
  465. Keyword arguments passed onto `compile_link_import_py_ext`.
  466. Returns
  467. =======
  468. mod : module
  469. The compiled and imported extension module.
  470. info : dict
  471. Containing ``build_dir`` as 'build_dir'.
  472. """
  473. source_files, build_dir = _write_sources_to_build_dir(sources, build_dir)
  474. mod = compile_link_import_py_ext(source_files, build_dir=build_dir, **kwargs)
  475. info = dict(build_dir=build_dir)
  476. return mod, info
  477. def compile_run_strings(sources, build_dir=None, clean=False, compile_kwargs=None, link_kwargs=None):
  478. """ Compiles, links and runs a program built from sources.
  479. Parameters
  480. ==========
  481. sources : iterable of name/source pair tuples
  482. build_dir : string (default: None)
  483. Path. ``None`` implies use a temporary directory.
  484. clean : bool
  485. Whether to remove build_dir after use. This will only have an
  486. effect if ``build_dir`` is ``None`` (which creates a temporary directory).
  487. Passing ``clean == True`` and ``build_dir != None`` raises a ``ValueError``.
  488. This will also set ``build_dir`` in returned info dictionary to ``None``.
  489. compile_kwargs: dict
  490. Keyword arguments passed onto ``compile_sources``
  491. link_kwargs: dict
  492. Keyword arguments passed onto ``link``
  493. Returns
  494. =======
  495. (stdout, stderr): pair of strings
  496. info: dict
  497. Containing exit status as 'exit_status' and ``build_dir`` as 'build_dir'
  498. """
  499. if clean and build_dir is not None:
  500. raise ValueError("Automatic removal of build_dir is only available for temporary directory.")
  501. try:
  502. source_files, build_dir = _write_sources_to_build_dir(sources, build_dir)
  503. objs = compile_sources(list(map(get_abspath, source_files)), destdir=build_dir,
  504. cwd=build_dir, **(compile_kwargs or {}))
  505. prog = link(objs, cwd=build_dir,
  506. fort=any_fortran_src(source_files),
  507. cplus=any_cplus_src(source_files), **(link_kwargs or {}))
  508. p = subprocess.Popen([prog], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  509. exit_status = p.wait()
  510. stdout, stderr = [txt.decode('utf-8') for txt in p.communicate()]
  511. finally:
  512. if clean and os.path.isdir(build_dir):
  513. shutil.rmtree(build_dir)
  514. build_dir = None
  515. info = dict(exit_status=exit_status, build_dir=build_dir)
  516. return (stdout, stderr), info