Building OpenFOAM with the Meson build system Volker Weißmann volker.weissmann@gmx.de https://weissmann.pm No company or university, just too much time
So ... I'm Volker Weißmann. I will talk about how I build OpenFOAM with meson instead of wmake and about how we could merge my work into the openfoam repo, so that other people can use meson to build OpenFOAM. Gnu Make make is a program, not affiliated with OpenFOAM or me, that reads $CWD/Makefile
(and usually also $CWD/subdir/Makefile
) and if any of these files contains e.g.
foo.o: foo.c
gcc foo.c -c -o foo.o
it runs
gcc foo.c -c -o foo.o
Last time we spoke I noticed that some people misunderstood which software is responsible for what, so I want to clear those uncertainties up, so let's just go throught the definitions real quick.
gnu make also known as make is a program. It was not written by the OpenFOAM people or by me. And if you start it, it searches for a file called Makefile in the current directory. If the Makefile contains something like this for example, it will call gcc like this. Most projects have their makefile recursively include makefiles in subdirectories and that is not considered bad code or anything. Ninja ninja is a program, not affiliated with OpenFOAM or me, that reads $CWD/ninja.build
and if $CWD/ninja.build
contains e.g.
rule cc
command = gcc $in -c -o $out
build foo.o: cc foo.c
it runs
gcc foo.c -c -o foo.o
ninja is basically a Make-alternative or competition to make. It's also not from the OpenFOAM people or me, but from a third party. Similar to how make reads the Makefile ninja reads ninja.build, which basically contains the same thing but with a different syntax and then for example calls gcc like that. I said before that most make-based projects have multiple makefiles, that's not how it works with ninja, you have just one big ninja.build file that contains everything. CMake cmake is a program, not affiliated with OpenFOAM or me, that reads $CWD/CMakeLists.txt
(and usually also $CWD/subdir/CMakeLists.txt
) and if these files contain something like
add_executable(example foo.c)
it will create the Makefile
's or the ninja.build
-file we talked about.
cmake -B builddir -G "Unix Makefiles"
cmake -B builddir -G Ninja
cmake is another third party program, and it reads the CMakeLists.txt files, but it does not call gcc, instead it generates Makefiles or ninja.build files and if you then run make or ninja it calls gcc. Meson Meson is a program, not affiliated with OpenFOAM or barely affiliated me, that reads $CWD/meson.build
(and usually also $CWD/subdir/meson.build
) and if these files contain something like
executable('example', 'foo.c')
it will create the ninja.build
-file we talked about.
meson setup builddir
Meson is the last third party program I'm gonna talk about. It's basically like cmake, but if you ask me, it's better. Unlike cmake, meson does not have a make backend, you have to use ninja, xcode or visual studio. The Makefile
's and ninja.build
-file generated by cmake or meson cannot be moved to another machine. Contain absolute paths to the source directory Detection which optional dependency exits happens when ninja.build
is generated You cannot commit these generated files to the git repo. Anyone who wants to build $project needs not just make and ninja, but also cmake, meson and python installed. One thing to be keep in mind is that the output generated by cmake or meson is machine dependent. So if you run cmake or meson on one machine, then move the generated makefile or build.ninja file to another machine, your build will fail. And this is very much by design and cannot be changed. Therefore, we cannot commit the generated files to the git repo. I mean we can, but it wouldn't be very useful. We have to commit the CMakeFiles.txt or meson.build files to the repo instead. Does that make sense to you? OpenFOAM, status quo
cd openfoam
source etc/bashrc
./Allwmake
cd src/surfMesh
wmake
source etc/bashrc
puts /path/to/openfoam/wmake
in $PATH
which wmake
will print /path/to/openfoam/wmake/wmake
./Allwmake
calls wmake
Now before I explain how the code works that I wrote, I want to explain how the code works that I want to replace. I'm sure you already know that, but I still wanna explain it.
If you want to build openfoam you need to source etc/bashrc which will set some enviromental variables. Then there is a script in the repo called Allwmake, that you need to call which will, among other things, go into each subdirectory and calls wmake. You can also go into any directory and execute wmake directly.
OpenFOAM, status quo wmake
calls
make -f /path/to/openfoam/wmake/makefiles/general
wmake/makefiles/general
contains
include $(OBJECTS_DIR)/options
src/surfMesh/Make/options
EXE_INC = \
-I$(LIB_SRC)/fileFormats/lnInclude
LIB_LIBS = \
-lOpenFOAM \
-lfileFormats
src/surfMesh/Make/files
MeshedSurfaceAllocator/MeshedSurfaceIOAllocator.C
MeshedSurface/MeshedSurfaceCore.C
MeshedSurface/MeshedSurfaces.C
LIB = $(FOAM_LIBBIN)/libsurfMesh
wmake is a bash script that is written and maintained by the OpenFOAM project. And it will call gnu make with this extra flag here, which will make make use this file called "general" instead of searching for a file called Makefile in the current directory.
This "general" file includes Make/options of the directory you are building. So if you are building src/surfMesh, it will include this path. It also somehow, through a mechanism I don't completely understand, read Make/files.
These two files contain information on what to build and how to build. And gnu make will then call gcc with the appropriate options.
OpenFOAM, what I did I wrote a python script.
Reads */Make/files and */Make/options Writes meson.build files to the openfoam source tree Machine independent output
git clone https://develop.openfoam.com/Development/openfoam
git clone https://codeberg.org/Volker_Weissmann/foam_meson
cd foam_meson
./generate_meson_build.py ../openfoam
cd ../openfoam
meson setup some_path
cd some_path
ninja
meson devenv # Launches a subshell
cd ../tutorials/basic/laplacianFoam/flange
./Allrun
Now we come to the main part, where I explain my project. So what I did is that I wrote a python script called generate_meson_build.py. Now the very high level description of what my script does, is that it reads all of those Make/files and Make/options files and writes meson.build files to the openfoam source tree. The output is machine independent, so if you run my script on different operating systems you will get the same meson.build files, as long as the openfoam repository is identical.
So If you want to run it, you can clone the official openfoam repository, and the repo of my script. Then run my script and pass the path to the openfoam repository as a command line option. After that you will find that there are meson.build files inside the openfoam directory. And you can use "meson setup some_path" to generate a build.ninja file, then you can use ninja to actually build openfoam. In this whole process we have never sourced etc/bashrc. Running "meson devenv" sets some environmental variables, similar to sourcing etc/bashrc. In this subshell you can run the openfoam software you just build and it should work. Note that after running generate_meson_build.py we don't need or read the wmake related files like Make/files Make/options or anything in the wmake folder.
Are there any question at this point?
Dependencies and Installation ninja
Packaged in every major distro Also available as a single binary Depends on libc, libm, libstdc++, gcc-libs meson
We need 0.59.0 minimum (2021-07-18) Packaged in every major distro pip install meson git clone https://github.com/mesonbuild/meson/ curl https://github.com/mesonbuild/meson/releases/download/0.59.0/meson-0.59.0.tar.gz ./meson.py Newest meson version needs Python 3.7 or greater (2018-06-27) Meson version 0.59.0 needs Python 3.6 or greater (2016-12-23) Now let's talk about a more practical topic. If you decide to adopt my proposal, everyone who builds openfoam using meson needs to install ninja and meson. Ninja is quite unproblematic. I don't even know the minimum version we need, I never encountered a version that is too old.
For meson, we need 0.59 as a minimum. That version is 3 years old now. The simplest way to install meson is through your package manager, but if your distro gives you a meson version that is too old, you can install meson using pip, or either clone the repository or download the tarball. Then you can just run the meson.py directly, without installing or anything.
Meson has a python dependency, but an old python installation will work just fine.
Dependencies and Installation foam_meson git clone https://codeberg.org/Volker_Weissmann/foam_meson ./generate_meson_build.py Python 3.6 or newer Make
print_stuff:
echo $(LIB_INC)
echo $(EXE_INC)
echo $(LIB_LIBS)
echo $(EXE_LIBS)
If anyone wants to run my script, the installation process is quite harmless, you just clone the repo and run it. There is a python and a make dependency. The reason for the make dependency is that parsing Make/options is kinda tricky. So we create a temporary file that contains this code snippete here and the contents of Make/options. Then we run "make print_stuff" so make prints out the variables that we are interested in. If that confuses you, just ignore it it's not that important.
Meson description More high-level than Gnu Make Much logic is moved from the Makefiles inside of meson itself instead of meson.build Meson wants to take control more than Gnu Make does Hacks are hard (e.g. lnInclude, .C. vs .cpp), escape hatches are limited meson.build is not turing complete Can't add your own abstraction layer Way more opinionated than Gnu Make One of the biggest differences between meson and make is that meson is much more high level. So a lot of logic that is inside of your Makefile if you use Gnu Make is not inside of meson.build if you use meson, but inside the source code of meson itself. This is both a good and a bad thing. One one hand, it's a good thing because it means meson knows more about your project than gnu make does, so meson can do more stuff for you than gnu make can. Also, it means less work when writing meson.build files. And you have less oportunity to mess up when writing meson.build files. And it's easier to read meson.build files. On the other hand, it's a bad thing since you are basically in a padded cell and meson thinks you are an idiot. If you are searching for escape hatches to implement some weird hack since your project might have different requirements than other projects you kind of have a problem. For example, openfoam uses .C instead of .cpp for the C++ files and that required me to file a PR that only got merged after some debate. Another example is how openfoam has this lnInclude symlinks. If you interested in it I can talk more about it, but let's just say it took me really long to find a solution and the solution is not very idiomatic code. The language for meson.build is purposefully non-turing-complete and you can't really add your own abstraction layers in meson files. To summarize, my exprience is that meson is great if the project works the way meson thinks all projects work, but not so great if the project works differently. In other words, meson is very opinionated. Why switch to meson Let's say someone is somewhat familiar with meson, but completely new to openfoam. He will have a very easy time doing $thing with build. With $thing being e.g.
IDE Integration Adding an optional dependency ... The same cannot be said about gnu wmake.
Unlike wmake, Not the only project using meson Googling "How to do $thing in meson" gives more results than "How to do $thing in wmake" meson.build files are very easy to read Now why do I think that switching to meson is a good idea? Probably the biggest advantage would be that if someone is familiar with meson but completely new to openfoam, he knows how to do all kinds of stuff with the build. Because while wmake is just used by the openfoam project, meson is used by other projects as well and is well documented. Also the meson.build files are genuinly easy to read, even for large real world projects. Which is an amazing accomplishment by the meson project. Why switch to meson meson setup
generates compilation_commands.json
-> good for IDE support If nothing has changed: ninja
takes 2-5 seocnds, ./Allwmake
takes over a minute Meson is better at knowing what needs to be rebuild Running wclean
is sometimes required If you change WM_COMPILE_OPTION, wmake does not rebuild anything ninja can build a single binary Meson errors out immediately if a dependency is missing Meson can do out of tree builds Make interleaves the output of multiple threads Source code of meson seems good There are some other nice things about meson as well: If you run meson setup, which btw. only takes a minute or something like that, you get a compilation_commands.json which telsl your IDE how Openfoam gets compiled and enables a better "Go To Definition" in your IDE.
Partial rebuils are faster with ninja than with Allwmake.
Meson is also better at knowing what needs to be rebuild. If a shared library changes, dependents of that shared library are only rebuild if the exported symbols change.
Also, it happened to me a few times that I needed to manually call wclean, because wmake messed it up. That wasn't a very nice experience. Something similar never happened to me with meson.
If you change WM_COMPILE_OPTION wmake does not rebuild anything, while meson knows what to rebuild if you change the equivalent option in wmake.
If you tell ninja to just build one of the many openfoam binaries, it will build all libraries that are needed for this openfoam binary, but no libraries that aren't needed. So if you just need a few binaries, you don't need to do a full rebuild. Be aware that that advantage is partially negated by the fact that nearly every binary depends on libOpenFOAM.so and that library takes very long to build.
If you try to build openfoam on a machine where a required dependency is missing, meson will error out immediately, but wmake will start building and then after and hour or so complain.
With meson you can do out of tree builds, so you can for example easily build the same source code with different configurations at different paths. And you can build openfoam even if you have no write permissions to the source directory.
One detail that annoys me about make is that if run make -j, every thread prints everything immediately, so you see the output interleaved, which can be confusing. Ninja buffers the outputs and then prints them without interleaving.
I contributed to the meson project and I worked with the source code of meson, and I would say that the quality of the source code is quite good. In general I would say that meson is better, cleaner, less buggy, more modern and has more features than the alternatives.
About me Master in Physics Young (25) with lots of Energy Good at C++, Python, debugging Know a lot about Linux, tooling and the C++ ecosystem watch talks, read blogposts, talk with other hackers Limited knowledge about using OpenFOAM Limited knowledge about OpenFOAM's source code Next to no knowledge about OpenFOAM's organisational structure and development process Next to no community connection (4th Openfoam Meeting) Started as a proof of concept when I was young and stupid No idea how to merge Now that I told you what's great about the project, we need to talk about the big problem this problem has. And to understand that problem we need to talk about who I am. The good thing is that I have a Master in Physics, I'm young and full of energy, I know enough about C++ or Python or Linux or Build Systems, so if you show me a bug I can fix it even if its hard and I can definitely plan and write software. But I have very little experience in using OpenFOAM or developing OpenFOAM. And I have next to no knowledge about how OpenFOAM's organisations structure and development process work. I'm unfortunately very much an outsider, for example this is only my 4th openfoam meeting ever.
This whole project started when I was young and stupid and I learned about meson and I decided to just try it out without any planning on how we could transition the project form wmake to meson. It is a nice proof of concept, and it works, but I have no idea on how to manage such a project. So I hope you can integrate me into the community, and teach me how project management stuff works.
Important General principle in Programming Let's say you have stuff in format/language $foo, you want format/language $boo, and you have a tool that translates $foo to $bar.
Translate your stuff to $bar, use it, be happy
What if you want to change something? You have 3 options
Manually change it in the $foo-code and rerun the tool Write a program that changes the $bar-code Manually change the $bar-code If you do option 3, you cannot do option 1 ever again, at least not with manual labor or wiping your changes!
Before we talk about how we could merge my changes upstream and transition from wmake to meson, I want to explain one thing that I think is important that everyone understands. So let me do that very slowly because abstract concepts are often difficult to put into words and it's important that you 100% intuitively understand this.
So lets say you have some stuff in some format or programming language called $foo but you want it in some other format or programming language called bar but you have tool that translates one format or language into the other. Then you translate your stuff and your done.
But what if sometime later you realize that your foo-code is not what it should be. Then you have 3 options. You can either use your favorite text editor to update the foo-code and rerun the tool. Or you can write a program that modifies the bar-code and run the progrem. Or you can use your favorite text editor to manually change the bar-code. All of those work, but if you do option 3 once, and you ever want to do option 1 again, you have a problem. Does everyone here understand what the problem is?
Example: You have main.c
, i.e. C-Code, but your CPU wants x86 assembly
gcc can translate C-Code to x86 assembly
Change main.c
using a text-editor, then rerun-gcc E.g. strip Changing the generated assembly, i.e. main.s
, using a text-editor If you do option 3, and rerun gcc it wipes your changes.
So let's make an example. $foo is the C language and $bar is assembly code and the tool is the gcc. During development you change main.c using a text editor then rerun gcc. Stripping the binary would be an example of option 2. And you could also change the generated assembly using a text editor. But if you do that, you cann't really touch main.c anymore. Do you understand that? In our case We have many Make/options
and Make/files
generate_meson_build.py
reads that and outputs meson.build
files
Let's say we add another .C-file to openfoam. Two options:
vim subdir/meson.build
vim subdir/Make/files
and re-run generate_meson_build.py
Now in our case, the $foo-format are the Make/options and Make/files, the $bar-format are the meson.build files and the converting tool is my generate_meson_build.py. Now let's say that one day we will add another .C-file to openfoam. Then we could either directly add the new file to meson.build, that would be option 3 from the slides before, or we update Make/files and rerun generate_meson_build.py that would be option 1 from the slides before. The option 2 from the slides before probably won't be really practical. So when we discuss on how to transition openfoam from wmake to meson, we need to decide when to use which of these two options, and keep in mind that if you do option 3 and then you want to do option 1, you have to do some extra work. Now before you say that option 1 is better, there is one thing you need to know. Heuristics in generate_meson_build.py
src/OSspecific/POSIX/Make/files
:
#ifdef __sun__
printStack/dummyPrintStack.C
#else
printStack/printStack.C
#endif
src/OSspecific/POSIX/meson.build
if host_machine.system() == 'sunos'
srcfiles += files('printStack/dummyPrintStack.C')
else
srcfiles += files('printStack/printStack.C')
endif
The first snippet exist as a string-literal in my script. It's that my generate_meson_build.py script is full of hardcoded heuristics. For example, my script needs to translate the first snippet into the second snippet. And the way we do that, is by having the first snippet as a string literal and searching for this string literal. Let's say someone changes
#ifdef __sun__
printStack/dummyPrintStack.C
#else
printStack/printStack.C
#endif
to
#ifndef __sun__
printStack/printStack.C
#else
printStack/dummyPrintStack.C
#endif
generate_meson_build.py
will crash!
You have to fix/change/adapt generate_meson_build.py
So if you for example change it like this, you haven't change the meaning of the file, but my script will crash since we're not able to parse arbitrary preprocessor statement. If you would want to make this change, then you would need add the new snippet as a string literal generate_meson_build.py. Creating valid C-Code that gcc cannot correctly translate is hard.
Creating valid Make/options
and Make/files
that my script cannot correctly translate is easy.
This will never change!
So in other words, the difference between the example with C and assembly code and our case is that while it's very hard to find valid C-code that gcc cannot translate, it's almost trivial to create valid Make/options and Make/files files that crashes my script. And this will never change. Let's say we add another .C-file to openfoam. Two options:
vim subdir/meson.build
vim subdir/Make/files
and re-run generate_meson_build.py
Option 3 can be done by basically anyone. You will only rarely run into any issues. Basic knowledge of openfoam and meson is enough, no knowledge of my script is required.
Option 1 might result in you getting a stacktrace of my script and you need to read, understand and modify my script. Only I, or people who want to co-maintain my script, or people that have me on speeddial should run my script. (At least if we want users to have a nice experience.)
The disadvantage of option 3 is that if you want both wmake and meson to keep working, you need to update two files.
So Option 3 is easy to do, since meson.build files are easy to read and since meson and the generated meson.build files have a good architecture, are mostly bugfree and do not contain heuristics you will only rarely encounter any issues. And you don't need to run or read my python script.
But option 1 is a bit more tricky. Since my script is essentially a heuristic, this heuristic might break with openfoam updates and then you get a stacktrace of my script and you need to work on it. So I can't be too far away from whoever runs this script.
On the other hand, the disadvantage of option 3 is that if we want both wmake and meson to keep working for a certain transition period, we need to do twice the maintenance.
Seperately compiled libraries Let's say you have dust that is hitting the wall and rebounding.
openfoam/src/lagrangian/intermediate/submodels/Kinematic/PatchInteractionModel/Rebound/Rebound.C
// Calculate motion relative to patch velocity
U -= Up;
scalar Un = U & nw;
if (Un > 0.0)
{
U -= UFactor_*2.0*Un*nw;
}
// Return velocity to global space
U += Up;
Let's say you want to change the physics for that. My project is not feature complete yet, but most of the missing features are stuff I am confident I can implement, so I only want to talk about one feature of wmake that will be very hard to match with meson. I'm gonna explain the usecase of this feature with an example that happened to me when I was working for the university. We simulated an airflow that carries dust-particles. And if those dust-particles hit a wall they rebound. The code for the rebounding physics can be found in this file here. But we wanted to implement a different rebounding physics. There are 3 options on how we could do that with wmake. Option 1 Download openfoam source code Modify src/lagrangian/intermediate/submodels/Kinematic/PatchInteractionModel/Rebound/Rebound.C
Build the modified openfoam One option would be to download the openfoam source code, modify the code that handels the rebound physics and then run Allwmake. Option 2 Download openfoam source code Go to src/lagrangian/intermediate/submodels/Kinematic/PatchInteractionModel
Copy Rebound
to MyRebound
Modify MyRebound/Rebound.C
Modify Allwmake
Build the modified openfoam Adjust your case to use MyRebound
instead of Rebound
Another option would be to download the openfoam source code, then copy and modify the Rebound folder and modify Allwmake so that the new MyRebound folder also gets build. Then you have an openfoam that has an additional PatchInteractionModel called MyRebound. Then you can use that model in your case. Option 3 Download openfoam source code and build it Copy the Rebound
folder from the openfoam source tree to MyRebound
outside of the openfoam source tree. Build just the MyRebound
folder Adjust your case to use MyRebound
instead of Rebound
Step 3 and 4 work without write permissions to the source tree, so openfoam source tree could be shared between many users Difficult to do something similar with meson There is also a third option you could do. You start by downloading the openfoam source code and building everything. Then you copy this Rebound folder somewhere outside the source tree. With wmake, you can then build just that one folder and use it. I don't completely understand why exactly the openfoam devs implemented this feature, but I think it has something to do with the fact that you can do the last two steps without write permissions to the source tree. I have not worked on implemented anything like that with meson, I could work doing that, but I would need to know more about what the requirements and the usecases are. End of Presentation Discuss if you want to merge. Discuss how we want to merge. Discuss how we handle the "externally build" MyRebound feature. Volker Weißmann volker.weissmann@gmx.de https://weissmann.pm No company or university, just too much time