Why should I use CMake?

Superficially CMake just creates Makefiles but since I do that myself I’m not sure what I gain by using it.
At the moment my Makefiles support two different compilers and two types of build but I can’t see how to do that with CMake.

I have tried many times to use them and it almost always ends in failure. CMake seems to add an arcane layer of indirection that makes it very difficult to control which compiler will be used with which options.

3 Likes

CMake is interesting if you want to distribute your projects on various systems. It will for example detect if there is a Fortran compiler installed. It’s a very powerful tool.

If you don’t want to use the default compiler (f95 link on a Linux system), you can easily tell CMake which compiler you want, for example:

$ cmake -D CMAKE_Fortran_COMPILER:FILEPATH="/opt/intel/bin/ifort" ..
$ cmake -D CMAKE_Fortran_COMPILER:FILEPATH=$(which gfortran-9) ..

As you can see, a default of CMake, in my opinion, is that it is quite verbose…

Default build type is release, but you can override it:

$ cmake -D CMAKE_BUILD_TYPE=debug ..

In that file, you will find how to use some other variables, like CMAKE_Fortran_FLAGS_DEBUG:
https://github.com/vmagnin/gtk-fortran/blob/gtk4/CMakeLists.txt

Meson is easier to use, but does not yet manage correctly the installation of Fortran mod files:

And of course, there is now fpm…

2 Likes

I agree that CMake has drawbacks and am unable to use it on Windows. It would be nice if a project that provides CMake files also provides make files for Windows and other common platforms. Even a simple script listing the order of compilation of files to create an executable is useful.

In general, Makefiles are not platform-independent, as there is no universal Makefile standard. That may not be a problem for simple Fortran projects but you probably want to use a build automation tool like CMake, fpm, xmake, Meson, Ninja, … once your project reaches a certain complexity, and especially if you want to cross-build for multiple operating systems.

With CMake, you can force a specific Fortran compiler from the command line, for example:

$ mkdir build && cd build/
$ cmake -DCMAKE_Fortran_COMPILER=ifort ..

Or, see this more verbose example.

1 Like

Could you explain the difficulties you are encountering? I use it a lot on Windows (all of plain Windows, Cygwin and MinGW in fact) and have found it easy enough to use. The alternative in the PLplot project, where I learned about CMake, was autotools on Linux/MacOS and hadcrafted makefiels on Windows, which was a pain to maintain. Not that CMake is easy, but at least it provides a single configuration/build system for al lthese platforms.

1 Like

The nonsensical idea that “f95” is the default Fortran compiler is only the beginning. We have found that cmake is riddled with errors for use with Fortran and strongly discourage its use.

1 Like

See also: https://twitter.com/CmakeHate

4 Likes

I earlier linked to my problem: Cannot build using cmake with gfortran on Windows – the Fortran compiler identification is unknown.

When I had to take charge of the CMake files of gtk-fortran around 2015, I bought that book:
Ken Martin, Bill Hoffm, Mastering CMake - A Cross-Platform Build System. Kitware, 2010, ISBN 978-1930934313.
I read the 234 pages of the chapters, the 460 following pages being the reference manual. And that was very helpful…
I am satisfied with CMake for that project, as it can be built under Linux, macOS, FreeBSD, and MSYS2/Windows. It does the job. My only problem with it, is that its syntax is painful (2 miles long options…).

I tried to replace it with Meson, but was blocked by the .mod installation issue. Apart that problem, Meson is really impressive, with its Python-like syntax.

For personnal projects, I am happy with the actual fpm, and I am sure it has a bright future.

I am not the only programmer who refuses to read 200 pages about a build system :slight_smile: My homegrown build system is just
(1) list in a text the source files in proper order (considering module dependencies) that can be compiled to create an executable
(2) run a Python 2 script (below) that creates a make file for nmake.

""" create a make file given the name of a compiler and an executable and a list of source files
    handles g95, gfortran, NAG, and ifort """
from sys          import argv
from strip_suffix import strip_suffix
compiler   = argv[1]
list_file  = argv[2]
list_pref  = list_file[:3]
if compiler == "intel":
    compiler = "ifort"
