meson.build 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. # Platform detection
  2. is_mingw = is_windows and cc.get_id() == 'gcc'
  3. if is_mingw and ff.get_id() != 'gcc'
  4. error('If you are using GCC on Windows, you must also use GFortran! Detected ' + ff.get_id())
  5. endif
  6. cython_c_args = []
  7. if is_mingw
  8. # For mingw-w64, link statically against the UCRT.
  9. gcc_link_args = ['-lucrt', '-static']
  10. add_project_link_arguments(gcc_link_args, language: ['c', 'cpp', 'fortran'])
  11. # Force gcc to float64 long doubles for compatibility with MSVC
  12. # builds, for C only.
  13. add_project_arguments('-mlong-double-64', language: 'c')
  14. # Make fprintf("%zd") work (see https://github.com/rgommers/scipy/issues/118)
  15. add_project_arguments('-D__USE_MINGW_ANSI_STDIO=1', language: ['c', 'cpp'])
  16. # Silence warnings emitted by PyOS_snprintf for (%zd), see
  17. # https://github.com/rgommers/scipy/issues/118.
  18. # Use as c_args for extensions containing Cython code
  19. cython_c_args += ['-Wno-format-extra-args', '-Wno-format']
  20. # Flag needed to work around BLAS and LAPACK Gfortran dependence on
  21. # undocumented C feature when passing single character string arguments. See:
  22. # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90329
  23. # https://github.com/wch/r-source/blob/838f9d5a7be08f2a8c08e47bcd28756f5d0aac90/src/gnuwin32/MkRules.rules#L121
  24. add_project_arguments('-fno-optimize-sibling-calls', language: ['fortran'])
  25. endif
  26. thread_dep = dependency('threads', required: false)
  27. # NumPy include directory - needed in all submodules
  28. # The chdir is needed because within numpy there's an `import signal`
  29. # statement, and we don't want that to pick up scipy's signal module rather
  30. # than the stdlib module. The try-except is needed because when things are
  31. # split across drives on Windows, there is no relative path and an exception
  32. # gets raised. There may be other such cases, so add a catch-all and switch to
  33. # an absolute path. Relative paths are needed when for example a virtualenv is
  34. # placed inside the source tree; Meson rejects absolute paths to places inside
  35. # the source tree.
  36. # For cross-compilation it is often not possible to run the Python interpreter
  37. # in order to retrieve numpy's include directory. It can be specified in the
  38. # cross file instead:
  39. # [properties]
  40. # numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include
  41. #
  42. # This uses the path as is, and avoids running the interpreter.
  43. incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given')
  44. if incdir_numpy == 'not-given'
  45. incdir_numpy = run_command(py3,
  46. [
  47. '-c',
  48. '''import os
  49. os.chdir(os.path.join("..", "tools"))
  50. import numpy as np
  51. try:
  52. incdir = os.path.relpath(np.get_include())
  53. except Exception:
  54. incdir = np.get_include()
  55. print(incdir)
  56. '''
  57. ],
  58. check: true
  59. ).stdout().strip()
  60. # We do need an absolute path to feed to `cc.find_library` below
  61. _incdir_numpy_abs = run_command(py3,
  62. ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
  63. check: true
  64. ).stdout().strip()
  65. else
  66. _incdir_numpy_abs = incdir_numpy
  67. endif
  68. inc_np = include_directories(incdir_numpy)
  69. np_dep = declare_dependency(include_directories: inc_np)
  70. incdir_f2py = incdir_numpy / '..' / '..' / 'f2py' / 'src'
  71. inc_f2py = include_directories(incdir_f2py)
  72. fortranobject_c = incdir_f2py / 'fortranobject.c'
  73. npymath_path = _incdir_numpy_abs / '..' / 'lib'
  74. npyrandom_path = _incdir_numpy_abs / '..' / '..' / 'random' / 'lib'
  75. npymath_lib = cc.find_library('npymath', dirs: npymath_path)
  76. npyrandom_lib = cc.find_library('npyrandom', dirs: npyrandom_path)
  77. pybind11_dep = dependency('pybind11', version: '>=2.10.4')
  78. # Pythran include directory and build flags
  79. if use_pythran
  80. # This external-property may not be needed if we can use the native include
  81. # dir, see https://github.com/serge-sans-paille/pythran/issues/1394
  82. incdir_pythran = meson.get_external_property('pythran-include-dir', 'not-given')
  83. if incdir_pythran == 'not-given'
  84. incdir_pythran = run_command(py3,
  85. [
  86. '-c',
  87. '''import os
  88. os.chdir(os.path.join("..", "tools"))
  89. import pythran
  90. try:
  91. incdir = os.path.relpath(pythran.get_include())
  92. except Exception:
  93. incdir = pythran.get_include()
  94. print(incdir)
  95. '''
  96. ],
  97. check: true
  98. ).stdout().strip()
  99. endif
  100. pythran_dep = declare_dependency(
  101. include_directories: incdir_pythran,
  102. dependencies: xsimd_dep,
  103. )
  104. else
  105. pythran_dep = []
  106. endif
  107. # Note: warning flags are added to this further down
  108. cpp_args_pythran = [
  109. '-DENABLE_PYTHON_MODULE',
  110. '-D__PYTHRAN__=3',
  111. '-DPYTHRAN_BLAS_NONE'
  112. ]
  113. # Don't use the deprecated NumPy C API. Define this to a fixed version instead of
  114. # NPY_API_VERSION in order not to break compilation for released SciPy versions
  115. # when NumPy introduces a new deprecation. Use in a meson.build file::
  116. #
  117. # py3.extension_module('_name',
  118. # 'source_fname',
  119. # numpy_nodepr_api)
  120. #
  121. numpy_nodepr_api = '-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION'
  122. # Share this object across multiple modules.
  123. fortranobject_lib = static_library('_fortranobject',
  124. fortranobject_c,
  125. c_args: numpy_nodepr_api,
  126. dependencies: py3_dep,
  127. include_directories: [inc_np, inc_f2py],
  128. )
  129. fortranobject_dep = declare_dependency(
  130. link_with: fortranobject_lib,
  131. include_directories: [inc_np, inc_f2py],
  132. )
  133. # TODO: 64-bit BLAS and LAPACK
  134. #
  135. # Note that this works as long as BLAS and LAPACK are detected properly via
  136. # pkg-config. By default we look for OpenBLAS, other libraries can be configured via
  137. # `meson configure -Dblas=blas -Dlapack=lapack` (example to build with Netlib
  138. # BLAS and LAPACK).
  139. # For MKL and for auto-detecting one of multiple libs, we'll need a custom
  140. # dependency in Meson (like is done for scalapack) - see
  141. # https://github.com/mesonbuild/meson/issues/2835
  142. blas_name = get_option('blas')
  143. lapack_name = get_option('lapack')
  144. # pkg-config uses a lower-case name while CMake uses a capitalized name, so try
  145. # that too to make the fallback detection with CMake work
  146. if blas_name == 'openblas'
  147. blas = dependency(['openblas', 'OpenBLAS'])
  148. else
  149. blas = dependency(blas_name)
  150. endif
  151. if blas_name == 'blas'
  152. # Netlib BLAS has a separate `libcblas.so` which we use directly in the g77
  153. # ABI wrappers, so detect it and error out if we cannot find it.
  154. # In the future, this should be done automatically for:
  155. # `dependency('blas', modules: cblas)`
  156. # see https://github.com/mesonbuild/meson/pull/10921.
  157. cblas = dependency('cblas')
  158. else
  159. cblas = []
  160. endif
  161. if lapack_name == 'openblas'
  162. lapack = dependency(['openblas', 'OpenBLAS'])
  163. else
  164. lapack = dependency(lapack_name)
  165. endif
  166. dependency_map = {
  167. 'BLAS': blas,
  168. 'LAPACK': lapack,
  169. 'PYBIND11': pybind11_dep,
  170. }
  171. # FIXME: conda-forge sets MKL_INTERFACE_LAYER=LP64,GNU, see gh-11812.
  172. # This needs work on gh-16200 to make MKL robust. We should be
  173. # requesting `mkl-dynamic-lp64-seq` here. And then there's work needed
  174. # in general to enable the ILP64 interface (also for OpenBLAS).
  175. uses_mkl = blas.name().to_lower().startswith('mkl') or lapack.name().to_lower().startswith('mkl')
  176. uses_accelerate = blas.name().to_lower().startswith('accelerate') or lapack.name().to_lower().startswith('accelerate')
  177. use_g77_abi = uses_mkl or uses_accelerate or get_option('use-g77-abi')
  178. if use_g77_abi
  179. g77_abi_wrappers = declare_dependency(
  180. sources:
  181. [
  182. '_build_utils/src/wrap_g77_abi_f.f',
  183. '_build_utils/src/wrap_g77_abi_c.c'
  184. ],
  185. include_directories: inc_np,
  186. dependencies: [py3_dep, cblas],
  187. )
  188. else
  189. g77_abi_wrappers = declare_dependency(sources: ['_build_utils/src/wrap_dummy_g77_abi.f'])
  190. endif
  191. scipy_dir = py3.get_install_dir() / 'scipy'
  192. generate_version = custom_target(
  193. 'generate-version',
  194. install: true,
  195. build_always_stale: true,
  196. build_by_default: true,
  197. output: 'version.py',
  198. input: '../tools/version_utils.py',
  199. command: [py3, '@INPUT@', '--source-root', '@SOURCE_ROOT@'],
  200. install_dir: scipy_dir
  201. )
  202. python_sources = [
  203. '__init__.py',
  204. '_distributor_init.py',
  205. 'conftest.py',
  206. 'linalg.pxd',
  207. 'optimize.pxd',
  208. 'special.pxd'
  209. ]
  210. py3.install_sources(
  211. python_sources,
  212. subdir: 'scipy'
  213. )
  214. py3.install_sources(
  215. ['_build_utils/tests/test_scipy_version.py'],
  216. subdir: 'scipy/_lib/tests'
  217. )
  218. # Copy the main __init__.py and pxd files to the build dir.
  219. # Needed to trick Cython, it won't do a relative import outside a package
  220. fs = import('fs')
  221. #_cython_tree = declare_dependency(sources: [
  222. _cython_tree = [
  223. fs.copyfile('__init__.py'),
  224. fs.copyfile('linalg.pxd'),
  225. fs.copyfile('optimize.pxd'),
  226. fs.copyfile('special.pxd'),
  227. ]
  228. cython_args = ['-3', '--fast-fail', '--output-file', '@OUTPUT@', '--include-dir', '@BUILD_ROOT@', '@INPUT@']
  229. cython_cplus_args = ['--cplus'] + cython_args
  230. cython_gen = generator(cython,
  231. arguments : cython_args,
  232. output : '@BASENAME@.c',
  233. depends : _cython_tree)
  234. cython_gen_cpp = generator(cython,
  235. arguments : cython_cplus_args,
  236. output : '@BASENAME@.cpp',
  237. depends : [_cython_tree])
  238. # Check if compiler flags are supported. This is necessary to ensure that SciPy
  239. # can be built with any supported compiler. We need so many warning flags
  240. # because we want to be able to build with `-Werror` in CI; that ensures that
  241. # for new code we add, there are no unexpected new issues introduced.
  242. #
  243. # Cleaning up code so we no longer need some of these warning flags is useful,
  244. # but not a priority.
  245. #
  246. # The standard convention used here is:
  247. # - for C, drop the leading dash and turn remaining dashes into underscores
  248. # - for C++, prepend `_cpp` and turn remaining dashes into underscores
  249. # - for Fortran, prepend `_fflags` and turn remaining dashes into underscores
  250. # C warning flags
  251. Wno_maybe_uninitialized = cc.get_supported_arguments('-Wno-maybe-uninitialized')
  252. Wno_discarded_qualifiers = cc.get_supported_arguments('-Wno-discarded-qualifiers')
  253. Wno_empty_body = cc.get_supported_arguments('-Wno-empty-body')
  254. Wno_implicit_function_declaration = cc.get_supported_arguments('-Wno-implicit-function-declaration')
  255. Wno_parentheses = cc.get_supported_arguments('-Wno-parentheses')
  256. Wno_switch = cc.get_supported_arguments('-Wno-switch')
  257. Wno_unused_label = cc.get_supported_arguments('-Wno-unused-label')
  258. Wno_unused_variable = cc.get_supported_arguments('-Wno-unused-variable')
  259. Wno_incompatible_pointer_types = cc.get_supported_arguments('-Wno-incompatible-pointer-types')
  260. # C++ warning flags
  261. _cpp_Wno_cpp = cpp.get_supported_arguments('-Wno-cpp')
  262. _cpp_Wno_deprecated_declarations = cpp.get_supported_arguments('-Wno-deprecated-declarations')
  263. _cpp_Wno_class_memaccess = cpp.get_supported_arguments('-Wno-class-memaccess')
  264. _cpp_Wno_format_truncation = cpp.get_supported_arguments('-Wno-format-truncation')
  265. _cpp_Wno_non_virtual_dtor = cpp.get_supported_arguments('-Wno-non-virtual-dtor')
  266. _cpp_Wno_sign_compare = cpp.get_supported_arguments('-Wno-sign-compare')
  267. _cpp_Wno_switch = cpp.get_supported_arguments('-Wno-switch')
  268. _cpp_Wno_terminate = cpp.get_supported_arguments('-Wno-terminate')
  269. _cpp_Wno_unused_but_set_variable = cpp.get_supported_arguments('-Wno-unused-but-set-variable')
  270. _cpp_Wno_unused_function = cpp.get_supported_arguments('-Wno-unused-function')
  271. _cpp_Wno_unused_local_typedefs = cpp.get_supported_arguments('-Wno-unused-local-typedefs')
  272. _cpp_Wno_unused_variable = cpp.get_supported_arguments('-Wno-unused-variable')
  273. _cpp_Wno_int_in_bool_context = cpp.get_supported_arguments('-Wno-int-in-bool-context')
  274. cpp_args_pythran += [
  275. _cpp_Wno_cpp,
  276. _cpp_Wno_deprecated_declarations,
  277. _cpp_Wno_unused_but_set_variable,
  278. _cpp_Wno_unused_function,
  279. _cpp_Wno_unused_variable,
  280. _cpp_Wno_int_in_bool_context,
  281. ]
  282. # Fortran warning flags
  283. _fflag_Wno_argument_mismatch = ff.get_supported_arguments('-Wno-argument-mismatch')
  284. _fflag_Wno_conversion = ff.get_supported_arguments('-Wno-conversion')
  285. _fflag_Wno_intrinsic_shadow = ff.get_supported_arguments('-Wno-intrinsic-shadow')
  286. _fflag_Wno_maybe_uninitialized = ff.get_supported_arguments('-Wno-maybe-uninitialized')
  287. _fflag_Wno_surprising = ff.get_supported_arguments('-Wno-surprising')
  288. _fflag_Wno_uninitialized = ff.get_supported_arguments('-Wno-uninitialized')
  289. _fflag_Wno_unused_dummy_argument = ff.get_supported_arguments('-Wno-unused-dummy-argument')
  290. _fflag_Wno_unused_label = ff.get_supported_arguments('-Wno-unused-label')
  291. _fflag_Wno_unused_variable = ff.get_supported_arguments('-Wno-unused-variable')
  292. _fflag_Wno_tabs = ff.get_supported_arguments('-Wno-tabs')
  293. # The default list of warnings to ignore from Fortran code. There is a lot of
  294. # old, vendored code that is very bad and we want to compile it silently (at
  295. # least with GCC and Clang)
  296. fortran_ignore_warnings = ff.get_supported_arguments(
  297. _fflag_Wno_argument_mismatch,
  298. _fflag_Wno_conversion,
  299. _fflag_Wno_maybe_uninitialized,
  300. _fflag_Wno_unused_dummy_argument,
  301. _fflag_Wno_unused_label,
  302. _fflag_Wno_unused_variable,
  303. _fflag_Wno_tabs,
  304. )
  305. # Intel Fortran (ifort) does not run the preprocessor by default, if Fortran
  306. # code uses preprocessor statements, add this compile flag to it.
  307. _fflag_fpp = []
  308. if ff.get_id() == 'intel-cl'
  309. if is_windows
  310. _fflag_fpp = ff.get_supported_arguments('/fpp')
  311. else
  312. _fflag_fpp = ff.get_supported_arguments('-fpp')
  313. endif
  314. endif
  315. # Deal with M_PI & friends; add `use_math_defines` to c_args or cpp_args
  316. # Cython doesn't always get this right itself (see, e.g., gh-16800), so
  317. # explicitly add the define as a compiler flag for Cython-generated code.
  318. if is_windows
  319. use_math_defines = ['-D_USE_MATH_DEFINES']
  320. else
  321. use_math_defines = []
  322. endif
  323. # Determine whether it is necessary to link libatomic. This could be the case
  324. # e.g. on 32-bit platforms when atomic operations are used on 64-bit types.
  325. # The check is copied from Mesa <https://www.mesa3d.org/>.
  326. # Note that this dependency is not desired, it came in with a HiGHS update.
  327. # We should try to get rid of it. For discussion, see gh-17777.
  328. null_dep = dependency('', required : false)
  329. atomic_dep = null_dep
  330. code_non_lockfree = '''
  331. #include <stdint.h>
  332. int main() {
  333. struct {
  334. uint64_t *v;
  335. } x;
  336. return (int)__atomic_load_n(x.v, __ATOMIC_ACQUIRE) &
  337. (int)__atomic_add_fetch(x.v, (uint64_t)1, __ATOMIC_ACQ_REL);
  338. }
  339. '''
  340. if cc.get_id() != 'msvc'
  341. if not cc.links(
  342. code_non_lockfree,
  343. name : 'Check atomic builtins without -latomic'
  344. )
  345. atomic_dep = cc.find_library('atomic', required: false)
  346. if atomic_dep.found()
  347. # We're not sure that with `-latomic` things will work for all compilers,
  348. # so verify and only keep libatomic as a dependency if this works. It is
  349. # possible the build will fail later otherwise - unclear under what
  350. # circumstances (compilers, runtimes, etc.) exactly.
  351. if not cc.links(
  352. code_non_lockfree,
  353. dependencies: atomic_dep,
  354. name : 'Check atomic builtins with -latomic'
  355. )
  356. atomic_dep = null_dep
  357. endif
  358. endif
  359. endif
  360. endif
  361. # Suppress warning for deprecated Numpy API.
  362. # (Suppress warning messages emitted by #warning directives).
  363. # Replace with numpy_nodepr_api after Cython 3.0 is out
  364. cython_c_args += [_cpp_Wno_cpp, use_math_defines]
  365. cython_cpp_args = cython_c_args
  366. compilers = {
  367. 'C': cc,
  368. 'CPP': cpp,
  369. 'CYTHON': meson.get_compiler('cython'),
  370. 'FORTRAN': meson.get_compiler('fortran')
  371. }
  372. machines = {
  373. 'HOST': host_machine,
  374. 'BUILD': build_machine,
  375. }
  376. conf_data = configuration_data()
  377. # Set compiler information
  378. foreach name, compiler : compilers
  379. # conf_data.set(name + '_COMP_CMD_ARRAY', compiler.cmd_array())
  380. conf_data.set(name + '_COMP_CMD_ARRAY', compiler.get_id())
  381. conf_data.set(name + '_COMP', compiler.get_id())
  382. conf_data.set(name + '_COMP_LINKER_ID', compiler.get_linker_id())
  383. conf_data.set(name + '_COMP_VERSION', compiler.version())
  384. conf_data.set(name + '_COMP_CMD_ARRAY', ', '.join(compiler.cmd_array()))
  385. endforeach
  386. # Add `pythran` information if present
  387. if use_pythran
  388. conf_data.set('PYTHRAN_VERSION', pythran.version())
  389. conf_data.set('PYTHRAN_INCDIR', incdir_pythran)
  390. endif
  391. # Machines CPU and system information
  392. foreach name, machine : machines
  393. conf_data.set(name + '_CPU', machine.cpu())
  394. conf_data.set(name + '_CPU_FAMILY', machine.cpu_family())
  395. conf_data.set(name + '_CPU_ENDIAN', machine.endian())
  396. conf_data.set(name + '_CPU_SYSTEM', machine.system())
  397. endforeach
  398. conf_data.set('CROSS_COMPILED', meson.is_cross_build())
  399. # Python information
  400. conf_data.set('PYTHON_PATH', py3.full_path())
  401. conf_data.set('PYTHON_VERSION', py3.language_version())
  402. # Dependencies information
  403. foreach name, dep : dependency_map
  404. conf_data.set(name + '_NAME', dep.name())
  405. conf_data.set(name + '_FOUND', dep.found())
  406. if dep.found()
  407. conf_data.set(name + '_VERSION', dep.version())
  408. conf_data.set(name + '_TYPE_NAME', dep.type_name())
  409. conf_data.set(name + '_INCLUDEDIR', dep.get_variable('includedir', default_value: 'unknown'))
  410. conf_data.set(name + '_LIBDIR', dep.get_variable('libdir', default_value: 'unknown'))
  411. conf_data.set(name + '_OPENBLAS_CONFIG', dep.get_variable('openblas_config', default_value: 'unknown'))
  412. conf_data.set(name + '_PCFILEDIR', dep.get_variable('pcfiledir', default_value: 'unknown'))
  413. endif
  414. endforeach
  415. configure_file(
  416. input: '__config__.py.in',
  417. output: '__config__.py',
  418. configuration : conf_data,
  419. install_dir: scipy_dir,
  420. )
  421. # Ordering of subdirs: special and linalg come first, because other submodules
  422. # have dependencies on cython_special.pxd and cython_linalg.pxd. After those,
  423. # subdirs with the most heavy builds should come first (that parallelizes
  424. # better)
  425. subdir('_lib')
  426. subdir('special')
  427. subdir('linalg')
  428. subdir('sparse')
  429. subdir('stats')
  430. subdir('fft')
  431. subdir('spatial')
  432. subdir('cluster')
  433. subdir('constants')
  434. subdir('fftpack')
  435. subdir('integrate')
  436. subdir('signal')
  437. subdir('interpolate')
  438. subdir('ndimage')
  439. subdir('odr')
  440. subdir('optimize')
  441. subdir('datasets')
  442. subdir('misc')
  443. subdir('io')