vincent-picaud/mma_meson_demo
How to use Meson to build a Mathematica package relying on LibraryLink and on an external C++ library
Table of Contents
What is it?
The goal is to use the Meson build system to define an easy to use and
portable solution for Mathematica (MMA) Wolfram LibraryLink
developments. This GitHub repository is a small illustrative example.
The important files are:
+ meson.build
+ mma/
+ algorithm1_mma.cpp
+ config.wls
+ meson.build
+ myLib_mma.cpp
+ myLib.wl
+ src/
+ meson.build
+ myLib/
+ algorithm1.cpp
+ algorithm1.hpp
+ meson.build
News
- <Tue Mar 26 2019> added support for MMA v10 (before only v11 was supported)
How to use it?
Compile and install the package
git clone git@github.com:vincent-picaud/mma_meson_demo.git
cd mma_meson_demo
meson build --prefix=$(pwd)/install_dir
cd build
ninja install
you will get something like:
picaud@hostname:~/GitHub/mma_meson_demo$ meson build --prefix=$(pwd)/install_dir
The Meson build system
Version: 0.49.2
Source dir: /home/picaud/GitHub/mma_meson_demo
Build dir: /home/picaud/GitHub/mma_meson_demo/build
Build type: native build
Project name: Meson_MMA_Demo
Project version: undefined
Native C++ compiler: ccache c++ (gcc 8.3.0 "c++ (Debian 8.3.0-2) 8.3.0")
Build machine cpu family: x86_64
Build machine cpu: x86_64
Program wolframscript found: YES (/usr/bin/wolframscript)
Message: MMA library installation directory: /home/picaud/.Mathematica/SystemFiles/LibraryResources/Linux-x86-64
Message: MMA package installation directory: /home/picaud/.Mathematica/Applications
Build targets in project: 2
Found ninja-1.8.2 at /usr/bin/ninja
picaud@hostname:~/GitHub/mma_meson_demo$ cd build/
picaud@hostname:~/GitHub/mma_meson_demo/build$ ninja install
[5/6] Installing files.
Installing src/myLib/libmyLib.so to /home/picaud/GitHub/mma_meson_demo/install_dir/lib/x86_64-linux-gnu
Installing mma/libmyLibMMA.so to /home/picaud/.Mathematica/SystemFiles/LibraryResources/Linux-x86-64
Installing /home/picaud/GitHub/mma_meson_demo/src/myLib/algorithm1.hpp to /home/picaud/GitHub/mma_meson_demo/install_dir/include/myLib
Installing /home/picaud/GitHub/mma_meson_demo/mma/myLib.wl to /home/picaud/.Mathematica/Applications
Remark: [role of –prefix]
This demo will generate two dynamic libraries:
libmyLib.soa regular C++ library (totally independent of MMA)libmylibMMA.soa dynamic library that uses MMA's LibraryLink to wrap functions contained inlibmyLib.so.The
libmyLib.soinstall directory is defined by the--prefixflag, with--prefix=$(pwd)/install_dirthis is/home/picaud/GitHub/mma_meson_demo/install_dir/lib/x86_64-linux-gnuin my case.
By default
libmylibMMA.sois installed in the user directory$HOME/.Mathematica/SystemFiles/LibraryResources/Linux-x86-64in my case.
The important point to understand is that
libmylibMMA.soneeds to know
where thelibmyLib.solibrary is installed. We provide this
information by embedding it into thelibmylibMMA.sorpath
section. This section is filled by Meson at installation time. By
consequence to make it works we must define this installation path.If the
--prefixvariable is not defined everything works equally well
except that you will need extra permissions to do a default system
install (invoked when you typeninja install).
Mathematica side
To test it works, type the following under Mathematica:
<< myLib`
?"myLib`*"
data = {2, 1, 3, 5, 6, 6, 4, 3 };
algorithm1[100.,data]
it should print:
algorithm1[α_Real,vector_?VectorQ] returns α.vector
{200., 100., 300., 500., 600., 600., 400., 300.}
Remark: [
myLib`Private`unload[]]If you recompile the
libmylib.soorlibmylibMMA.soC++ library you
must callmyLib`Private`unload[]to make MMA aware of this
modification (no need to kill the Kernel!).Obviously, for regular uses of the MMA
myLibpackage, you do not need
this function.
Portability
I have no access to Windows. It would we nice to have some feedback
concerning this platform:
- Mathematica + Linux
- Mathematica + MacOS
- Mathematica + Windows ???
How it works?
meson.build
#----------------
# Regular Meson C++ project
#----------------
project('Meson_MMA_Demo', 'cpp')
subdir('src')
#----------------
# MMA specific
#----------------
subdir('mma')
The src directory contains a regular C++ meson project, which is
compiled in a regular way.
After that we move to the mma directory which is dedicated to the
Mathematica part.
Remark:
Even if you do not have Mathematica installed, the C++ library (in the
./src/directory) is properly compiled and installed.
./src/meson.build
As explained, this is a regular C++ Meson project:
myLib_inc = include_directories('.')
subdir('myLib')
./src/myLib/meson.build
For this small demo we create a dynamic library libmyLib.so containing
only one file algorithm1.cpp. You can obviously add others files by
appending them to the myLib_headers and myLib_sources Meson variables.
myLib_headers = ['algorithm1.hpp']
myLib_sources = ['algorithm1.cpp']
myLib_lib = library('myLib',
include_directories : myLib_inc,
install : true,
sources: [myLib_headers,myLib_sources])
myLib_dep = declare_dependency(include_directories : myLib_inc,
link_with : myLib_lib)
install_headers(myLib_headers,
subdir : 'myLib')
./mma/meson.build
This part is specific to MMA.
#----------------
# Extract MMA information
#----------------
# try MMA v10 first
mma_wolframscript = find_program('MathematicaScript', required: false)
if mma_wolframscript.found()
maa_config = run_command(mma_wolframscript,'-script',files('config.wls'), check: true)
else
# then MMA v11
mma_wolframscript = find_program('wolframscript', required: false)
if mma_wolframscript.found()
maa_config = run_command(mma_wolframscript,'-f',files('config.wls'), check: true)
else
# no MMA?
warning('Mathematica not found!')
subdir_done()
endif
endif
maa_config = maa_config.stdout().split(';')
mma_include_directories = include_directories(maa_config.get(0).split(','))
mma_library_install_dir = maa_config.get(1).strip() # caveat: strip is mandatory to get
mma_package_install_dir = maa_config.get(2).strip() # a correct filename
message('MMA library installation directory: '+mma_library_install_dir)
message('MMA package installation directory: '+mma_package_install_dir)
#----------------
# myLibMMA library
#----------------
myLibMMA_sources = ['myLib_mma.cpp',
'algorithm1_mma.cpp']
shared_library('myLibMMA',
sources: [myLibMMA_sources],
dependencies: [myLib_dep],
include_directories: mma_include_directories,
install: true,
# libmyLibMMA.so needs to find libmyLib.so, this can be done using rpath
install_rpath: join_paths(get_option('prefix'),get_option('libdir')),
install_dir: mma_library_install_dir)
#----------------
# MMA package
#----------------
install_data('myLib.wl', install_dir: mma_package_install_dir )
In a first step we run the config.wls script to extract from MMA the
relevant information required by the Meson build process. These
information are printed in a form easily readable by Meson:
wolframscript -f config.wls
/usr/local/Wolfram/Mathematica/11.2/SystemFiles/IncludeFiles/C,/usr/local/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions;/home/picaud/.Mathematica/SystemFiles/LibraryResources/Linux-x86-64;/home/picaud/.Mathematica/Applications
These extracted information are stored into the mma_include_directories, mma_library_install_dir and mma_package_install_dir Meson variables.
In a second step we create the libmylibMMA.so dynamic library and also
define its rpath variable to allow it to find the installed
libmyLib.so library (see Compile and install the package).
In a third step we define where the MMA package myLib.wl will be
installed (here in the mma_package_install_dir default location).
That's it!
config.wls
The config.wls script extracts the relevant information required by the
Meson build process.
libraryLinkIncludeDirectories={FileNameJoin[{$InstallationDirectory,"SystemFiles","IncludeFiles","C"}],
FileNameJoin[{$InstallationDirectory,"SystemFiles","Links","MathLink","DeveloperKit",$SystemID,"CompilerAdditions"}]};
libraryInstallDirectory=FileNameJoin[{$UserBaseDirectory,"SystemFiles","LibraryResources",$SystemID}];
packageInstallDirectory=FileNameJoin[{$UserBaseDirectory,"Applications"}];
(* MMA < v10.1 does not have native StringRiffle *)
stringRiffle[stringList_List,sep_String]:=TextString[stringList, ListFormat -> {"", sep, ""}];
format[s_List]:=stringRiffle[s,","]
(* stdout result in a format Meson can read *)
Print[format[libraryLinkIncludeDirectories]<>";"<>libraryInstallDirectory<>";"<>packageInstallDirectory]
Our cpp files
This is really for demo purpose as we simply compute a scalar-vector product w=α.v
The libmyLib.so cpp files (our c++ library)
The ./src/myLib/algorithm1.hpp file:
#pragma once
#include <cstddef>
namespace myLib
{
// For demo purpose: dest <- alpha.source
void algorithm1(const double alpha,const double* source, double* dest, const size_t n);
}
The ./src/myLib/algorithm1.cpp file:
#include "myLib/algorithm1.hpp"
namespace myLib
{
void algorithm1(const double alpha, const double* source, double* dest, const size_t n)
{
for (size_t i = 0; i < n; i++)
{
dest[i] = alpha*source[i];
}
}
} // namespace myLib
The libmyLibMMA.so cpp files (our MMA wrapper)
The ./mma/myLib_mma.cpp file:
#include "WolframLibrary.h"
#include "WolframSparseLibrary.h"
extern "C" DLLEXPORT mint WolframLibrary_getVersion() { return WolframLibraryVersion; }
extern "C" DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { return LIBRARY_NO_ERROR; }
extern "C" DLLEXPORT void WolframLibrary_uninitialize(WolframLibraryData libData) { return; }
The ./mma/algorithm1_mma.cpp file:
#include <cassert>
#include <iostream>
#include "WolframLibrary.h"
#include "WolframSparseLibrary.h"
#include "myLib/algorithm1.hpp"
//----------------
// TODO replace asserts by MMA error message
extern "C" DLLEXPORT int algorithm1(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res)
{
// alpha
const double alpha = MArgument_getReal(Args[0]);
// source vector
const MTensor src_tensor = MArgument_getMTensor(Args[1]);
assert(libData->MTensor_getType(src_tensor)==MType_Real);
const mint src_rank = libData->MTensor_getRank(src_tensor);
assert(src_rank == 1);
const mint* const src_dims = libData->MTensor_getDimensions(src_tensor);
const double* const src_data = libData->MTensor_getRealData(src_tensor);
// dest vector
MTensor dest_tensor;
const mint dest_rank = src_rank;
const mint dest_dims[] = { src_dims[0] };
libData->MTensor_new(MType_Real, dest_rank, dest_dims, &dest_tensor);
double* const dest_data = libData->MTensor_getRealData(dest_tensor);
// call our libmyLib.so function
myLib::algorithm1(alpha,src_data,dest_data,src_dims[0]);
MArgument_setMTensor(Res, dest_tensor);
return LIBRARY_NO_ERROR;
}
Useful references
- Doing nothing with LibraryLink certainly the place to begin with if
you do not know LibraryLink yet. - a short but instructive video about LibraryLink some tips (youtube video)
- Wolfram LibraryLink User Guide (official)
- https://github.com/arnoudbuzing/wolfram-librarylink-examples
- NumericArray—Compact Representation of "Numeric" Arrays (youtube video)
Note: I also have written a bash-script to generate pure C++ project
templates (configured with gtest and doxygen): meson_starter_script.