compilation.py 22 KB

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