# print "compiler = " + compiler
compile_and_link_only =  False # True #
write_bat  = True
make_file  = "mak_" + compiler + "_" + list_pref + ".txt"
outp       = open(make_file,"w")
indent     = "  "
try:
    fp         = open(list_file,"r")
except:
    list_file = list_file + "_files.txt"
    fp        = open(list_file,"r")
nag        = "nf95"
src_suffix = ".f90"
suff_strip = src_suffix
test_gfortran = False
if (compiler == "g95"):
    # warnings turned off:
    # 112 Variable is set but never used
    # 167 PRIVATE module procedure is not invoked
    # warnings treated as errors:
    # 100 Unexpected end of format string in format string
    # 113 Variable is used but not set
    # 115 Line is being truncated
    # 137 Variable is never used and never set
    # 146 Variable is set and never allocated
    # 147 Variable is used and never allocated
    # 159 Parameter is never used
    # 163 Actual argument does not have an intent
    opt         = "-Wall -Wextra -Wimplicit-none -Werror=100,113,115,137,146,147,159,163 -ftrace=full -fbounds-check -freal=nan -fmodule-private -Wno=112,167"
    cmpl_abbrev = "g95mak" # compiler
elif (compiler == "gfortran"):
    if (test_gfortran):
        opt = "-O0" # "-Wall -O3 -fbounds-check -g"
    else:
        opt = "-O0 -Wall -Werror=unused-variable -Werror=unused-function -Wno-surprising -fbounds-check -static -g -fmodule-private"
        # -Wno-suprising option used to avoid messages such as
        # Warning: Array 'x' at (1) is larger than limit set by '-fmax-stack-var-size=', moved from stack to static storage. This makes the procedure unsafe when called recursively, or concurrently from multiple threads. Consider using '-frecursive', or increase the '-fmax-stack-var-size=' limit, or change the code to use an ALLOCATABLE array. [-Wsurprising]
    cmpl_abbrev = "gfort"
elif (compiler == "ifort" or compiler == "intel"):
    # opt = "/Od /nologo /traceback /check:all /warn:all /warn:unused"
    # /check:all triggers bug in Intel Fortran 9.1.028 -- see message
    # "problem with allocatable array of derived types" in Intel Visual Fortran Forum
    opt = "/fast /nologo /traceback /check:bounds /check:uninit /warn:all /warn:unused /gen-interfaces /warn:interfaces /F512000000"
    cmpl_abbrev = "ifort"
elif (compiler == "lf95"):
    opt = "-nco -chk -g -stchk -trace -nsav -trap diou -lst -w -o0"
    cmpl_abbrev = "lf95"
elif (compiler == nag):
    opt = "-C -C=undefined -nan -w=all -gline -info"
    cmpl_abbrev = compiler
try:
    exe_name = argv[3]
except:
    exe_name = list_pref + "_" + cmpl_abbrev + ".exe"
srcs = fp.readlines()
if (compiler == "g95" or compiler == "gfortran" or compiler == nag):
   out_opt    = "-o "
   obj_suffix = ".o"
elif (compiler == "ifort" or compiler == "intel"):
   out_opt    = "/exe:"
   obj_suffix = ".obj"
elif (compiler == "lf95"):
   out_opt = "-out "
   obj_suffix = ".obj"
compiler += " "
del_first = True
print>>outp, "exec = " + exe_name
print>>outp, "obj  = ",
for xfile in srcs:
    xfile = strip_suffix(xfile.strip(),src_suffix)
    if (not xfile):
        break
    print>>outp, xfile + obj_suffix,
print>>outp, "\nopt  = " + opt
if (compile_and_link_only):
    print>>outp, "\nall: clean $(exec)\n"
else:
   print>>outp, "\nall: clean run\n"
print>>outp, src_suffix + obj_suffix + ":"
if (del_first):
    print>>outp, "  if exist $*" + obj_suffix + " del $*" + obj_suffix
