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
- <Tue Mar 26 2019> added support for MMA v10 (before only v11 was supported)
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.so
a regular C++ library (totally independent of MMA)libmylibMMA.so
a dynamic library that uses MMA's LibraryLink to wrap functions contained inlibmyLib.so
.The
libmyLib.so
install directory is defined by the--prefix
flag, with--prefix=$(pwd)/install_dir
this is/home/picaud/GitHub/mma_meson_demo/install_dir/lib/x86_64-linux-gnu
in my case.
By default
libmylibMMA.so
is installed in the user directory$HOME/.Mathematica/SystemFiles/LibraryResources/Linux-x86-64
in my case.
The important point to understand is that
libmylibMMA.so
needs to know where thelibmyLib.so
library is installed. We provide this information by embedding it into thelibmylibMMA.so
rpath
section. This section is filled by Meson at installation time. By consequence to make it works we must define this installation path.If the
--prefix
variable 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
).
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.so
orlibmylibMMA.so
C++ 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
myLib
package, you do not need this function.
I have no access to Windows. It would we nice to have some feedback concerning this platform:
- Mathematica + Linux
- Mathematica + MacOS
- Mathematica + Windows ???
#----------------
# 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.
As explained, this is a regular C++ Meson project:
myLib_inc = include_directories('.')
subdir('myLib')
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')
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!
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]
This is really for demo purpose as we simply compute a scalar-vector product w=α.v
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 ./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;
}
- 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.