build_src.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. """ Build swig and f2py sources.
  2. """
  3. from __future__ import division, absolute_import, print_function
  4. import os
  5. import re
  6. import sys
  7. import shlex
  8. import copy
  9. from distutils.command import build_ext
  10. from distutils.dep_util import newer_group, newer
  11. from distutils.util import get_platform
  12. from distutils.errors import DistutilsError, DistutilsSetupError
  13. # this import can't be done here, as it uses numpy stuff only available
  14. # after it's installed
  15. #import numpy.f2py
  16. from numpy.distutils import log
  17. from numpy.distutils.misc_util import (
  18. fortran_ext_match, appendpath, is_string, is_sequence, get_cmd
  19. )
  20. from numpy.distutils.from_template import process_file as process_f_file
  21. from numpy.distutils.conv_template import process_file as process_c_file
  22. def subst_vars(target, source, d):
  23. """Substitute any occurrence of @foo@ by d['foo'] from source file into
  24. target."""
  25. var = re.compile('@([a-zA-Z_]+)@')
  26. with open(source, 'r') as fs:
  27. with open(target, 'w') as ft:
  28. for l in fs:
  29. m = var.search(l)
  30. if m:
  31. ft.write(l.replace('@%s@' % m.group(1), d[m.group(1)]))
  32. else:
  33. ft.write(l)
  34. class build_src(build_ext.build_ext):
  35. description = "build sources from SWIG, F2PY files or a function"
  36. user_options = [
  37. ('build-src=', 'd', "directory to \"build\" sources to"),
  38. ('f2py-opts=', None, "list of f2py command line options"),
  39. ('swig=', None, "path to the SWIG executable"),
  40. ('swig-opts=', None, "list of SWIG command line options"),
  41. ('swig-cpp', None, "make SWIG create C++ files (default is autodetected from sources)"),
  42. ('f2pyflags=', None, "additional flags to f2py (use --f2py-opts= instead)"), # obsolete
  43. ('swigflags=', None, "additional flags to swig (use --swig-opts= instead)"), # obsolete
  44. ('force', 'f', "forcibly build everything (ignore file timestamps)"),
  45. ('inplace', 'i',
  46. "ignore build-lib and put compiled extensions into the source " +
  47. "directory alongside your pure Python modules"),
  48. ('verbose-cfg', None,
  49. "change logging level from WARN to INFO which will show all " +
  50. "compiler output")
  51. ]
  52. boolean_options = ['force', 'inplace', 'verbose-cfg']
  53. help_options = []
  54. def initialize_options(self):
  55. self.extensions = None
  56. self.package = None
  57. self.py_modules = None
  58. self.py_modules_dict = None
  59. self.build_src = None
  60. self.build_lib = None
  61. self.build_base = None
  62. self.force = None
  63. self.inplace = None
  64. self.package_dir = None
  65. self.f2pyflags = None # obsolete
  66. self.f2py_opts = None
  67. self.swigflags = None # obsolete
  68. self.swig_opts = None
  69. self.swig_cpp = None
  70. self.swig = None
  71. self.verbose_cfg = None
  72. def finalize_options(self):
  73. self.set_undefined_options('build',
  74. ('build_base', 'build_base'),
  75. ('build_lib', 'build_lib'),
  76. ('force', 'force'))
  77. if self.package is None:
  78. self.package = self.distribution.ext_package
  79. self.extensions = self.distribution.ext_modules
  80. self.libraries = self.distribution.libraries or []
  81. self.py_modules = self.distribution.py_modules or []
  82. self.data_files = self.distribution.data_files or []
  83. if self.build_src is None:
  84. plat_specifier = ".{}-{}.{}".format(get_platform(), *sys.version_info[:2])
  85. self.build_src = os.path.join(self.build_base, 'src'+plat_specifier)
  86. # py_modules_dict is used in build_py.find_package_modules
  87. self.py_modules_dict = {}
  88. if self.f2pyflags:
  89. if self.f2py_opts:
  90. log.warn('ignoring --f2pyflags as --f2py-opts already used')
  91. else:
  92. self.f2py_opts = self.f2pyflags
  93. self.f2pyflags = None
  94. if self.f2py_opts is None:
  95. self.f2py_opts = []
  96. else:
  97. self.f2py_opts = shlex.split(self.f2py_opts)
  98. if self.swigflags:
  99. if self.swig_opts:
  100. log.warn('ignoring --swigflags as --swig-opts already used')
  101. else:
  102. self.swig_opts = self.swigflags
  103. self.swigflags = None
  104. if self.swig_opts is None:
  105. self.swig_opts = []
  106. else:
  107. self.swig_opts = shlex.split(self.swig_opts)
  108. # use options from build_ext command
  109. build_ext = self.get_finalized_command('build_ext')
  110. if self.inplace is None:
  111. self.inplace = build_ext.inplace
  112. if self.swig_cpp is None:
  113. self.swig_cpp = build_ext.swig_cpp
  114. for c in ['swig', 'swig_opt']:
  115. o = '--'+c.replace('_', '-')
  116. v = getattr(build_ext, c, None)
  117. if v:
  118. if getattr(self, c):
  119. log.warn('both build_src and build_ext define %s option' % (o))
  120. else:
  121. log.info('using "%s=%s" option from build_ext command' % (o, v))
  122. setattr(self, c, v)
  123. def run(self):
  124. log.info("build_src")
  125. if not (self.extensions or self.libraries):
  126. return
  127. self.build_sources()
  128. def build_sources(self):
  129. if self.inplace:
  130. self.get_package_dir = \
  131. self.get_finalized_command('build_py').get_package_dir
  132. self.build_py_modules_sources()
  133. for libname_info in self.libraries:
  134. self.build_library_sources(*libname_info)
  135. if self.extensions:
  136. self.check_extensions_list(self.extensions)
  137. for ext in self.extensions:
  138. self.build_extension_sources(ext)
  139. self.build_data_files_sources()
  140. self.build_npy_pkg_config()
  141. def build_data_files_sources(self):
  142. if not self.data_files:
  143. return
  144. log.info('building data_files sources')
  145. from numpy.distutils.misc_util import get_data_files
  146. new_data_files = []
  147. for data in self.data_files:
  148. if isinstance(data, str):
  149. new_data_files.append(data)
  150. elif isinstance(data, tuple):
  151. d, files = data
  152. if self.inplace:
  153. build_dir = self.get_package_dir('.'.join(d.split(os.sep)))
  154. else:
  155. build_dir = os.path.join(self.build_src, d)
  156. funcs = [f for f in files if hasattr(f, '__call__')]
  157. files = [f for f in files if not hasattr(f, '__call__')]
  158. for f in funcs:
  159. if f.__code__.co_argcount==1:
  160. s = f(build_dir)
  161. else:
  162. s = f()
  163. if s is not None:
  164. if isinstance(s, list):
  165. files.extend(s)
  166. elif isinstance(s, str):
  167. files.append(s)
  168. else:
  169. raise TypeError(repr(s))
  170. filenames = get_data_files((d, files))
  171. new_data_files.append((d, filenames))
  172. else:
  173. raise TypeError(repr(data))
  174. self.data_files[:] = new_data_files
  175. def _build_npy_pkg_config(self, info, gd):
  176. template, install_dir, subst_dict = info
  177. template_dir = os.path.dirname(template)
  178. for k, v in gd.items():
  179. subst_dict[k] = v
  180. if self.inplace == 1:
  181. generated_dir = os.path.join(template_dir, install_dir)
  182. else:
  183. generated_dir = os.path.join(self.build_src, template_dir,
  184. install_dir)
  185. generated = os.path.basename(os.path.splitext(template)[0])
  186. generated_path = os.path.join(generated_dir, generated)
  187. if not os.path.exists(generated_dir):
  188. os.makedirs(generated_dir)
  189. subst_vars(generated_path, template, subst_dict)
  190. # Where to install relatively to install prefix
  191. full_install_dir = os.path.join(template_dir, install_dir)
  192. return full_install_dir, generated_path
  193. def build_npy_pkg_config(self):
  194. log.info('build_src: building npy-pkg config files')
  195. # XXX: another ugly workaround to circumvent distutils brain damage. We
  196. # need the install prefix here, but finalizing the options of the
  197. # install command when only building sources cause error. Instead, we
  198. # copy the install command instance, and finalize the copy so that it
  199. # does not disrupt how distutils want to do things when with the
  200. # original install command instance.
  201. install_cmd = copy.copy(get_cmd('install'))
  202. if not install_cmd.finalized == 1:
  203. install_cmd.finalize_options()
  204. build_npkg = False
  205. if self.inplace == 1:
  206. top_prefix = '.'
  207. build_npkg = True
  208. elif hasattr(install_cmd, 'install_libbase'):
  209. top_prefix = install_cmd.install_libbase
  210. build_npkg = True
  211. if build_npkg:
  212. for pkg, infos in self.distribution.installed_pkg_config.items():
  213. pkg_path = self.distribution.package_dir[pkg]
  214. prefix = os.path.join(os.path.abspath(top_prefix), pkg_path)
  215. d = {'prefix': prefix}
  216. for info in infos:
  217. install_dir, generated = self._build_npy_pkg_config(info, d)
  218. self.distribution.data_files.append((install_dir,
  219. [generated]))
  220. def build_py_modules_sources(self):
  221. if not self.py_modules:
  222. return
  223. log.info('building py_modules sources')
  224. new_py_modules = []
  225. for source in self.py_modules:
  226. if is_sequence(source) and len(source)==3:
  227. package, module_base, source = source
  228. if self.inplace:
  229. build_dir = self.get_package_dir(package)
  230. else:
  231. build_dir = os.path.join(self.build_src,
  232. os.path.join(*package.split('.')))
  233. if hasattr(source, '__call__'):
  234. target = os.path.join(build_dir, module_base + '.py')
  235. source = source(target)
  236. if source is None:
  237. continue
  238. modules = [(package, module_base, source)]
  239. if package not in self.py_modules_dict:
  240. self.py_modules_dict[package] = []
  241. self.py_modules_dict[package] += modules
  242. else:
  243. new_py_modules.append(source)
  244. self.py_modules[:] = new_py_modules
  245. def build_library_sources(self, lib_name, build_info):
  246. sources = list(build_info.get('sources', []))
  247. if not sources:
  248. return
  249. log.info('building library "%s" sources' % (lib_name))
  250. sources = self.generate_sources(sources, (lib_name, build_info))
  251. sources = self.template_sources(sources, (lib_name, build_info))
  252. sources, h_files = self.filter_h_files(sources)
  253. if h_files:
  254. log.info('%s - nothing done with h_files = %s',
  255. self.package, h_files)
  256. #for f in h_files:
  257. # self.distribution.headers.append((lib_name,f))
  258. build_info['sources'] = sources
  259. return
  260. def build_extension_sources(self, ext):
  261. sources = list(ext.sources)
  262. log.info('building extension "%s" sources' % (ext.name))
  263. fullname = self.get_ext_fullname(ext.name)
  264. modpath = fullname.split('.')
  265. package = '.'.join(modpath[0:-1])
  266. if self.inplace:
  267. self.ext_target_dir = self.get_package_dir(package)
  268. sources = self.generate_sources(sources, ext)
  269. sources = self.template_sources(sources, ext)
  270. sources = self.swig_sources(sources, ext)
  271. sources = self.f2py_sources(sources, ext)
  272. sources = self.pyrex_sources(sources, ext)
  273. sources, py_files = self.filter_py_files(sources)
  274. if package not in self.py_modules_dict:
  275. self.py_modules_dict[package] = []
  276. modules = []
  277. for f in py_files:
  278. module = os.path.splitext(os.path.basename(f))[0]
  279. modules.append((package, module, f))
  280. self.py_modules_dict[package] += modules
  281. sources, h_files = self.filter_h_files(sources)
  282. if h_files:
  283. log.info('%s - nothing done with h_files = %s',
  284. package, h_files)
  285. #for f in h_files:
  286. # self.distribution.headers.append((package,f))
  287. ext.sources = sources
  288. def generate_sources(self, sources, extension):
  289. new_sources = []
  290. func_sources = []
  291. for source in sources:
  292. if is_string(source):
  293. new_sources.append(source)
  294. else:
  295. func_sources.append(source)
  296. if not func_sources:
  297. return new_sources
  298. if self.inplace and not is_sequence(extension):
  299. build_dir = self.ext_target_dir
  300. else:
  301. if is_sequence(extension):
  302. name = extension[0]
  303. # if 'include_dirs' not in extension[1]:
  304. # extension[1]['include_dirs'] = []
  305. # incl_dirs = extension[1]['include_dirs']
  306. else:
  307. name = extension.name
  308. # incl_dirs = extension.include_dirs
  309. #if self.build_src not in incl_dirs:
  310. # incl_dirs.append(self.build_src)
  311. build_dir = os.path.join(*([self.build_src]
  312. +name.split('.')[:-1]))
  313. self.mkpath(build_dir)
  314. if self.verbose_cfg:
  315. new_level = log.INFO
  316. else:
  317. new_level = log.WARN
  318. old_level = log.set_threshold(new_level)
  319. for func in func_sources:
  320. source = func(extension, build_dir)
  321. if not source:
  322. continue
  323. if is_sequence(source):
  324. [log.info(" adding '%s' to sources." % (s,)) for s in source]
  325. new_sources.extend(source)
  326. else:
  327. log.info(" adding '%s' to sources." % (source,))
  328. new_sources.append(source)
  329. log.set_threshold(old_level)
  330. return new_sources
  331. def filter_py_files(self, sources):
  332. return self.filter_files(sources, ['.py'])
  333. def filter_h_files(self, sources):
  334. return self.filter_files(sources, ['.h', '.hpp', '.inc'])
  335. def filter_files(self, sources, exts = []):
  336. new_sources = []
  337. files = []
  338. for source in sources:
  339. (base, ext) = os.path.splitext(source)
  340. if ext in exts:
  341. files.append(source)
  342. else:
  343. new_sources.append(source)
  344. return new_sources, files
  345. def template_sources(self, sources, extension):
  346. new_sources = []
  347. if is_sequence(extension):
  348. depends = extension[1].get('depends')
  349. include_dirs = extension[1].get('include_dirs')
  350. else:
  351. depends = extension.depends
  352. include_dirs = extension.include_dirs
  353. for source in sources:
  354. (base, ext) = os.path.splitext(source)
  355. if ext == '.src': # Template file
  356. if self.inplace:
  357. target_dir = os.path.dirname(base)
  358. else:
  359. target_dir = appendpath(self.build_src, os.path.dirname(base))
  360. self.mkpath(target_dir)
  361. target_file = os.path.join(target_dir, os.path.basename(base))
  362. if (self.force or newer_group([source] + depends, target_file)):
  363. if _f_pyf_ext_match(base):
  364. log.info("from_template:> %s" % (target_file))
  365. outstr = process_f_file(source)
  366. else:
  367. log.info("conv_template:> %s" % (target_file))
  368. outstr = process_c_file(source)
  369. with open(target_file, 'w') as fid:
  370. fid.write(outstr)
  371. if _header_ext_match(target_file):
  372. d = os.path.dirname(target_file)
  373. if d not in include_dirs:
  374. log.info(" adding '%s' to include_dirs." % (d))
  375. include_dirs.append(d)
  376. new_sources.append(target_file)
  377. else:
  378. new_sources.append(source)
  379. return new_sources
  380. def pyrex_sources(self, sources, extension):
  381. """Pyrex not supported; this remains for Cython support (see below)"""
  382. new_sources = []
  383. ext_name = extension.name.split('.')[-1]
  384. for source in sources:
  385. (base, ext) = os.path.splitext(source)
  386. if ext == '.pyx':
  387. target_file = self.generate_a_pyrex_source(base, ext_name,
  388. source,
  389. extension)
  390. new_sources.append(target_file)
  391. else:
  392. new_sources.append(source)
  393. return new_sources
  394. def generate_a_pyrex_source(self, base, ext_name, source, extension):
  395. """Pyrex is not supported, but some projects monkeypatch this method.
  396. That allows compiling Cython code, see gh-6955.
  397. This method will remain here for compatibility reasons.
  398. """
  399. return []
  400. def f2py_sources(self, sources, extension):
  401. new_sources = []
  402. f2py_sources = []
  403. f_sources = []
  404. f2py_targets = {}
  405. target_dirs = []
  406. ext_name = extension.name.split('.')[-1]
  407. skip_f2py = 0
  408. for source in sources:
  409. (base, ext) = os.path.splitext(source)
  410. if ext == '.pyf': # F2PY interface file
  411. if self.inplace:
  412. target_dir = os.path.dirname(base)
  413. else:
  414. target_dir = appendpath(self.build_src, os.path.dirname(base))
  415. if os.path.isfile(source):
  416. name = get_f2py_modulename(source)
  417. if name != ext_name:
  418. raise DistutilsSetupError('mismatch of extension names: %s '
  419. 'provides %r but expected %r' % (
  420. source, name, ext_name))
  421. target_file = os.path.join(target_dir, name+'module.c')
  422. else:
  423. log.debug(' source %s does not exist: skipping f2py\'ing.' \
  424. % (source))
  425. name = ext_name
  426. skip_f2py = 1
  427. target_file = os.path.join(target_dir, name+'module.c')
  428. if not os.path.isfile(target_file):
  429. log.warn(' target %s does not exist:\n '\
  430. 'Assuming %smodule.c was generated with '\
  431. '"build_src --inplace" command.' \
  432. % (target_file, name))
  433. target_dir = os.path.dirname(base)
  434. target_file = os.path.join(target_dir, name+'module.c')
  435. if not os.path.isfile(target_file):
  436. raise DistutilsSetupError("%r missing" % (target_file,))
  437. log.info(' Yes! Using %r as up-to-date target.' \
  438. % (target_file))
  439. target_dirs.append(target_dir)
  440. f2py_sources.append(source)
  441. f2py_targets[source] = target_file
  442. new_sources.append(target_file)
  443. elif fortran_ext_match(ext):
  444. f_sources.append(source)
  445. else:
  446. new_sources.append(source)
  447. if not (f2py_sources or f_sources):
  448. return new_sources
  449. for d in target_dirs:
  450. self.mkpath(d)
  451. f2py_options = extension.f2py_options + self.f2py_opts
  452. if self.distribution.libraries:
  453. for name, build_info in self.distribution.libraries:
  454. if name in extension.libraries:
  455. f2py_options.extend(build_info.get('f2py_options', []))
  456. log.info("f2py options: %s" % (f2py_options))
  457. if f2py_sources:
  458. if len(f2py_sources) != 1:
  459. raise DistutilsSetupError(
  460. 'only one .pyf file is allowed per extension module but got'\
  461. ' more: %r' % (f2py_sources,))
  462. source = f2py_sources[0]
  463. target_file = f2py_targets[source]
  464. target_dir = os.path.dirname(target_file) or '.'
  465. depends = [source] + extension.depends
  466. if (self.force or newer_group(depends, target_file, 'newer')) \
  467. and not skip_f2py:
  468. log.info("f2py: %s" % (source))
  469. import numpy.f2py
  470. numpy.f2py.run_main(f2py_options
  471. + ['--build-dir', target_dir, source])
  472. else:
  473. log.debug(" skipping '%s' f2py interface (up-to-date)" % (source))
  474. else:
  475. #XXX TODO: --inplace support for sdist command
  476. if is_sequence(extension):
  477. name = extension[0]
  478. else: name = extension.name
  479. target_dir = os.path.join(*([self.build_src]
  480. +name.split('.')[:-1]))
  481. target_file = os.path.join(target_dir, ext_name + 'module.c')
  482. new_sources.append(target_file)
  483. depends = f_sources + extension.depends
  484. if (self.force or newer_group(depends, target_file, 'newer')) \
  485. and not skip_f2py:
  486. log.info("f2py:> %s" % (target_file))
  487. self.mkpath(target_dir)
  488. import numpy.f2py
  489. numpy.f2py.run_main(f2py_options + ['--lower',
  490. '--build-dir', target_dir]+\
  491. ['-m', ext_name]+f_sources)
  492. else:
  493. log.debug(" skipping f2py fortran files for '%s' (up-to-date)"\
  494. % (target_file))
  495. if not os.path.isfile(target_file):
  496. raise DistutilsError("f2py target file %r not generated" % (target_file,))
  497. build_dir = os.path.join(self.build_src, target_dir)
  498. target_c = os.path.join(build_dir, 'fortranobject.c')
  499. target_h = os.path.join(build_dir, 'fortranobject.h')
  500. log.info(" adding '%s' to sources." % (target_c))
  501. new_sources.append(target_c)
  502. if build_dir not in extension.include_dirs:
  503. log.info(" adding '%s' to include_dirs." % (build_dir))
  504. extension.include_dirs.append(build_dir)
  505. if not skip_f2py:
  506. import numpy.f2py
  507. d = os.path.dirname(numpy.f2py.__file__)
  508. source_c = os.path.join(d, 'src', 'fortranobject.c')
  509. source_h = os.path.join(d, 'src', 'fortranobject.h')
  510. if newer(source_c, target_c) or newer(source_h, target_h):
  511. self.mkpath(os.path.dirname(target_c))
  512. self.copy_file(source_c, target_c)
  513. self.copy_file(source_h, target_h)
  514. else:
  515. if not os.path.isfile(target_c):
  516. raise DistutilsSetupError("f2py target_c file %r not found" % (target_c,))
  517. if not os.path.isfile(target_h):
  518. raise DistutilsSetupError("f2py target_h file %r not found" % (target_h,))
  519. for name_ext in ['-f2pywrappers.f', '-f2pywrappers2.f90']:
  520. filename = os.path.join(target_dir, ext_name + name_ext)
  521. if os.path.isfile(filename):
  522. log.info(" adding '%s' to sources." % (filename))
  523. f_sources.append(filename)
  524. return new_sources + f_sources
  525. def swig_sources(self, sources, extension):
  526. # Assuming SWIG 1.3.14 or later. See compatibility note in
  527. # http://www.swig.org/Doc1.3/Python.html#Python_nn6
  528. new_sources = []
  529. swig_sources = []
  530. swig_targets = {}
  531. target_dirs = []
  532. py_files = [] # swig generated .py files
  533. target_ext = '.c'
  534. if '-c++' in extension.swig_opts:
  535. typ = 'c++'
  536. is_cpp = True
  537. extension.swig_opts.remove('-c++')
  538. elif self.swig_cpp:
  539. typ = 'c++'
  540. is_cpp = True
  541. else:
  542. typ = None
  543. is_cpp = False
  544. skip_swig = 0
  545. ext_name = extension.name.split('.')[-1]
  546. for source in sources:
  547. (base, ext) = os.path.splitext(source)
  548. if ext == '.i': # SWIG interface file
  549. # the code below assumes that the sources list
  550. # contains not more than one .i SWIG interface file
  551. if self.inplace:
  552. target_dir = os.path.dirname(base)
  553. py_target_dir = self.ext_target_dir
  554. else:
  555. target_dir = appendpath(self.build_src, os.path.dirname(base))
  556. py_target_dir = target_dir
  557. if os.path.isfile(source):
  558. name = get_swig_modulename(source)
  559. if name != ext_name[1:]:
  560. raise DistutilsSetupError(
  561. 'mismatch of extension names: %s provides %r'
  562. ' but expected %r' % (source, name, ext_name[1:]))
  563. if typ is None:
  564. typ = get_swig_target(source)
  565. is_cpp = typ=='c++'
  566. else:
  567. typ2 = get_swig_target(source)
  568. if typ2 is None:
  569. log.warn('source %r does not define swig target, assuming %s swig target' \
  570. % (source, typ))
  571. elif typ!=typ2:
  572. log.warn('expected %r but source %r defines %r swig target' \
  573. % (typ, source, typ2))
  574. if typ2=='c++':
  575. log.warn('resetting swig target to c++ (some targets may have .c extension)')
  576. is_cpp = True
  577. else:
  578. log.warn('assuming that %r has c++ swig target' % (source))
  579. if is_cpp:
  580. target_ext = '.cpp'
  581. target_file = os.path.join(target_dir, '%s_wrap%s' \
  582. % (name, target_ext))
  583. else:
  584. log.warn(' source %s does not exist: skipping swig\'ing.' \
  585. % (source))
  586. name = ext_name[1:]
  587. skip_swig = 1
  588. target_file = _find_swig_target(target_dir, name)
  589. if not os.path.isfile(target_file):
  590. log.warn(' target %s does not exist:\n '\
  591. 'Assuming %s_wrap.{c,cpp} was generated with '\
  592. '"build_src --inplace" command.' \
  593. % (target_file, name))
  594. target_dir = os.path.dirname(base)
  595. target_file = _find_swig_target(target_dir, name)
  596. if not os.path.isfile(target_file):
  597. raise DistutilsSetupError("%r missing" % (target_file,))
  598. log.warn(' Yes! Using %r as up-to-date target.' \
  599. % (target_file))
  600. target_dirs.append(target_dir)
  601. new_sources.append(target_file)
  602. py_files.append(os.path.join(py_target_dir, name+'.py'))
  603. swig_sources.append(source)
  604. swig_targets[source] = new_sources[-1]
  605. else:
  606. new_sources.append(source)
  607. if not swig_sources:
  608. return new_sources
  609. if skip_swig:
  610. return new_sources + py_files
  611. for d in target_dirs:
  612. self.mkpath(d)
  613. swig = self.swig or self.find_swig()
  614. swig_cmd = [swig, "-python"] + extension.swig_opts
  615. if is_cpp:
  616. swig_cmd.append('-c++')
  617. for d in extension.include_dirs:
  618. swig_cmd.append('-I'+d)
  619. for source in swig_sources:
  620. target = swig_targets[source]
  621. depends = [source] + extension.depends
  622. if self.force or newer_group(depends, target, 'newer'):
  623. log.info("%s: %s" % (os.path.basename(swig) \
  624. + (is_cpp and '++' or ''), source))
  625. self.spawn(swig_cmd + self.swig_opts \
  626. + ["-o", target, '-outdir', py_target_dir, source])
  627. else:
  628. log.debug(" skipping '%s' swig interface (up-to-date)" \
  629. % (source))
  630. return new_sources + py_files
  631. _f_pyf_ext_match = re.compile(r'.*[.](f90|f95|f77|for|ftn|f|pyf)\Z', re.I).match
  632. _header_ext_match = re.compile(r'.*[.](inc|h|hpp)\Z', re.I).match
  633. #### SWIG related auxiliary functions ####
  634. _swig_module_name_match = re.compile(r'\s*%module\s*(.*\(\s*package\s*=\s*"(?P<package>[\w_]+)".*\)|)\s*(?P<name>[\w_]+)',
  635. re.I).match
  636. _has_c_header = re.compile(r'-[*]-\s*c\s*-[*]-', re.I).search
  637. _has_cpp_header = re.compile(r'-[*]-\s*c[+][+]\s*-[*]-', re.I).search
  638. def get_swig_target(source):
  639. with open(source, 'r') as f:
  640. result = None
  641. line = f.readline()
  642. if _has_cpp_header(line):
  643. result = 'c++'
  644. if _has_c_header(line):
  645. result = 'c'
  646. return result
  647. def get_swig_modulename(source):
  648. with open(source, 'r') as f:
  649. name = None
  650. for line in f:
  651. m = _swig_module_name_match(line)
  652. if m:
  653. name = m.group('name')
  654. break
  655. return name
  656. def _find_swig_target(target_dir, name):
  657. for ext in ['.cpp', '.c']:
  658. target = os.path.join(target_dir, '%s_wrap%s' % (name, ext))
  659. if os.path.isfile(target):
  660. break
  661. return target
  662. #### F2PY related auxiliary functions ####
  663. _f2py_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]+)',
  664. re.I).match
  665. _f2py_user_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'
  666. r'__user__[\w_]*)', re.I).match
  667. def get_f2py_modulename(source):
  668. name = None
  669. with open(source) as f:
  670. for line in f:
  671. m = _f2py_module_name_match(line)
  672. if m:
  673. if _f2py_user_module_name_match(line): # skip *__user__* names
  674. continue
  675. name = m.group('name')
  676. break
  677. return name
  678. ##########################################