print>>outp, "  " + compiler + "-c $(opt) $*" + src_suffix
print>>outp, "\n$(exec): $(obj)"
print>>outp, "  " + compiler + out_opt + "$(exec) $(opt) $**"
print>>outp,"\nrun: $(exec)"
print>>outp,"   if exist $(exec) $(exec)"
print>>outp,"\nclean:"
print>>outp,"   if exist $(exec) del $(exec)"
print "created make file",make_file
if (write_bat):
    bat_file = cmpl_abbrev.strip() + list_pref + ".bat"
    batp = open(bat_file,"w")
    print>>batp,"@echo off"
    print>>batp,"nmake /nologo /f",make_file
    print "created batch file",bat_file

The script above, for a list file

kind.f90
get_unit.f90
util.f90
xread_matrix_unit.f90

produces a make file

exec = rmu_gfort.exe
obj  =  kind.o get_unit.o util.o xread_matrix_unit.o 
opt  = -O0 -Wall -Werror=unused-variable -Werror=unused-function -Wno-surprising -fbounds-check -static -g -fmodule-private

all: clean run

.f90.o:
  if exist $*.o del $*.o
  gfortran -c $(opt) $*.f90

$(exec): $(obj)
  gfortran -o $(exec) $(opt) $**

run: $(exec)
   if exist $(exec) $(exec)

clean:
   if exist $(exec) del $(exec)

File strip_suffix.py, used in the Python 2 script above, is just

def strip_suffix(string,suffix):
    if (string.endswith(suffix)):
        return string[:-len(suffix)]
    else:
        return string
2 Likes

I bought the ebook Professional CMake. If you buy the book once you also get updates for new versions of CMake. I read the first few chapters (the entire book is 538 pages), but ultimately I figured it will be easier to just copy/steal from other Fortran projects using CMake available on GitHub and adapt them to my needs.

A downside of most CMake content is it is not written with Fortran programmers in mind. The book I mention says nothing about how to deal with Fortran modules.

The biggest benefit of CMake vs make for me is it knows how to automatically figure out the source code dependencies, without needing to specify them manually or define a bunch of rules.

I’ve also used it succesfully to build mixed-language projects (C, C++, and Fortran), even though I hate figuring out how it works each time.

2 Likes

If your project is small and simple (i.e. less than half a dozen source files), and you don’t intend for anyone else’s Fortran code to depend on it, use whatever makes sense and is easiest for you.

If you have need of some feature or complexity that fpm doesn’t support (which is shrinking all the time), use CMake. For example I’ve had decent luck with CMake for complex testing requirements that fpm wouldn’t have been able to accommodate as easily.

For everything else, use fpm. It makes life sooooo much easier.

4 Likes

Of all the suggestions for how to tell CMake which compiler to use, nobody mentioned that it checks the FC environment variable. So in most shells FC=ifort cmake -B build works.

Also, my understanding is that the default build system in Windows Command Prompt is Ninja, not Make.

1 Like

I have used the Python script I presented earlier to create many make files for programs consisting of dozens of source files, for several compilers. A modification of the script generates make files compatible with GNU Make.

Yes, it’s important to notice CMake can use Ninja:

$ cmake -GNinja ..
$ ninja

I have just done the following experiment:

  • Install the GCC compiler suite from equation.com
  • Open a command window (DOS box, nothing fancy like PowerShell)
  • Set the path to include d:\equation\bin
  • Checked that gfortran was found
  • Ran CMake in a suitable build directory with the command:
    cmake -G “Unix Makefiles” …
  • CMake found the gfortran executable from equation.com and the make utility from that installation did the rest

Here is the output:
d:\fortran\regexp\build>d:\cmake-3.17.2-win64\bin\cmake -G “Unix Makefiles” …
– The Fortran compiler identification is GNU 10.2.0
– Check for working Fortran compiler: D:/equation/bin/gfortran.exe
– Check for working Fortran compiler: D:/equation/bin/gfortran.exe - works
– Detecting Fortran compiler ABI info
– Detecting Fortran compiler ABI info - done
– Checking whether D:/equation/bin/gfortran.exe supports Fortran 90
– Checking whether D:/equation/bin/gfortran.exe supports Fortran 90 - yes
– Configuring done
– Generating done
– Build files have been written to: D:/fortran/regexp/build

