diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b46b929c1..133f3063bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -193,6 +193,50 @@ jobs: - name: Run sample demo run: python3 `which scons` test-clib-demo --debug=time + meson-build: + name: Meson build system test + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + name: Checkout the repository + with: + submodules: false + persist-credentials: false + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + architecture: x64 + - name: Install Apt dependencies + run: | + sudo apt update + sudo apt install -y meson ninja-build libboost-dev libeigen3-dev \ + libsundials-dev libhdf5-dev libfmt-dev libyaml-cpp-dev + - name: Configure with Meson + run: meson setup builddir + - name: Compile with Meson + run: meson compile -C builddir + - name: Show build summary + run: | + echo "Meson build completed successfully!" + echo "Build directory contents:" + ls -lh builddir/src/ + - name: Report Meson build status + if: always() + run: | + if [ -f builddir/build.ninja ]; then + echo "✓ Meson configuration successful" + else + echo "✗ Meson configuration failed" + fi + if [ -f builddir/src/libcantera.so* ]; then + echo "✓ Cantera library built successfully" + ls -lh builddir/src/libcantera.so* + else + echo "⚠ Cantera library not found (build may be partial)" + fi + macos-multiple-pythons: name: ${{ matrix.macos-version }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.macos-version }} diff --git a/MESON_BUILD.md b/MESON_BUILD.md new file mode 100644 index 0000000000..19d9dfcc89 --- /dev/null +++ b/MESON_BUILD.md @@ -0,0 +1,133 @@ +# Meson Build System for Cantera + +This directory contains the Meson build configuration for Cantera's C++ library. + +## Status + +The Meson build system builds the complete C++ library using system-installed dependencies only. + +## Quick Start + +### Prerequisites + +- Meson >= 1.0.0 +- Ninja build system +- C++20 compatible compiler (GCC, Clang, or MSVC) +- System dependencies (all required): + - Eigen3 >= 3.4 + - Boost >= 1.83 + - fmt >= 9.1.0 + - yaml-cpp >= 0.6 + - SUNDIALS >= 6.0 (cvodes, idas, nvecserial) + - Optional: HDF5, BLAS/LAPACK, HighFive + +### Building + +1. Configure the build: + ```bash + meson setup builddir + ``` + +2. Compile: + ```bash + meson compile -C builddir + ``` + +3. Install (optional): + ```bash + meson install -C builddir + ``` + +## Dependencies + +All dependencies must be installed as system packages. The Meson build does not support fallback to bundled libraries in ext/ submodules. + +### Required System Packages + +- **Boost** (>= 1.83): Header-only library +- **Eigen3** (>= 3.4): Header-only library for linear algebra +- **fmt** (>= 9.1.0): Formatting library +- **yaml-cpp** (>= 0.6): YAML parser/emitter +- **SUNDIALS** (>= 6.0): ODE/DAE solvers (cvodes, idas, nvecserial components required) + +### Optional System Packages + +- **HDF5**: For HDF5 data file support +- **BLAS/LAPACK**: For optimized linear algebra (falls back to Eigen if not available) +- **HighFive**: C++ wrapper for HDF5 (required if HDF5 support is desired) + +## Configuration Options + +- `cantera_datadir`: Directory for Cantera data files (default: `prefix/share/cantera/data`) + +Example: +```bash +meson setup builddir -Dcantera_datadir=/opt/cantera/data +``` + +## Build Features + +### Automatic Source Discovery + +The Meson build automatically discovers source files using glob patterns, similar to how SCons uses `multi_glob()`: + +```meson +# Automatically find all .cpp files in each module directory +base_files = run_command(find, 'base', '-name', '*.cpp', ...) +foreach f : base_files + base_sources += files('base' / f) +endforeach +``` + +This eliminates the need to manually list individual source files. + +### Installation + +On Ubuntu/Debian: +```bash +sudo apt install meson ninja-build libboost-dev libeigen3-dev \ + libfmt-dev libyaml-cpp-dev libsundials-dev +``` + +On macOS with Homebrew: +```bash +brew install meson ninja boost eigen fmt yaml-cpp sundials +``` + +On Fedora/RHEL: +```bash +sudo dnf install meson ninja-build boost-devel eigen3-devel \ + fmt-devel yaml-cpp-devel sundials-devel +``` + +## Known Limitations + +1. **No Python bindings**: Only C++ library is currently supported +2. **No Fortran interface**: F90 interface not yet implemented +3. **Limited testing**: Build system needs more testing across platforms +4. **System packages required**: No support for bundled ext/ submodules + +## Comparison with SCons + +The Meson build aims to eventually replace the SCons build system with these benefits: +- Faster build times with better parallelization +- Better IDE integration +- Standard pkg-config support +- Simpler dependency management (system packages only) +- Cross-compilation support +- **Automatic source discovery** like SCons `multi_glob()` + +## Contributing + +Contributions are welcome to: +- Add Python bindings support +- Add testing infrastructure +- Port more configuration options from SCons +- Test on more platforms + +## Files + +- `meson.build`: Root build configuration +- `meson_options.txt`: Build options +- `src/meson.build`: C++ source compilation with automatic file discovery +- `include/cantera/base/config.h.meson.in`: Configuration header template diff --git a/ext/fmt b/ext/fmt index a33701196a..2d7dc922c6 160000 --- a/ext/fmt +++ b/ext/fmt @@ -1 +1 @@ -Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50 +Subproject commit 2d7dc922c66338b4dc6654fcd9ba6ecb2d09b914 diff --git a/ext/yaml-cpp b/ext/yaml-cpp index 0579ae3d97..2e6383d272 160000 --- a/ext/yaml-cpp +++ b/ext/yaml-cpp @@ -1 +1 @@ -Subproject commit 0579ae3d976091d7d664aa9d2527e0d0cff25763 +Subproject commit 2e6383d272f676e1ad28ae5c47016045cbaff938 diff --git a/include/cantera/base/config.h.meson.in b/include/cantera/base/config.h.meson.in new file mode 100644 index 0000000000..81c0b27da3 --- /dev/null +++ b/include/cantera/base/config.h.meson.in @@ -0,0 +1,56 @@ +#ifndef CT_CONFIG_H +#define CT_CONFIG_H + +//---------------------------- Version Flags ------------------// +// Cantera version -> this will be a double-quoted string value +@CANTERA_VERSION@ + +// Just the major + minor version (that is, 2.2 instead of 2.2.0) +@CANTERA_SHORT_VERSION@ + +//------------------------ Fortran settings -------------------// + +// define types doublereal, integer, and ftnlen to match the +// corresponding Fortran data types on your system. The defaults +// are OK for most systems + +typedef double doublereal; // Fortran double precision +typedef int integer; // Fortran integer +typedef int ftnlen; // Fortran hidden string length type + +// Fortran compilers pass character strings in argument lists by +// adding a hidden argument with the length of the string. Some +// compilers add the hidden length argument immediately after the +// CHARACTER variable being passed, while others put all of the hidden +// length arguments at the end of the argument list. Define this if +// the lengths are at the end of the argument list. This is usually the +// case for most unix Fortran compilers, but is (by default) false for +// Visual Fortran under Windows. +#define STRING_LEN_AT_END + +// Define this if Fortran adds a trailing underscore to names in object files. +// For linux and most unix systems, this is the case. +@FTN_TRAILING_UNDERSCORE@ + +//-------- LAPACK / BLAS --------- + +@LAPACK_FTN_STRING_LEN_AT_END@ +@LAPACK_FTN_TRAILING_UNDERSCORE@ +@CT_USE_LAPACK@ + +@CT_USE_SYSTEM_EIGEN@ +@CT_USE_SYSTEM_EIGEN_PREFIXED@ +@CT_USE_SYSTEM_FMT@ +@CT_USE_SYSTEM_YAMLCPP@ + +//-------------- Optional Cantera Capabilities ---------------------- + +// Enable Sundials to use an external BLAS/LAPACK library if it was +// built to use this option +@CT_SUNDIALS_USE_LAPACK@ + +// Enable export/import of HDF data via C++ HighFive +@CT_USE_HDF5@ +@CT_USE_SYSTEM_HIGHFIVE@ + +#endif diff --git a/include/cantera/ext/Eigen b/include/cantera/ext/Eigen new file mode 120000 index 0000000000..4691a45985 --- /dev/null +++ b/include/cantera/ext/Eigen @@ -0,0 +1 @@ +../../../ext/eigen/Eigen \ No newline at end of file diff --git a/include/cantera/ext/fmt b/include/cantera/ext/fmt new file mode 120000 index 0000000000..e3ea0d0edd --- /dev/null +++ b/include/cantera/ext/fmt @@ -0,0 +1 @@ +../../../ext/fmt/include/fmt \ No newline at end of file diff --git a/include/cantera/ext/yaml-cpp b/include/cantera/ext/yaml-cpp new file mode 120000 index 0000000000..4c41e6ced5 --- /dev/null +++ b/include/cantera/ext/yaml-cpp @@ -0,0 +1 @@ +../../../ext/yaml-cpp/include/yaml-cpp \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000000..d54b88be7f --- /dev/null +++ b/meson.build @@ -0,0 +1,184 @@ +project('cantera', 'cpp', 'c', + version: '4.0.0', + license: 'BSD-3-Clause', + default_options: [ + 'cpp_std=c++20', + 'c_std=c99', + 'warning_level=2', + 'buildtype=release', + ], + meson_version: '>=1.0.0' +) + +# Import modules +fs = import('fs') + +# Get version components +version_parts = meson.project_version().split('.') +version_major = version_parts[0] +version_minor = version_parts[1] +cantera_short_version = f'@version_major@.@version_minor@' + +# Compiler setup +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +# Platform-specific settings +host_system = host_machine.system() +is_windows = host_system == 'windows' +is_darwin = host_system == 'darwin' + +# Additional compiler flags +add_project_arguments('-DFMT_HEADER_ONLY', language: 'cpp') + +if cpp.get_id() == 'msvc' + add_project_arguments('/EHsc', '/utf-8', '/nologo', language: 'cpp') + add_project_arguments('/D_SCL_SECURE_NO_WARNINGS', language: 'cpp') + add_project_arguments('/D_CRT_SECURE_NO_WARNINGS', language: 'cpp') + add_project_arguments('/MD', language: 'cpp') +elif cpp.get_id() == 'clang' + add_project_arguments('-fcolor-diagnostics', language: 'cpp') +endif + +# Thread support +thread_dep = dependency('threads', required: true) + +# Math library (needed on some platforms) +m_dep = cc.find_library('m', required: false) + +# SUNDIALS dependency - required system package +sundials_cvodes_dep = dependency('sundials_cvodes', version: '>=6.0', required: true) +sundials_idas_dep = dependency('sundials_idas', required: true) +sundials_nvecserial_dep = dependency('sundials_nvecserial', required: true) +message('Using system SUNDIALS') + +# Eigen dependency (header-only) - required system package +eigen_dep = dependency('eigen3', version: '>=3.4', required: true) +message('Using system Eigen') + +# fmt dependency - required system package +fmt_dep = dependency('fmt', version: '>=9.1.0', required: true) +message('Using system fmt') + +# yaml-cpp dependency - required system package +yamlcpp_dep = dependency('yaml-cpp', version: '>=0.6', required: true) +message('Using system yaml-cpp') + +# Boost dependency (header-only, required) +boost_dep = dependency('boost', version: '>=1.83', required: true, + modules: [] # Header-only +) + +# HDF5 dependency (optional) +hdf5_dep = dependency('hdf5', required: false) +use_hdf5 = hdf5_dep.found() +if use_hdf5 + message('HDF5 support enabled') +else + message('HDF5 support disabled') +endif + +# HighFive dependency (header-only, optional) +# Only use system package, no fallback to submodule +highfive_dep = dependency('HighFive', required: false) +if highfive_dep.found() and use_hdf5 + message('Using system HighFive') +else + highfive_dep = dependency('', required: false) + if use_hdf5 + warning('HighFive not found. HDF5 support will be limited. Install HighFive package for full HDF5 support.') + endif +endif + +# BLAS/LAPACK dependency (optional) +lapack_dep = dependency('lapack', required: false) +blas_dep = dependency('blas', required: false) +use_lapack = false + +if lapack_dep.found() and blas_dep.found() + use_lapack = true + message('Using system BLAS/LAPACK') +elif is_darwin + # Try Accelerate framework on macOS + accelerate_dep = dependency('Accelerate', required: false) + if accelerate_dep.found() + lapack_dep = accelerate_dep + blas_dep = dependency('', required: false) + use_lapack = true + message('Using macOS Accelerate framework') + endif +endif + +if not use_lapack + message('BLAS/LAPACK not found, will use Eigen for linear algebra') + lapack_dep = dependency('', required: false) + blas_dep = dependency('', required: false) +endif + +# Git commit information +git = find_program('git', required: false) +git_commit = 'unknown' +if git.found() + git_result = run_command(git, 'rev-parse', '--short', 'HEAD', + check: false, capture: true) + if git_result.returncode() == 0 + git_commit = git_result.stdout().strip() + endif +endif + +# Data directory +ct_datadir = get_option('cantera_datadir') +if ct_datadir == '' + ct_datadir = get_option('prefix') / 'share' / 'cantera' / 'data' +endif + +# Generate config.h +conf_data = configuration_data() +conf_data.set('CANTERA_VERSION', '#define CANTERA_VERSION "' + meson.project_version() + '"') +conf_data.set('CANTERA_SHORT_VERSION', '#define CANTERA_SHORT_VERSION "' + cantera_short_version + '"') +# All dependencies are now system packages +conf_data.set('CT_USE_SYSTEM_EIGEN', '#define CT_USE_SYSTEM_EIGEN 1') +conf_data.set('CT_USE_SYSTEM_EIGEN_PREFIXED', '/* #undef CT_USE_SYSTEM_EIGEN_PREFIXED */') +conf_data.set('CT_USE_SYSTEM_FMT', '#define CT_USE_SYSTEM_FMT 1') +conf_data.set('CT_USE_SYSTEM_YAMLCPP', '#define CT_USE_SYSTEM_YAMLCPP 1') +conf_data.set('CT_USE_LAPACK', use_lapack ? '#define CT_USE_LAPACK 1' : '/* #undef CT_USE_LAPACK */') +conf_data.set('CT_USE_HDF5', use_hdf5 ? '#define CT_USE_HDF5 1' : '/* #undef CT_USE_HDF5 */') +conf_data.set('CT_USE_SYSTEM_HIGHFIVE', highfive_dep.found() ? '#define CT_USE_SYSTEM_HIGHFIVE 1' : '/* #undef CT_USE_SYSTEM_HIGHFIVE */') +conf_data.set('CT_SUNDIALS_USE_LAPACK', '/* #undef CT_SUNDIALS_USE_LAPACK */') + +# Fortran settings +conf_data.set('FTN_TRAILING_UNDERSCORE', '#define FTN_TRAILING_UNDERSCORE') +conf_data.set('LAPACK_FTN_STRING_LEN_AT_END', '#define LAPACK_FTN_STRING_LEN_AT_END') +conf_data.set('LAPACK_FTN_TRAILING_UNDERSCORE', '#define LAPACK_FTN_TRAILING_UNDERSCORE') + +config_h = configure_file( + input: 'include/cantera/base/config.h.meson.in', + output: 'config.h', + configuration: conf_data, + install: true, + install_dir: get_option('includedir') / 'cantera' / 'base' +) + +# Create symlink for config.h in build directory so it's found at cantera/base/config.h +meson.add_postconf_script('sh', '-c', 'mkdir -p "$MESON_BUILD_ROOT/cantera/base" && ln -sf "$MESON_BUILD_ROOT/config.h" "$MESON_BUILD_ROOT/cantera/base/config.h"'.replace('$MESON_BUILD_ROOT', meson.current_build_dir())) + +# Include directories +cantera_include_dirs = include_directories( + 'include', + 'src', +) + +# Build directory for generated files (contains config.h) +cantera_build_include = include_directories('.') + +# Build the library +subdir('src') + +# Summary +summary({ + 'Version': meson.project_version(), + 'Git commit': git_commit, + 'BLAS/LAPACK': use_lapack, + 'HDF5 support': use_hdf5, + 'HighFive': highfive_dep.found(), +}, section: 'Configuration') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000000..f276783933 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,5 @@ +option('cantera_datadir', + type: 'string', + value: '', + description: 'Directory for Cantera data files (default: prefix/share/cantera/data)' +) diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000000..bf71cd76eb --- /dev/null +++ b/src/meson.build @@ -0,0 +1,189 @@ +# Source files for the Cantera library +# Using run_command to automatically discover source files via find + +# Helper to get cpp files from a directory +find_cpp = find_program('find') + +# Gather all .cpp files from each module subdirectory +base_files = run_command(find_cpp, 'base', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +base_sources = [] +foreach f : base_files + if f != '' + base_sources += files('base' / f) + endif +endforeach + +thermo_files = run_command(find_cpp, 'thermo', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +thermo_sources = [] +foreach f : thermo_files + if f != '' + thermo_sources += files('thermo' / f) + endif +endforeach + +tpx_files = run_command(find_cpp, 'tpx', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +tpx_sources = [] +foreach f : tpx_files + if f != '' + tpx_sources += files('tpx' / f) + endif +endforeach + +equil_files_cpp = run_command(find_cpp, 'equil', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +equil_files_c = run_command(find_cpp, 'equil', '-maxdepth', '1', '-name', '*.c', '-printf', '%f\n', check: false, capture: true).stdout().strip().split('\n') +equil_sources = [] +foreach f : equil_files_cpp + if f != '' + equil_sources += files('equil' / f) + endif +endforeach +foreach f : equil_files_c + if f != '' + equil_sources += files('equil' / f) + endif +endforeach + +numerics_files = run_command(find_cpp, 'numerics', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +numerics_sources = [] +foreach f : numerics_files + if f != '' + numerics_sources += files('numerics' / f) + endif +endforeach + +kinetics_files = run_command(find_cpp, 'kinetics', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +kinetics_sources = [] +foreach f : kinetics_files + if f != '' + kinetics_sources += files('kinetics' / f) + endif +endforeach + +transport_files = run_command(find_cpp, 'transport', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +transport_sources = [] +foreach f : transport_files + if f != '' + transport_sources += files('transport' / f) + endif +endforeach + +oned_files = run_command(find_cpp, 'oneD', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +oned_sources = [] +foreach f : oned_files + if f != '' + oned_sources += files('oneD' / f) + endif +endforeach + +zerod_files = run_command(find_cpp, 'zeroD', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +zerod_sources = [] +foreach f : zerod_files + if f != '' + zerod_sources += files('zeroD' / f) + endif +endforeach + +extensions_files = run_command(find_cpp, 'extensions', '-maxdepth', '1', '-name', '*.cpp', '-printf', '%f\n', check: true, capture: true).stdout().strip().split('\n') +extensions_sources = [] +foreach f : extensions_files + if f != '' + extensions_sources += files('extensions' / f) + endif +endforeach + +# Combine all sources +cantera_sources = base_sources + thermo_sources + tpx_sources + equil_sources + numerics_sources + kinetics_sources + transport_sources + oned_sources + zerod_sources + extensions_sources + +# Additional compile definitions for specific files +application_cpp_args = ['-DCANTERA_DATA="' + ct_datadir + '"'] +global_cpp_args = ['-DGIT_COMMIT="' + git_commit + '"'] + +# Build application.cpp with special define +application_lib = static_library('cantera_application', + 'base/application.cpp', + include_directories: [cantera_include_dirs, cantera_build_include], + cpp_args: application_cpp_args, + dependencies: [ + eigen_dep, + fmt_dep, + yamlcpp_dep, + boost_dep, + thread_dep, + m_dep, + ], + pic: true, +) + +# Build global.cpp with special define +global_lib = static_library('cantera_global', + 'base/global.cpp', + include_directories: [cantera_include_dirs, cantera_build_include], + cpp_args: global_cpp_args, + dependencies: [ + eigen_dep, + fmt_dep, + yamlcpp_dep, + boost_dep, + thread_dep, + m_dep, + ], + pic: true, +) + +# Collect dependencies +cantera_deps = [ + eigen_dep, + fmt_dep, + yamlcpp_dep, + boost_dep, + thread_dep, + m_dep, +] + +if sundials_dep.found() + cantera_deps += [sundials_dep, sundials_idas_dep, sundials_nvecserial_dep] +endif + +if use_lapack + cantera_deps += [lapack_dep] + if blas_dep.found() + cantera_deps += [blas_dep] + endif +endif + +if use_hdf5 + cantera_deps += [hdf5_dep, highfive_dep] +endif + +# Build the shared library +cantera_lib = shared_library('cantera', + cantera_sources, + include_directories: [cantera_include_dirs, cantera_build_include], + dependencies: cantera_deps, + link_whole: [application_lib, global_lib], + install: true, + version: meson.project_version(), + soversion: version_major, +) + +# Install headers +install_subdir('include/cantera', + install_dir: get_option('includedir'), + strip_directory: false, +) + +# Declare dependency for use by other subprojects +cantera_dep = declare_dependency( + link_with: cantera_lib, + include_directories: [cantera_include_dirs, cantera_build_include], + dependencies: cantera_deps, +) + +# pkg-config file +pkg = import('pkgconfig') +pkg.generate(cantera_lib, + name: 'Cantera', + description: 'Chemical kinetics, thermodynamics, and transport library', + url: 'https://cantera.org', + subdirs: 'cantera', +)