diff --git a/matlab/add_path_to_mex_files.m b/matlab/add_path_to_mex_files.m index 97548b1ea..165753f9b 100644 --- a/matlab/add_path_to_mex_files.m +++ b/matlab/add_path_to_mex_files.m @@ -21,7 +21,14 @@ if nargin<2 modifypath = true; end -if isoctave +build_dir = get_build_dir(dynareroot); +if ~isempty(build_dir) + % If a Meson build directory is found, use it preferably + mexpath = { build_dir }; + if modifypath + addpath(build_dir) + end +elseif isoctave % Add specific paths for Dynare Windows package if ispc if strcmpi(computer(), 'i686-w64-mingw32') diff --git a/matlab/dynare.m b/matlab/dynare.m index 42814cb4a..c6f960c13 100644 --- a/matlab/dynare.m +++ b/matlab/dynare.m @@ -201,7 +201,15 @@ if preprocessoroutput end end -command = ['"' dynareroot '..' filesep 'preprocessor' filesep 'dynare-preprocessor" ' fname] ; +build_dir = get_build_dir(dynareroot); +if isempty(build_dir) + preprocessor_dir = [ dynareroot '..' filesep 'preprocessor' ]; +else + disp(['Using build directory: ' build_dir ]) + preprocessor_dir = [ build_dir filesep 'preprocessor' filesep 'src' ]; +end + +command = ['"' preprocessor_dir filesep 'dynare-preprocessor" ' fname]; command = [ command ' mexext=' mexext ' "matlabroot=' matlabroot '"']; % Properly quote arguments before passing them to the shell if ~isempty(varargin) diff --git a/matlab/get_build_dir.m b/matlab/get_build_dir.m new file mode 100644 index 000000000..aa0e39ae3 --- /dev/null +++ b/matlab/get_build_dir.m @@ -0,0 +1,36 @@ +function p = get_build_dir(dynareroot) +% Returns a Meson build directory if one is found. +% Otherwise returns an empty value. + +% Copyright © 2023 Dynare Team +% +% This file is part of Dynare. +% +% Dynare is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% Dynare is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Dynare. If not, see . + +envvar = getenv('DYNARE_BUILD_DIR'); +default_matlab = [ dynareroot '..' filesep 'build-matlab' ]; +default_octave = [ dynareroot '..' filesep 'build-octave' ]; + +if ~isempty(envvar) + p = envvar; +elseif ~isoctave && exist(default_matlab, 'dir') + p = default_matlab; +elseif isoctave && exist(default_octave, 'dir') + p = default_octave; +else + p = []; +end + +end diff --git a/meson.build b/meson.build new file mode 100644 index 000000000..e56717215 --- /dev/null +++ b/meson.build @@ -0,0 +1,500 @@ +# TODO: +# - move to fortran_std=f2018; this requires changes in libkordersim, and also replacing isnan() by ieee_is_nan() in various MEX (or use -fall-intrinsics) +# - use buildtype=debugoptimized by default? (and in the preprocessor too) +# - with -Dprefer_static=true, under Octave/Windows, we are now linking the compiler libs (libgcc, libstdc++, libgfortran, libquadmath, libssp, libgomp) statically (contrary to what we were doing with autotools). In theory this is better, because the compiler used for creating the Octave binary may be different from the one used for creating our MEX. Check that this is ok +# - configuration option to disable documentation +# - configuration option to disable preprocessor build +# - add -Wold-style-cast C++ flag except when building flex-generated files +# - determine minimal meson version required, and declare it in the project() function (here and the preprocessor); if possible at low cost, diminish the requirement. NB: Ubuntu “jammy” 22.04 has 0.61, Debian “bullseye” 11 has 0.56 (but bullseye-backports has 1.0), Ubuntu ”focal” 20.04 has 0.53 + +project('dynare', + 'cpp', 'fortran', 'c', + version : '6-unstable', + default_options : [ 'cpp_std=gnu++20', 'fortran_std=none', 'c_std=gnu17', 'warning_level=2' ]) + +add_global_arguments('-Wimplicit-interface', '-Wno-compare-reals', language : 'fortran') +add_global_arguments('-DPACKAGE_VERSION="' + meson.project_version() + '"', language : 'cpp') + +cpp_compiler = meson.get_compiler('cpp') +fortran_compiler = meson.get_compiler('fortran') +c_compiler = meson.get_compiler('c') + +### Preprocessor + +subdir('preprocessor/src') +subdir('preprocessor/doc') + +### Generated M-file + +sed_exe = find_program('sed') +custom_target(output : 'dynare_version.m', input : 'matlab/dynare_version.m.in', + command : [ sed_exe, 's/@PACKAGE_VERSION@/' + meson.project_version() + '/', '@INPUT@' ], + capture : true, + build_by_default : true) # FIXME: This option can be removed when “install” is set to true + +### MEX files + +mex_incdir = include_directories('mex/sources') + +## Various dependencies + +if host_machine.system() != 'windows' + dl_dep = dependency('dl') +else + # Under Windows, we don’t use dlopen but rely on LoadLibrary + dl_dep = [] +endif + +openmp_dep = dependency('openmp') +gsl_dep = dependency('gsl') + +if get_option('build_for') == 'octave' + matio_dep = dependency('matio') +else + # We don’t use MatIO under MATLAB + matio_dep = [] +endif + +pthread_t_sizeof = c_compiler.sizeof('pthread_t', prefix : '#include ') +# TODO: when requiring meson ⩾ 1.2, incorporate pthread.F08 into the dependency object with “extra_files” option +# (and thus remove the pthread_fortran_iface variable) +pthread_fortran_dep = declare_dependency(compile_args : '-DSIZEOF_PTHREAD_T=' + pthread_t_sizeof.to_string()) + +## Determine MEX compilation options + +if get_option('build_for') == 'matlab' + matlab_path = get_option('matlab_path') + if matlab_path == '' + error('The “matlab_path” option must be provided when doing a MATLAB build') + endif + matlab_version = run_command('scripts/get-matlab-version', matlab_path, check : true).stdout().strip() + matlab_minimal_version = [ '8.3', 'R2014a' ] + + if matlab_version.version_compare('<' + matlab_minimal_version[0]) + error('MATLAB is too old (version ' + matlab_version + '), please upgrade to version ' + matlab_minimal_version[0] + ' (' + matlab_minimal_version[1] + ') at least.') + endif + + matlab_version_hex = run_command('scripts/get-matlab-version', '--hex', matlab_path, check : true).stdout().strip() + matlab_exe = find_program(matlab_path / 'bin' / 'matlab', required : not meson.is_cross_build()) + + if host_machine.system() == 'linux' and host_machine.cpu_family() == 'x86_64' + mexext = 'mexa64' + matlab_arch = 'glnxa64' + export_file = matlab_path / 'extern/lib/glnxa64/mexFunction.map' + export_link_arg = '-Wl,--version-script,' + export_file + elif host_machine.system() == 'windows' and host_machine.cpu_family() == 'x86_64' + mexext = 'mexw64' + matlab_arch = 'win64' + export_file = meson.current_source_dir() / 'mex/build/matlab/mex.def' + export_link_arg = export_file + if get_option('build_for') == 'matlab' + arch_fortran_args = [ '-fno-underscoring' ] + endif + elif host_machine.system() == 'darwin' + if host_machine.cpu_family() == 'x86_64' + mexext = 'mexmaci64' + matlab_arch = 'maci64' + elif host_machine.cpu_family() == 'aarch64' + mexext = 'mexmaca64' + matlab_arch = 'maca64' + else + error('Unsupported platform') + endif + export_file = meson.current_source_dir() / 'mex/build/matlab/mexFunction-MacOSX.map' + export_link_arg = '-Wl,-exported_symbols_list,' + export_file + else + error('Unsupported platform') + endif + + matlab_defs = [ '-DMATLAB_MEX_FILE', '-DMATLAB_VERSION=' + matlab_version_hex, '-DMEXEXT="' + mexext + '"' ] + matlab_incdir = include_directories(matlab_path / 'extern/include') + + mex_kwargs = { 'name_prefix' : '', + 'name_suffix' : mexext, + 'include_directories' : [ mex_incdir, matlab_incdir ], + 'cpp_args' : matlab_defs, + 'fortran_args' : matlab_defs + [ '-fexceptions' ] + get_variable('arch_fortran_args', []), + 'c_args' : matlab_defs + [ '-fexceptions' ], + 'link_args' : [ export_link_arg, '-L' + (matlab_path / 'bin' / matlab_arch), '-lmx', '-lmex', '-lmat' ], + 'link_depends' : export_file } + + # For unit tests + exe_rpath = matlab_path / 'bin' / matlab_arch + exe_link_args = [ '-L' + exe_rpath, '-lmx', '-lmex', '-lmat' ] + + # No need to use find_library() for the following libraries, since they are always shipped with MATLAB + blas_dep = declare_dependency(link_args : '-lmwblas') + lapack_dep = declare_dependency(link_args : '-lmwlapack', dependencies : blas_dep) + umfpack_dep = declare_dependency(link_args : '-lmwumfpack', dependencies : blas_dep) + ut_dep = declare_dependency(link_args : '-lut') + + slicot_dep = declare_dependency(dependencies : [ fortran_compiler.find_library('slicot64_pic'), blas_dep, lapack_dep ]) +else # Octave build + octave_exe = find_program('octave', required : not meson.is_cross_build()) + mkoctfile_exe = find_program('mkoctfile') + octave_minimal_version = '6.2.0' + octave_version = run_command(mkoctfile_exe, '-v', check : true).stdout().replace('mkoctfile, version ', '') + + if octave_version.version_compare('<' + octave_minimal_version) + error('Octave is too old (version ' + octave_version + '), please upgrade to version ' + octave_minimal_version + ' at least.') + endif + + octave_incflags = run_command(mkoctfile_exe, '-p', 'INCFLAGS', check : true).stdout().split() + octlibdir = run_command(mkoctfile_exe, '-p', 'OCTLIBDIR', check : true).stdout().strip() + + # Determine whether to link MEX files against the Octave libraries. mkoctfile + # no longer does this by default but in practice it is needed for Windows and + # macOS. + octave_libs = run_command(mkoctfile_exe, '-p', 'OCTAVE_LIBS', check : true).stdout().split() + if host_machine.system() == 'windows' or host_machine.system() == 'darwin' + # Under Windows, --enable-link-all-dependencies is hardcoded in src/mkoctfile.cc.in. + # Under macOS, the Homebrew formula passes --enable-link-all-dependencies + # to the configure script. + octave_link_args = [ '-L' + octlibdir ] + octave_libs + else + octave_link_args = [] + endif + + # For unit tests + exe_rpath = octlibdir + exe_link_args = [ '-L' + octlibdir ] + octave_libs + + octave_defs = [ '-DOCTAVE_MEX_FILE', '-DMEXEXT="mex"' ] + + mex_kwargs = { 'name_prefix' : '', + 'name_suffix' : 'mex', + 'include_directories' : [ mex_incdir ], + 'cpp_args' : octave_incflags + octave_defs, + 'fortran_args' : octave_incflags + octave_defs, + 'c_args' : octave_incflags + octave_defs, + 'link_args' : octave_link_args} + + # The -L argument is useful when cross-compiling. + blas_dep = declare_dependency(link_args : [ '-L' + (octlibdir / '../..') ] + run_command(mkoctfile_exe, '-p', 'BLAS_LIBS', check : true).stdout().split()) + lapack_dep = declare_dependency(link_args : run_command(mkoctfile_exe, '-p', 'LAPACK_LIBS', check : true).stdout().split(), + dependencies : blas_dep) + + # Create a dependency object for UMFPACK. + # The dependency returned by find_library('umfpack') is not enough, because we also want the define + # that indicates the location of umfpack.h, so we construct a new dependency object. + if cpp_compiler.has_header('suitesparse/umfpack.h', args : octave_incflags) + umfpack_def = '-DHAVE_SUITESPARSE_UMFPACK_H' + elif cpp_compiler.has_header('umfpack.h', args : octave_incflags) + umfpack_def = '-DHAVE_UMFPACK_H' + else + error('Can’t find umfpack.h') + endif + # Do not enforce static linking even if prefer_static is true, since that library is shipped + # with Octave. + # The “dirs” argument is useful when cross-compiling. + umfpack_dep_tmp = cpp_compiler.find_library('umfpack', dirs : octlibdir / '../..', static : false) + umfpack_dep = declare_dependency(compile_args : umfpack_def, dependencies : [ umfpack_dep_tmp, blas_dep ]) + + # This library does not exist under Octave + ut_dep = [] + + # First look for libslicot, then if needed fallback on libslicot_pic + slicot_dep_tmp = fortran_compiler.find_library('slicot', required : false) + if not slicot_dep_tmp.found() + slicot_dep_tmp = fortran_compiler.find_library('slicot_pic') + endif + slicot_dep = declare_dependency(dependencies : [ slicot_dep_tmp, blas_dep, lapack_dep ]) +endif + +# When static linking is preferred, try to statically link the compiler libraries +if get_option('prefer_static') + static_flags_pre = [ '-static-libgcc', '-static-libstdc++' ] + static_flags_post = [] + if host_machine.system() == 'windows' + # Under Debian 12, libgfortran.a and libquadmath.a are not compiled with -fPIC, so can’t be linked in a MEX. + # Under macOS, -static-libgcc implies -static-libgfortran (see gfortran -dumpspecs), and for libquadmath + # we use a hack with a local copy of libquadmath.a. + # -static-libquadmath was introduced in GCC 13. Until we require the latter, use a hack. + static_flags_pre += [ '-static-libgfortran', '-Wl,-Bstatic,--whole-archive', '-lquadmath', '-Wl,-Bdynamic,--no-whole-archive' ] + + # Hack to avoid dynamically linking against libwinpthread DLL (which is + # pulled in by libstdc++, even without using threads, since we are using + # the POSIX threads version of MinGW). + static_flags_pre += [ '-Wl,-Bstatic,--whole-archive', '-lwinpthread', '-Wl,-Bdynamic,--no-whole-archive' ] + + # Hack for libssp, which is pulled in by -fstack-protector (curiously only + # on some MEX files), see windows/build.sh. Note that the link against + # libssp should not happen with compilers from MSYS2, see: + # https://www.msys2.org/news/#2022-10-10-libssp-is-no-longer-required + # But it happens with Debian’s cross compilers (as of Debian “bookworm” + # 12). Also note that the -lssp must come by the end of the link command + # (otherwise it will have to be enclosed within --whole-archive). + static_flags_post = [ '-Wl,-Bstatic', '-lssp', '-Wl,-Bdynamic' ] + endif + + mex_kwargs += { 'link_args' : static_flags_pre + mex_kwargs.get('link_args', []) + static_flags_post } + + # NB: constructing a dependency object with link_args : ['-Wl,-Bstatic', '-lgomp', '-Wl,-Bdynamic'] does not work, + # because it reorders the three arguments and puts -lgomp at the end + openmp_dep_tmp = cpp_compiler.find_library('gomp', static : true) + openmp_dep = declare_dependency(dependencies : [ openmp_dep, openmp_dep_tmp ]) +endif + +# For use when creating intermediate static libraries to be incorporated in MEX files +static_library_kwargs = mex_kwargs + { 'name_prefix' : [], 'name_suffix' : [], 'link_args' : [], 'pic' : true } + +mex_blas_fortran_iface = [ 'mex/sources/matlab_mex.F08', 'mex/sources/blas_lapack.F08' ] +pthread_fortran_iface = [ 'mex/sources/pthread.F08' ] + + +## Various core MEX + +shared_module('mjdgges', [ 'mex/sources/mjdgges/mjdgges.F08' ] + mex_blas_fortran_iface, kwargs : mex_kwargs, dependencies : lapack_dep) + +shared_module('num_procs', 'mex/sources/num_procs/num_procs.cc', kwargs : mex_kwargs) + +perfect_foresight_problem_src = [ 'mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc', + 'mex/sources/perfect_foresight_problem/DynamicModelCaller.cc' ] +shared_module('perfect_foresight_problem', perfect_foresight_problem_src, kwargs : mex_kwargs, dependencies : openmp_dep) + +block_trust_region_src = [ 'mex/sources/block_trust_region/dulmage_mendelsohn.f08', + 'mex/sources/block_trust_region/matlab_fcn_closure.F08', + 'mex/sources/block_trust_region/trust_region.f08', + 'mex/sources/block_trust_region/mexFunction.f08' ] + mex_blas_fortran_iface +shared_module('block_trust_region', block_trust_region_src, kwargs : mex_kwargs, dependencies : lapack_dep) + +bytecode_src = [ 'mex/sources/bytecode/bytecode.cc', + 'mex/sources/bytecode/Interpreter.cc', + 'mex/sources/bytecode/Mem_Mngr.cc', + 'mex/sources/bytecode/SparseMatrix.cc', + 'mex/sources/bytecode/Evaluate.cc', + 'mex/sources/bytecode/BasicSymbolTable.cc' ] +preprocessor_headers_dep = declare_dependency(include_directories : include_directories('preprocessor/src')) +shared_module('bytecode', bytecode_src, kwargs : mex_kwargs, dependencies : [ umfpack_dep, ut_dep, preprocessor_headers_dep ]) + +shared_module('sparse_hessian_times_B_kronecker_C', 'mex/sources/kronecker/sparse_hessian_times_B_kronecker_C.cc', + kwargs : mex_kwargs, dependencies : openmp_dep) + +# TODO: A_times_B_kronecker_C does not depend on LAPACK, but since the +# interfaces to both BLAS and LAPACK are in the same source file, the +# dependency must be added. Think about splitting into two files. +# NB: The problem does not appear with libkordersim because since it is a library, +# the LAPACK stuff is never pulled in. +shared_module('A_times_B_kronecker_C', [ 'mex/sources/kronecker/A_times_B_kronecker_C.f08' ] + mex_blas_fortran_iface, + kwargs : mex_kwargs, dependencies : [ blas_dep, lapack_dep ]) + +shared_module('cycle_reduction', [ 'mex/sources/cycle_reduction/mexFunction.f08' ] + mex_blas_fortran_iface, + kwargs : mex_kwargs, dependencies : [ blas_dep, lapack_dep ]) + +shared_module('logarithmic_reduction', [ 'mex/sources/logarithmic_reduction/mexFunction.f08' ] + mex_blas_fortran_iface, + kwargs : mex_kwargs, dependencies : [ blas_dep, lapack_dep ]) + +shared_module('disclyap_fast', [ 'mex/sources/disclyap_fast/disclyap_fast.f08' ] + mex_blas_fortran_iface, + kwargs : mex_kwargs, dependencies : [ blas_dep, lapack_dep ]) + +# TODO: Same remark as A_times_B_kronecker_C +shared_module('riccati_update', [ 'mex/sources/riccati_update/mexFunction.f08' ] + mex_blas_fortran_iface, + kwargs : mex_kwargs, dependencies : [ blas_dep, lapack_dep ]) + +qmc_sequence_src = [ 'mex/sources/sobol/qmc_sequence.cc', + 'mex/sources/sobol/sobol.f08' ] +# Hack for statically linking libgfortran +# Since qmc_sequence is a mix of C++ and Fortran, the linker invoked is the C++ one. +# Meson then rightly adds -lgfortran, but the -static-libgfortran flag does not work. +qmc_sequence_mex_kwargs = mex_kwargs +if get_option('prefer_static') and host_machine.system() == 'windows' + qmc_sequence_mex_kwargs += { 'link_args' : qmc_sequence_mex_kwargs.get('link_args') + [ '-Wl,-Bstatic', '-lgfortran', '-Wl,-Bdynamic' ] } +endif +shared_module('qmc_sequence', qmc_sequence_src, kwargs : qmc_sequence_mex_kwargs, dependencies : [ blas_dep, openmp_dep ]) + +shared_module('kalman_steady_state', 'mex/sources/kalman_steady_state/kalman_steady_state.cc', kwargs : mex_kwargs, dependencies : slicot_dep) + + +## k-order simulation stuff + +kordersim_src = [ 'mex/sources/libkordersim/pascal.f08', + 'mex/sources/libkordersim/sort.f08', + 'mex/sources/libkordersim/partitions.f08', + 'mex/sources/libkordersim/tensors.f08', + 'mex/sources/libkordersim/simulation.f08', + 'mex/sources/libkordersim/struct.f08' ] + mex_blas_fortran_iface + pthread_fortran_iface +kordersim_lib = static_library('kordersim', kordersim_src, kwargs : static_library_kwargs, dependencies : [ blas_dep, pthread_fortran_dep ]) + +shared_module('folded_to_unfolded_dr', 'mex/sources/folded_to_unfolded_dr/mexFunction.f08', kwargs : mex_kwargs, link_with : kordersim_lib) +shared_module('k_order_mean', 'mex/sources/k_order_mean/mexFunction.f08', kwargs : mex_kwargs, link_with : kordersim_lib) +shared_module('k_order_simul', 'mex/sources/k_order_simul/mexFunction.f08', kwargs : mex_kwargs, link_with : kordersim_lib) + +shared_module('local_state_space_iteration_2', 'mex/sources/local_state_space_iterations/local_state_space_iteration_2.cc', kwargs : mex_kwargs, dependencies : openmp_dep) +shared_module('local_state_space_iteration_3', 'mex/sources/local_state_space_iterations/local_state_space_iteration_3.f08', kwargs : mex_kwargs, link_with : kordersim_lib) +shared_module('local_state_space_iteration_k', 'mex/sources/local_state_space_iterations/local_state_space_iteration_k.f08', kwargs : mex_kwargs, link_with : kordersim_lib) + + +## k-order resolution stuff + +korder_src = [ 'mex/sources/libkorder/kord/approximation.cc', + 'mex/sources/libkorder/kord/decision_rule.cc', + 'mex/sources/libkorder/kord/dynamic_model.cc', + 'mex/sources/libkorder/kord/faa_di_bruno.cc', + 'mex/sources/libkorder/kord/first_order.cc', + 'mex/sources/libkorder/kord/korder.cc', + 'mex/sources/libkorder/kord/korder_stoch.cc', + 'mex/sources/libkorder/kord/journal.cc', + 'mex/sources/libkorder/sylv/BlockDiagonal.cc', + 'mex/sources/libkorder/sylv/GeneralMatrix.cc', + 'mex/sources/libkorder/sylv/GeneralSylvester.cc', + 'mex/sources/libkorder/sylv/IterativeSylvester.cc', + 'mex/sources/libkorder/sylv/KronUtils.cc', + 'mex/sources/libkorder/sylv/KronVector.cc', + 'mex/sources/libkorder/sylv/QuasiTriangular.cc', + 'mex/sources/libkorder/sylv/QuasiTriangularZero.cc', + 'mex/sources/libkorder/sylv/SchurDecomp.cc', + 'mex/sources/libkorder/sylv/SchurDecompEig.cc', + 'mex/sources/libkorder/sylv/SimilarityDecomp.cc', + 'mex/sources/libkorder/sylv/SylvException.cc', + 'mex/sources/libkorder/sylv/SylvMatrix.cc', + 'mex/sources/libkorder/sylv/SylvParams.cc', + 'mex/sources/libkorder/sylv/SymSchurDecomp.cc', + 'mex/sources/libkorder/sylv/TriangularSylvester.cc', + 'mex/sources/libkorder/sylv/Vector.cc', + 'mex/sources/libkorder/tl/equivalence.cc', + 'mex/sources/libkorder/tl/fine_container.cc', + 'mex/sources/libkorder/tl/fs_tensor.cc', + 'mex/sources/libkorder/tl/gs_tensor.cc', + 'mex/sources/libkorder/tl/int_sequence.cc', + 'mex/sources/libkorder/tl/kron_prod.cc', + 'mex/sources/libkorder/tl/normal_moments.cc', + 'mex/sources/libkorder/tl/permutation.cc', + 'mex/sources/libkorder/tl/ps_tensor.cc', + 'mex/sources/libkorder/tl/pyramid_prod.cc', + 'mex/sources/libkorder/tl/pyramid_prod2.cc', + 'mex/sources/libkorder/tl/rfs_tensor.cc', + 'mex/sources/libkorder/tl/sparse_tensor.cc', + 'mex/sources/libkorder/tl/stack_container.cc', + 'mex/sources/libkorder/tl/symmetry.cc', + 'mex/sources/libkorder/tl/t_container.cc', + 'mex/sources/libkorder/tl/t_polynomial.cc', + 'mex/sources/libkorder/tl/tensor.cc', + 'mex/sources/libkorder/tl/tl_static.cc', + 'mex/sources/libkorder/tl/twod_matrix.cc', + 'mex/sources/libkorder/utils/pascal_triangle.cc', + 'mex/sources/libkorder/utils/int_power.cc', + 'mex/sources/libkorder/utils/sthread.cc', + 'mex/sources/libkorder/k_ord_dynare.cc', + 'mex/sources/libkorder/dynamic_dll.cc', + 'mex/sources/libkorder/dynamic_m.cc' ] +korder_incdir = include_directories('mex/sources/libkorder', 'mex/sources/libkorder/tl', 'mex/sources/libkorder/sylv', + 'mex/sources/libkorder/kord', 'mex/sources/libkorder/utils') +korder_lib = static_library('korder', korder_src, + kwargs : static_library_kwargs + { 'include_directories' : static_library_kwargs.get('include_directories') + korder_incdir}, + dependencies : [ blas_dep, lapack_dep, dl_dep ]) + +korder_mex_kwargs = mex_kwargs + { 'include_directories' : mex_kwargs.get('include_directories') + korder_incdir} +shared_module('gensylv', 'mex/sources/gensylv/gensylv.cc', kwargs : korder_mex_kwargs, link_with : korder_lib) +shared_module('k_order_perturbation', 'mex/sources/gensylv/gensylv.cc', kwargs : korder_mex_kwargs, link_with : korder_lib) +k_order_welfare_src = [ 'mex/sources/k_order_welfare/k_order_welfare.cc', + 'mex/sources/k_order_welfare/approximation_welfare.cc', + 'mex/sources/k_order_welfare/k_ord_objective.cc', + 'mex/sources/k_order_welfare/objective_m.cc' ] +shared_module('k_order_welfare', k_order_welfare_src, kwargs : korder_mex_kwargs, link_with : korder_lib) + +# Unit tests + +korder_test_kwargs = { 'include_directories' : [ mex_incdir, korder_incdir ], + 'link_args' : exe_link_args, + 'build_rpath' : exe_rpath, + 'link_with' : korder_lib } + +korder_sylv_test_exe = executable('korder_sylv_test', [ 'mex/sources/libkorder/sylv/tests/MMMatrix.cc', + 'mex/sources/libkorder/sylv/tests/tests.cc'], + kwargs : korder_test_kwargs) +test('korder_sylv', korder_sylv_test_exe, workdir : meson.current_source_dir() / 'mex/sources/libkorder/sylv/tests', timeout : 300) + +korder_tl_test_exe = executable('korder_tl_test', [ 'mex/sources/libkorder/tl/tests/factory.cc', + 'mex/sources/libkorder/tl/tests/monoms.cc', + 'mex/sources/libkorder/tl/tests/tests.cc'], + kwargs : korder_test_kwargs) +test('korder_tl', korder_tl_test_exe, timeout : 300) + +korder_kord_test_exe = executable('korder_kord_test', 'mex/sources/libkorder/kord/tests/tests.cc', kwargs : korder_test_kwargs) +test('korder_kord', korder_kord_test_exe, timeout : 1500) + + +## MS-SBVAR stuff + +ms_sbvar_src = [ 'contrib/ms-sbvar/utilities_dw/arrays/dw_array.c', + 'contrib/ms-sbvar/utilities_dw/arrays/dw_matrix_array.c', + 'contrib/ms-sbvar/utilities_dw/ascii/dw_ascii.c', + 'contrib/ms-sbvar/utilities_dw/ascii/dw_parse_cmd.c', + 'contrib/ms-sbvar/utilities_dw/elliptical/dw_elliptical.c', + 'contrib/ms-sbvar/utilities_dw/error/dw_error.c', + 'contrib/ms-sbvar/utilities_dw/histogram/dw_histogram.c', + 'contrib/ms-sbvar/utilities_dw/math/dw_math.c', + 'contrib/ms-sbvar/utilities_dw/matrix/dw_matrix.c', + 'contrib/ms-sbvar/utilities_dw/matrix/bmatrix.c', + 'contrib/ms-sbvar/utilities_dw/sort/dw_matrix_sort.c', + 'contrib/ms-sbvar/utilities_dw/stat/dw_rand_gsl.c', + 'contrib/ms-sbvar/utilities_dw/stat/dw_matrix_rand.c', + 'contrib/ms-sbvar/switch_dw/switching/dw_switch.c', + 'contrib/ms-sbvar/switch_dw/switching/dw_switchio.c', + 'contrib/ms-sbvar/switch_dw/switching/dw_dirichlet_restrictions.c', + 'contrib/ms-sbvar/switch_dw/switching/dw_metropolis_theta.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/VARbase.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/VARio.c', + 'mex/sources/ms-sbvar/mex_top_level.cc', + 'mex/sources/ms-sbvar/modify_for_mex.cc' ] +ms_sbvar_defs = [ '-DSTRUCTURED_COLUMN_MAJOR' ] +ms_sbvar_incdir = include_directories('contrib/ms-sbvar/utilities_dw/include', 'contrib/ms-sbvar/switch_dw/switching', 'mex/sources/ms-sbvar') +ms_sbvar_lib = static_library('ms_sbvar', ms_sbvar_src, + kwargs : static_library_kwargs + { 'c_args' : static_library_kwargs.get('c_args') + ms_sbvar_defs, + 'cpp_args' : static_library_kwargs.get('cpp_args') + ms_sbvar_defs, + 'include_directories' : static_library_kwargs.get('include_directories') + ms_sbvar_incdir }, + dependencies : [ blas_dep, lapack_dep, gsl_dep, matio_dep, ut_dep ]) + +mex_ms_sbvar_kwargs = mex_kwargs + { 'c_args' : mex_kwargs.get('c_args') + ms_sbvar_defs, + 'cpp_args' : mex_kwargs.get('cpp_args') + ms_sbvar_defs, + 'include_directories' : mex_kwargs.get('include_directories') + ms_sbvar_incdir } +ms_sbvar_create_init_file_src = [ 'contrib/ms-sbvar/switch_dw/state_space/sbvar/create_init_file.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/VARio_matlab.c' ] +shared_module('ms_sbvar_create_init_file', ms_sbvar_create_init_file_src, kwargs : mex_ms_sbvar_kwargs, link_with : ms_sbvar_lib, dependencies : matio_dep) +ms_sbvar_command_line_src = [ 'contrib/ms-sbvar/switch_dw/switching/dw_switch_opt.c', + 'contrib/ms-sbvar/switch_dw/switching/dw_mdd_switch.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/dw_sbvar_command_line.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_estimate.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_simulate.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_probabilities.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_mdd.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_forecast.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_variance_decomposition.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/sbvar_impulse_responses.c', + 'contrib/ms-sbvar/switch_dw/state_space/sbvar/dw_csminwel.c' ] +shared_module('ms_sbvar_command_line', ms_sbvar_command_line_src, kwargs : mex_ms_sbvar_kwargs, link_with : ms_sbvar_lib, dependencies : [ gsl_dep, matio_dep ]) + + +### Integration tests + +# Create a test driver (in bash) that takes as arguments: +# - unique name of the test +# - get_option('build_for') +# - the path to the MATLAB/Octave executable: get_option('build_for') == 'matlab' ? matlab_exe.full_path() : octave_exe.full_path() +# - the version of MATLAB/Octave (needed under MATLAB for determining the batch options) +# - a list of .mod files that must be executed sequentially (i.e. a flattened dependency tree) +# - a separator (e.g. "--") +# - a list of extra files on which the .mod files depend (.inc, .mat, …) +# +# The test driver would do the following: +# - create a temporary directory (with mktemp -d or its equivalent on Windows/macOS), of the form dynare-$(testname).XXXXXX +# - copy the .mod files and the extra files into that temporary directory, keeping the original directory structure (a test dependency may be in another directory) +# - set the environment variable DYNARE_BUILD_DIR +# - run MATLAB/Octave on the .mod files sequentially (using a thin .m wrapper for printing the stack trace in case of error) +# - return the correct exitcode +# +# In the present file, the tests list could be organized in a array of arrays. Each inner array would correspond to a test, and contain: +# - the unique name of the test +# - an array containing the list of .mod files +# - an array containing the list of extra files +# - an array indicating the suite(s) to which this test belongs +# - an optional timeout value? +# +# This tests list would be used in a foreach loop around the test() command. +# +# Other items: +# - Decide whether to automatically delete the temporary directories created by the test driver. Cons: removes test data useful for debugging. Pro: avoid excessive disk filling. +# - See what to do with xvfb-run (see #1892). Maybe try to detect it from meson.build, and pass it optionally to the test driver script +# - Include the k-order unit tests in some suite(s) +# - See if the tests can be automatically disabled if the MATLAB/Octave executable is not found (using the disabler option of find_program?) +# - Deal with other types of tests (integration tests implemented as pure .m scripts, unit tests for .m files) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..9f13132c8 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,3 @@ +# TODO: Rename this file to meson.options when meson ⩾ 1.1 is required +option('build_for', type : 'combo', choices : [ 'matlab', 'octave' ], description : 'Whether to build for MATLAB or Octave') +option('matlab_path', type : 'string', description : 'Absolute directory containing the MATLAB installation') diff --git a/preprocessor b/preprocessor index 3a1870768..140c91e91 160000 --- a/preprocessor +++ b/preprocessor @@ -1 +1 @@ -Subproject commit 3a187076859751ebc3f6a4e43c9ffc3149655191 +Subproject commit 140c91e91188ee701d4a185d1ad771c23999f491 diff --git a/scripts/get-matlab-version b/scripts/get-matlab-version new file mode 100755 index 000000000..74ed47f8e --- /dev/null +++ b/scripts/get-matlab-version @@ -0,0 +1,193 @@ +#!/bin/bash + +# Returns the MATLAB version under the form x.y (not Rnnnn) +# Takes as argument the path to the MATLAB installation +# +# Alternatively, if the first argument is “--hex”, returns the MATLAB version +# as a pseudo-hexadecimal constant (0xMMmm) where MM is the major revision +# number (in decimal) and mm is the minor revision number (in decimal). +# E.g. version 9.14 is returned as 0x0914 + +# Copyright © 2009-2023 Dynare Team +# +# This file is part of Dynare. +# +# Dynare is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Dynare is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Dynare. If not, see . + +if [[ ($# == 0) || ($# == 1 && $1 == --hex) ]]; then + echo "Usage: $0 [--hex] /path/to/matlab" 2>&1 + exit 1 +fi + +if [[ $1 == --hex ]]; then + hex=true + MATLAB=$2 +else + hex=false + MATLAB=$1 +fi + +if [[ -f ${MATLAB}/VersionInfo.xml ]]; then + # The VersionInfo.xml file is present on all versions since R2017a, on all platforms. + # Extract the version number as x.y, since it is our preferred form, and is + # more robust to future versions. + MATLAB_VERSION=$(sed -En '//s/.*>([0-9]+\.[0-9]+).*/\1/p' "${MATLAB}/VersionInfo.xml") +elif [[ -f ${MATLAB}/bin/util/mex/version.txt ]]; then + # The bin/util/mex/version.txt file is present on Windows and macOS, at least + # since R2009b. It contains the release number (Rnnnnx). + MATLAB_VERSION=$(< "${MATLAB}/bin/util/mex/version.txt") +elif [[ -f ${MATLAB}/bin/mex || -f ${MATLAB}/bin/mexsh ]]; then + # Works on Linux and macOS until R2018a included. Returns the release number (Rnnnnx). + # Older MATLABs have the version in bin/mex, more recent in bin/mexsh + MATLAB_VERSION=$(sed -En "/^.*full_ver=/s/^.*full_ver='(R[^']+)'.*/\1/p" "${MATLAB}"/bin/mex*) +else + echo "Can’t determine the MATLAB version" 2>&1 + exit 1 +fi + +# If needed, convert a release number (Rnnnnx) into a version number (x.y) +case ${MATLAB_VERSION} in + *2023[aA]) + MATLAB_VERSION="9.14" + ;; + *2022[bB]) + MATLAB_VERSION="9.13" + ;; + *2022[aA]) + MATLAB_VERSION="9.12" + ;; + *2021[bB]) + MATLAB_VERSION="9.11" + ;; + *2021[aA]) + MATLAB_VERSION="9.10" + ;; + *2020[bB]) + MATLAB_VERSION="9.9" + ;; + *2020[aA]) + MATLAB_VERSION="9.8" + ;; + *2019[bB]) + MATLAB_VERSION="9.7" + ;; + *2019[aA]) + MATLAB_VERSION="9.6" + ;; + *2018[bB]) + MATLAB_VERSION="9.5" + ;; + *2018[aA]) + MATLAB_VERSION="9.4" + ;; + *2017[bB]) + MATLAB_VERSION="9.3" + ;; + *2017[aA]) + MATLAB_VERSION="9.2" + ;; + *2016[bB]) + MATLAB_VERSION="9.1" + ;; + *2016[aA]) + MATLAB_VERSION="9.0" + ;; + *2015[bB]) + MATLAB_VERSION="8.6" + ;; + *2015[aA]) + MATLAB_VERSION="8.5" + ;; + *2014[bB]) + MATLAB_VERSION="8.4" + ;; + *2014[aA]) + MATLAB_VERSION="8.3" + ;; + *2013[bB]) + MATLAB_VERSION="8.2" + ;; + *2013[aA]) + MATLAB_VERSION="8.1" + ;; + *2012[bB]) + MATLAB_VERSION="8.0" + ;; + *2012[aA]) + MATLAB_VERSION="7.14" + ;; + *2011[bB]) + MATLAB_VERSION="7.13" + ;; + *2011[aA]) + MATLAB_VERSION="7.12" + ;; + *2010[bB]) + MATLAB_VERSION="7.11" + ;; + *2010[aA]) + MATLAB_VERSION="7.10" + ;; + *2009[bB]) + MATLAB_VERSION="7.9" + ;; + *2009[aA]) + MATLAB_VERSION="7.8" + ;; + *2008[bB]) + MATLAB_VERSION="7.7" + ;; + *2008[aA]) + MATLAB_VERSION="7.6" + ;; + *2007[bB]) + MATLAB_VERSION="7.5" + ;; + *2007[aA]) + MATLAB_VERSION="7.4" + ;; + *2006[bB]) + MATLAB_VERSION="7.3" + ;; + *2006[aA]) + MATLAB_VERSION="7.2" + ;; + *14[sS][pP]3) + MATLAB_VERSION="7.1" + ;; + *14[sS][pP]2) + MATLAB_VERSION="7.0.4" + ;; + *14[sS][pP]1) + MATLAB_VERSION="7.0.1" + ;; + [rR]14) + MATLAB_VERSION="7.0.0" + ;; +esac + +# Check that we have an x.y version number +if ! grep -qE '^[0-9.]+$' <<< "${MATLAB_VERSION}"; then + echo "Unknown MATLAB version: ${MATLAB_VERSION}" 2>&1 + exit 1 +fi + +if [[ $hex == true ]]; then + echo -n "0x" + sed -e 's/\([0-9]*\)\.\([0-9]*\).*/Z\1ZZ\2Z/' \ + -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ + -e 's/[^0-9]//g' <<< "${MATLAB_VERSION}" +else + echo "${MATLAB_VERSION}" +fi diff --git a/scripts/homebrew-native.ini b/scripts/homebrew-native.ini new file mode 100644 index 000000000..f00b7712f --- /dev/null +++ b/scripts/homebrew-native.ini @@ -0,0 +1,11 @@ +# Meson native file for compiling under Homebrew + +[binaries] +cpp = 'g++-13' +c = 'gcc-13' +flex = '/usr/local/opt/flex/bin/flex' +bison = '/usr/local/opt/bison/bin/bison' + +[built-in options] +cpp_args = [ '-I/usr/local/include', '-B', '/usr/local/lib' ] +fortran_args = [ '-B', '/Users/sebastien/slicot' ] diff --git a/scripts/windows-cross-matlab.ini b/scripts/windows-cross-matlab.ini new file mode 100644 index 000000000..0ada1e0ab --- /dev/null +++ b/scripts/windows-cross-matlab.ini @@ -0,0 +1,9 @@ +# Meson cross file for targeting Windows from Linux +# This is the MATLAB-specific cross file, to be include before the common cross file + +[project options] +build_for='matlab' +#matlab_path='/tmp/deps/matlab64/R2018a' + +[constants] +#slicot_path='/tmp/deps/lib64/Slicot/without-underscore/lib/' diff --git a/scripts/windows-cross-octave.ini b/scripts/windows-cross-octave.ini new file mode 100644 index 000000000..c838bc5ed --- /dev/null +++ b/scripts/windows-cross-octave.ini @@ -0,0 +1,11 @@ +# Meson cross file for targeting Windows from Linux +# This is the Octave-specific cross file, to be include before the common cross file + +[binaries] +#mkoctfile='/tmp/deps/mkoctfile64' + +[project options] +build_for='octave' + +[constants] +#slicot_path='/tmp/deps/lib64/Slicot/with-underscore/lib/' diff --git a/scripts/windows-cross.ini b/scripts/windows-cross.ini new file mode 100644 index 000000000..c5ca2c77c --- /dev/null +++ b/scripts/windows-cross.ini @@ -0,0 +1,45 @@ +# Meson common cross file for targeting Windows from Linux +# To be included after windows-cross-{matlab,octave}.ini + +[constants] +# For the architectural baseline, we follow MSYS2: +# https://www.msys2.org/news/#2022-10-18-new-minimum-hardware-requirements-cpus-from-20067 +arch_flags = [ '-march=nocona', '-msahf', '-mtune=generic' ] + +[binaries] +cpp = 'x86_64-w64-mingw32-g++-posix' +fortran = 'x86_64-w64-mingw32-gfortran-posix' +c = 'x86_64-w64-mingw32-gcc-posix' +strip = 'x86_64-w64-mingw32-strip' +ar = 'x86_64-w64-mingw32-gcc-ar-posix' +pkgconfig = 'x86_64-w64-mingw32-pkg-config' + +[host_machine] +system = 'windows' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[built-in options] +prefer_static = true + +# MSYS2 libraries are now built with -fstack-protector-strong, see: +# https://www.msys2.org/news/#2022-10-23-mingw-packages-now-built-with-d_fortify_source2-and-fstack-protector-strong +# As of 2023-01-03, when linking against HDF5 (and possibly other libraries), +# it is necessary to compile our own code with -fstack-protector to avoid undefined symbols +# at link time. +# Note that specifying -fstack-protector-strong or -fstack-protector-all will lead +# to a dependency on libssp-0.dll (at least when using the MinGW compilers from Debian), +# and there seems to be no easy way of linking it statically. +# Also note that adding this flag is not necessary when building from MSYS2 shell. +# Maybe revisit this once our runners are upgraded to Debian “Bookworm” 12. +cpp_args = [ '-fstack-protector' ] + arch_flags +cpp_link_args = [ '-fstack-protector' ] + +fortran_args = arch_flags + [ '-B', slicot_path ] +c_args = arch_flags + +[properties] +#sys_root='/tmp/deps/sys_root' +#pkg_config_libdir = '/tmp/deps/sys_root/mingw64/lib/pkgconfig' +#boost_root = '/tmp/deps/sys_root/mingw64' diff --git a/windows/deps/mkoctfile64 b/windows/deps/mkoctfile64 index e6a4179ac..e1e8440bd 100755 --- a/windows/deps/mkoctfile64 +++ b/windows/deps/mkoctfile64 @@ -142,7 +142,7 @@ fi if [ $# -eq 1 ]; then case "$1" in -v | -version | --version) - echo $version_msg 1>&2 + echo $version_msg exit 0 ;; esac