d:\fortran\regexp\build>make
Scanning dependencies of target fortran_regex
[ 20%] Building Fortran object CMakeFiles/fortran_regex.dir/src/regex_state_mod.F90.obj
[ 40%] Building Fortran object CMakeFiles/fortran_regex.dir/src/regex_parser_mod.F90.obj
[ 60%] Linking Fortran static library libfortran_regex.a
[ 60%] Built target fortran_regex
Scanning dependencies of target regex_test.exe
[ 80%] Building Fortran object CMakeFiles/regex_test.exe.dir/src/regex_test.F90.obj
[100%] Linking Fortran executable regex_test.exe.exe
[100%] Built target regex_test.exe

So it seems CMake is capable of finding the gfortran executable and build a project with it even outside of Cygwin or MinGW-w64/MSYS2 (the two platforms I use gfortran on when running Windows).

What is the difference with the set-ups that caused all these problems? (I admit that the project I tested this with is a very simple one, but still)

2 Likes

Sure, that’s why Meson is presented like this (https://mesonbuild.com/):

Meson is an open source build system meant to be both extremely fast, and, even more importantly, as user friendly as possible.
The main design point of Meson is that every moment a developer spends writing or debugging build definitions is a second wasted. So is every second spent waiting for the build system to actually start compiling code.

Could be also a good presentation of fpm…

If it’s working for you and you aren’t experiencing significant downsides, then by all means keep doing what you’re doing.

I will note that from a quick scan of your script and makefile, it doesn’t actually appear to guarantee correct order of compilation (i.e. your .o files don’t depend on .mod files).

1 Like

I’m going to have to try this setup out. It sounds like the simplest way I’ve seen to get a consistent Windows environment for people who’ve been developing solely in Visual Studio with Intel.

I can see how that might be an advantage but my experience is that CMake doesn’t always detect the installed compiler correctly. In my current setup I just use
make intel=t
since there are different options for different compilers.
The requirement for another tool in the toolchain needs justification (since it’s another point of failure). And it isn’t only Fortran that is problematic.
Last year I tried building covid-sim (from github) which is a C++ project. I tried again this afternoon and it still fails:
[simon@localhost build (master)]$ make -C …
make: Entering directory ‘/home/simon/development/projects/c19/covid-sim’
make[1]: Entering directory ‘/home/simon/development/projects/c19/covid-sim’
make[2]: Entering directory ‘/home/simon/development/projects/c19/covid-sim’
make[2]: Leaving directory ‘/home/simon/development/projects/c19/covid-sim’
make[2]: Entering directory ‘/home/simon/development/projects/c19/covid-sim’
[ 4%] Building CXX object Geometry/CMakeFiles/geometrylib.dir/Vector2.o
/home/simon/development/projects/c19/covid-sim/src/Geometry/Vector2.cpp:1:10: fatal error: geometry/Vector2.h: No such file or directory
#include “geometry/Vector2.h”
^~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [Geometry/CMakeFiles/geometrylib.dir/build.make:63: Geometry/CMakeFiles/geometrylib.dir/Vector2.o] Error 1
make[2]: Leaving directory ‘/home/simon/development/projects/c19/covid-sim’
make[1]: *** [CMakeFiles/Makefile2:128: Geometry/CMakeFiles/geometrylib.dir/all] Error 2
make[1]: Leaving directory ‘/home/simon/development/projects/c19/covid-sim’
make: *** [Makefile:84: all] Error 2
make: Leaving directory ‘/home/simon/development/projects/c19/covid-sim’

No doubt the mistake is mine but the point is that this is really obscure and yet it is easy to build this program on the command line. It’s also easy to create a makefile with all the correct dependencies, and it is also easy to write a bash script that will create such a makefile.
I don’t agree that makefiles are just okay for small projects. My experience is that they’re good for larger projects spread over many directories and building on different platforms.