# Using via `meson`

The key advantage gained by leveraging `meson` over the techniques described in Using via numpy.distutils is that this feeds into existing systems and larger projects with ease. `meson` has a rather pythonic syntax which makes it more comfortable and amenable to extension for `python` users.

Note

Meson needs to be at-least `0.46.0` in order to resolve the `python` include directories.

## Fibonacci Walkthrough (F77)

We will need the generated `C` wrapper before we can use a general purpose build system like `meson`. We will acquire this by:

    python -m numpy.f2py fib1.f -m fib2

Now, consider the following `meson.build` file for the `fib` and `scalar` examples from Three ways to wrap - getting started section:

    project('f2py_examples', 'c',
      version : '0.1',
      license: 'BSD-3',
      meson_version: '>=0.64.0',
      default_options : ['warning_level=2'],
    )

    add_languages('fortran')

    py_mod = import('python')
    py = py_mod.find_installation(pure: false)
    py_dep = py.dependency()

    incdir_numpy = run_command(py,
      ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
      check : true
    ).stdout().strip()

    incdir_f2py = run_command(py,
        ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
        check : true
    ).stdout().strip()

    inc_np = include_directories(incdir_numpy, incdir_f2py)

    py.extension_module('fib2',
      [
        'fib1.f',
        'fib2module.c',  # note: this assumes f2py was manually run before!
      ],
      incdir_f2py / 'fortranobject.c',
      include_directories: inc_np,
      dependencies : py_dep,
      install : true
    )

At this point the build will complete, but the import will fail:

    meson setup builddir
    meson compile -C builddir
    cd builddir
    python -c 'import fib2'
    Traceback (most recent call last):
    File "<string>", line 1, in <module>
    ImportError: fib2.cpython-39-x86_64-linux-gnu.so: undefined symbol: FIB_
    # Check this isn't a false positive
    nm -A fib2.cpython-39-x86_64-linux-gnu.so | grep FIB_
    fib2.cpython-39-x86_64-linux-gnu.so: U FIB_

Recall that the original example, as reproduced below, was in SCREAMCASE:

    C FILE: FIB1.F
          SUBROUTINE FIB(A,N)
    C
    C     CALCULATE FIRST N FIBONACCI NUMBERS
    C
          INTEGER N
          REAL*8 A(N)
          DO I=1,N
             IF (I.EQ.1) THEN
                A(I) = 0.0D0
             ELSEIF (I.EQ.2) THEN
                A(I) = 1.0D0
             ELSE
                A(I) = A(I-1) + A(I-2)
             ENDIF
          ENDDO
          END
    C END FILE FIB1.F

With the standard approach, the subroutine exposed to `python` is `fib` and not `FIB`. This means we have a few options. One approach (where possible) is to lowercase the original Fortran file with say:

    tr "[:upper:]" "[:lower:]" < fib1.f > fib1.f
    python -m numpy.f2py fib1.f -m fib2
    meson --wipe builddir
    meson compile -C builddir
    cd builddir
    python -c 'import fib2'

However this requires the ability to modify the source which is not always possible. The easiest way to solve this is to let `f2py` deal with it:

    python -m numpy.f2py fib1.f -m fib2 --lower
    meson --wipe builddir
    meson compile -C builddir
    cd builddir
    python -c 'import fib2'

### Automating wrapper generation

A major pain point in the workflow defined above, is the manual tracking of inputs. Although it would require more effort to figure out the actual outputs for reasons discussed in F2PY and Build Systems.

Note

From NumPy `1.22.4` onwards, `f2py` will deterministically generate wrapper files based on the input file Fortran standard (F77 or greater). `--skip-empty-wrappers` can be passed to `f2py` to restore the previous behaviour of only generating wrappers when needed by the input .

However, we can augment our workflow in a straightforward to take into account files for which the outputs are known when the build system is set up.

    project('f2py_examples', 'c',
      version : '0.1',
      license: 'BSD-3',
      meson_version: '>=0.64.0',
      default_options : ['warning_level=2'],
    )

    add_languages('fortran')

    py_mod = import('python')
    py = py_mod.find_installation(pure: false)
    py_dep = py.dependency()

    incdir_numpy = run_command(py,
      ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
      check : true
    ).stdout().strip()

    incdir_f2py = run_command(py,
        ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
        check : true
    ).stdout().strip()

    fibby_source = custom_target('fibbymodule.c',
      input : ['fib1.f'],  # .f so no F90 wrappers
      output : ['fibbymodule.c', 'fibby-f2pywrappers.f'],
      command : [py, '-m', 'numpy.f2py', '@INPUT@', '-m', 'fibby', '--lower']
    )

    inc_np = include_directories(incdir_numpy, incdir_f2py)

    py.extension_module('fibby',
      ['fib1.f', fibby_source],
      incdir_f2py / 'fortranobject.c',
      include_directories: inc_np,
      dependencies : py_dep,
      install : true
    )

This can be compiled and run as before.

    rm -rf builddir
    meson setup builddir
    meson compile -C builddir
    cd builddir
    python -c "import numpy as np; import fibby; a = np.zeros(9); fibby.fib(a); print (a)"
    # [ 0.  1.  1.  2.  3.  5.  8. 13. 21.]

## Salient points

It is worth keeping in mind the following:

- `meson` will default to passing `-fimplicit-none` under `gfortran` by default, which differs from that of the standard `np.distutils` behaviour

- It is not possible to use SCREAMCASE in this context, so either the contents of the `.f` file or the generated wrapper `.c` needs to be lowered to regular letters; which can be facilitated by the `--lower` option of `F2PY`