2525import time
2626
2727import distutils
28- from distutils .core import Extension
28+ from distutils .core import Extension , Distribution
29+ from distutils .command .build_clib import build_clib
2930
3031import Cython
3132from Cython .Build .Inline import _get_build_extension
3940
4041logger = logging .getLogger ('pystan' )
4142
43+ def _build_libraries (self , libraries ):
44+ """Fork distutils.build_clib.build_libraries to enable compiler flags."""
45+ for (lib_name , build_info ) in libraries :
46+ sources = build_info .get ('sources' )
47+ if sources is None or not isinstance (sources , (list , tuple )):
48+ raise DistutilsSetupError (
49+ "in 'libraries' option (library '%s'), "
50+ "'sources' must be present and must be "
51+ "a list of source filenames" % lib_name )
52+ sources = list (sources )
53+
54+ distutils .command .build_clib .log .info ("building '%s' library" , lib_name )
55+
56+ macros = build_info .get ('macros' )
57+ include_dirs = build_info .get ('include_dirs' )
58+ extra_postargs = build_info .get ('extra_postargs' )
59+ objects = self .compiler .compile (sources ,
60+ output_dir = self .build_temp ,
61+ macros = macros ,
62+ include_dirs = include_dirs ,
63+ extra_postargs = extra_postargs ,
64+ debug = self .debug )
65+
66+ self .compiler .create_static_lib (objects , lib_name ,
67+ output_dir = self .build_clib ,
68+ debug = self .debug )
69+
70+ # overwrite the default class method
71+ build_clib .build_libraries = _build_libraries
72+
73+ def _get_build_clib ():
74+ """Convenience function to create build_clib class instance."""
75+ dist = Distribution ()
76+ config_files = dist .find_config_files ()
77+ dist .parse_config_files (config_files )
78+ build_clibrary = build_clib (dist )
79+ build_clibrary .finalize_options ()
80+ return build_clibrary
81+
82+ def _build_clib (sources , include_dirs_c , lib_dir , extra_args ):
83+ """Build C-library."""
84+ libraries = [("libsundials" , {
85+ "sources" : sources ,
86+ "include_dirs" : include_dirs_c ,
87+ "extra_postargs" : extra_args ,
88+ })]
89+
90+ build_clibrary = _get_build_clib ()
91+ build_clibrary .libraries = libraries
92+ build_clibrary .include_dirs = include_dirs_c
93+ build_clibrary .build_temp = lib_dir
94+ build_clibrary .build_clib = lib_dir
95+ build_clibrary .run ()
96+
97+ objects = []
98+ for _ , build_info in libraries :
99+ obj = build_clibrary .compiler .object_filenames (build_info ["sources" ], output_dir = lib_dir )
100+ objects .extend (obj )
101+
102+ return objects
42103
43104def load_module (module_name , module_path ):
44105 """Load the module named `module_name` from `module_path`
@@ -309,58 +370,95 @@ def __init__(self, file=None, charset='utf-8', model_name="anon_model",
309370 s = template .safe_substitute (model_cppname = self .model_cppname )
310371 outfile .write (s )
311372
312- ## cvodes sources
373+ if extra_compile_args is None :
374+ extra_compile_args = []
375+
376+ distutils .log .set_verbosity (verbose )
377+ build_extension = _get_build_extension ()
378+ ## sundials sources
313379
314- # cvodes sources are complied and linked together with the Stan model
380+ # sundials sources are compiled and linked together with the Stan model
315381 # extension module. This is not ideal. In theory, build_clib could be
316382 # used to build a library once and models would be complied and then
317383 # linked with this library. This would save 7 or more seconds from every build.
318384 # But such a strategy is frustrated by the
319385 # lack of ``install_clib`` functionality in Python's distutils.
320386 #
321387 # TODO: numpy provides install_clib functionality, use that.
322- cvodes_src_path = os .path .join (pystan_dir , 'stan' , 'lib' , 'stan_math' , 'lib' , 'cvodes_2.9.0' , 'src' )
323- cvodes_sources = [
324- os .path .join (cvodes_src_path , 'cvodes' , 'cvodea.c' ),
325- os .path .join (cvodes_src_path , 'cvodes' , 'cvodea_io.c' ),
326- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes.c' ),
327- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_band.c' ),
328- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_bandpre.c' ),
329- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_bbdpre.c' ),
330- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_dense.c' ),
331- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_diag.c' ),
332- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_direct.c' ),
333- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_io.c' ),
334- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_sparse.c' ),
335- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_spbcgs.c' ),
336- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_spgmr.c' ),
337- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_spils.c' ),
338- os .path .join (cvodes_src_path , 'cvodes' , 'cvodes_sptfqmr.c' ),
339- os .path .join (cvodes_src_path , 'nvec_ser' , 'nvector_serial.c' ),
340- os .path .join (cvodes_src_path , 'sundials' , 'sundials_band.c' ),
341- os .path .join (cvodes_src_path , 'sundials' , 'sundials_dense.c' ),
342- os .path .join (cvodes_src_path , 'sundials' , 'sundials_direct.c' ),
343- os .path .join (cvodes_src_path , 'sundials' , 'sundials_iterative.c' ),
344- os .path .join (cvodes_src_path , 'sundials' , 'sundials_math.c' ),
345- os .path .join (cvodes_src_path , 'sundials' , 'sundials_nvector.c' ),
346- os .path .join (cvodes_src_path , 'sundials' , 'sundials_spbcgs.c' ),
347- os .path .join (cvodes_src_path , 'sundials' , 'sundials_spgmr.c' ),
348- os .path .join (cvodes_src_path , 'sundials' , 'sundials_sptfqmr.c' ),
388+
389+ sundials_excluded = {
390+ "nvector/openmp" ,
391+ "nvector/openmpdev" ,
392+ "nvector/parallel" ,
393+ "nvector/parhyp" ,
394+ "nvector/petsc" ,
395+ "nvector/pthreads" ,
396+ "sundials_mpi" ,
397+ "sunlinsol/klu" ,
398+ "sunlinsol/lapack" ,
399+ "sunlinsol/super" ,
400+ }
401+
402+ sundials_src_path = os .path .join (pystan_dir , 'stan' , 'lib' , 'stan_math' , 'lib' , 'sundials_4.1.0' , 'src' )
403+ sundials_sources = []
404+ for sun_root , _ , sunfiles in os .walk (sundials_src_path ):
405+ for sunfile in sunfiles :
406+ if os .path .splitext (sunfile )[1 ] == ".c" :
407+ path = os .path .join (sun_root , sunfile ).replace ("\\ " , "/" )
408+ if not any (item in path for item in sundials_excluded ):
409+ sundials_sources .append (path )
410+
411+ include_dirs_c = [
412+ lib_dir ,
413+ os .path .join (pystan_dir , "stan" , "lib" , "stan_math" , "lib" , "sundials_4.1.0" , "include" ),
349414 ]
350415
416+ extra_compile_args_c = []
417+ if platform .system () == 'Windows' :
418+ if build_extension .compiler in (None , 'msvc' ):
419+ logger .warning ("MSVC compiler is not supported" )
420+ extra_compile_args_c .extend ([
421+ '/EHsc' ,
422+ '-DBOOST_DATE_TIME_NO_LIB'
423+ ])
424+ else :
425+ # Windows, but not msvc, likely mingw
426+ # fix bug in MingW-W64
427+ # use posix threads
428+ extra_compile_args_c .extend ([
429+ '-O2' ,
430+ '-Wno-unused-function' ,
431+ '-Wno-uninitialized' ,
432+ '-std=c11' ,
433+ "-D_hypot=hypot" ,
434+ "-pthread" ,
435+ "-fexceptions" ,
436+ "-include" ,
437+ "stan_sundials_printf_override.hpp" ,
438+ ] + [item for item in extra_compile_args if "std=c++" not in extra_compile_args ])
439+ else :
440+ # linux or macOS
441+ extra_compile_args_c .extend ([
442+ '-O2' ,
443+ '-Wno-unused-function' ,
444+ '-Wno-uninitialized' ,
445+ '-std=c11' ,
446+ "-include" ,
447+ "stan_sundials_printf_override.hpp" ,
448+ ] + [item for item in extra_compile_args if "std=c++" not in extra_compile_args ])
449+
450+ sundials_objects = _build_clib (sundials_sources , include_dirs_c , lib_dir , extra_compile_args_c )
451+
351452 stan_macros = [
352453 ('BOOST_RESULT_OF_USE_TR1' , None ),
353454 ('BOOST_NO_DECLTYPE' , None ),
354455 ('BOOST_DISABLE_ASSERTS' , None ),
355456 ]
356457
357- build_extension = _get_build_extension ()
358458 # compile stan models with optimization (-O2)
359459 # (stanc is compiled without optimization (-O0) currently, see #33)
360- if extra_compile_args is None :
361- extra_compile_args = []
362460
363- if platform .platform (). startswith ( 'Win' ) :
461+ if platform .system () == 'Windows' :
364462 if build_extension .compiler in (None , 'msvc' ):
365463 logger .warning ("MSVC compiler is not supported" )
366464 extra_compile_args = [
@@ -381,6 +479,8 @@ def __init__(self, file=None, charset='utf-8', model_name="anon_model",
381479 "-D_hypot=hypot" ,
382480 "-pthread" ,
383481 "-fexceptions" ,
482+ "-include" ,
483+ "stan_sundials_printf_override.hpp" ,
384484 ] + extra_compile_args
385485 else :
386486 # linux or macOS
@@ -390,15 +490,18 @@ def __init__(self, file=None, charset='utf-8', model_name="anon_model",
390490 '-Wno-unused-function' ,
391491 '-Wno-uninitialized' ,
392492 '-std=c++1y' ,
493+ '-include' ,
494+ 'stan_sundials_printf_override.hpp' ,
393495 ] + extra_compile_args
394496
395- distutils .log .set_verbosity (verbose )
396497 extension = Extension (name = self .module_name ,
397498 language = "c++" ,
398- sources = [pyx_file ] + cvodes_sources ,
499+ sources = [pyx_file ],
399500 define_macros = stan_macros ,
400501 include_dirs = include_dirs ,
401- extra_compile_args = extra_compile_args )
502+ extra_objects = sundials_objects ,
503+ extra_compile_args = extra_compile_args
504+ )
402505
403506 cython_include_dirs = ['.' , pystan_dir ]
404507
0 commit comments