Repository: numpy/numpy
Pull request: DOC, MAINT: Update build systems for f2py
<!--         ----------------------------------------------------------------
                MAKE SURE YOUR PR GETS THE ATTENTION IT DESERVES!
                ----------------------------------------------------------------

*  FORMAT IT RIGHT:
      http://www.numpy.org/devdocs/dev/development_workflow.html#writing-the-commit-message

*  IF IT'S A NEW FEATURE OR API CHANGE, TEST THE WATERS:
      http://www.numpy.org/devdocs/dev/development_workflow.html#get-the-mailing-list-s-opinion

*  HIT ALL THE GUIDELINES:
      https://numpy.org/devdocs/dev/index.html#guidelines

*  WHAT TO DO IF WE HAVEN'T GOTTEN BACK TO YOU:
      http://www.numpy.org/devdocs/dev/development_workflow.html#getting-your-pr-reviewed
-->
Primarily establishes:
- [x] Hierarchy and structure for adding build systems and their usage with `f2py`
- [x] Meson example
- [x] CMake example
- [x] Scikit Build example with @Nicholaswogan

Also minor stylistic changes across the `f2py` docs.

Suggestions welcome, but do not review until draft status is removed :)

Closes #20181.

Problem statement:
DOC: F2PY and Build Systems
### Issue with current documentation:

There are several examples of using `f2py` with build systems. A structure should be in place to allow such contributions. 

### Idea or request for content:

In particular the following should be part of the official docs:
- Meson
- Scikit Build
- CMake

It would also be in scope to perhaps leave a warning w.r.t. using `np.distutils`

Hints from issue discussion:
@Nicholaswogan would you prefer to open an `rst` example for `scikit-build` along the lines of `Using via meson` or should I adapt it and add you as a co-committer?

Commit message: DOC: Update meson and f2py for automated wrappers

Patch to review:
diff --git a/doc/source/f2py/buildtools/cmake.rst b/doc/source/f2py/buildtools/cmake.rst
new file mode 100644
index 000000000000..f7c69a8bb186
--- /dev/null
+++ b/doc/source/f2py/buildtools/cmake.rst
@@ -0,0 +1,60 @@
+.. _f2py-cmake:
+
+===================
+Using via ``cmake``
+===================
+
+In terms of complexity, ``cmake`` falls between ``make`` and ``meson``. The
+learning curve is steeper since CMake syntax is not pythonic and is closer to
+``make`` with environment variables.
+
+However, the trade-off is enhanced flexibility and support for most architectures
+and compilers. An introduction to the syntax is out of scope for this document,
+but the `official CMake Tutorials`_ are a fantastic resource.
+
+.. note::
+
+   ``cmake`` is very popular for mixed-language systems, however support for
+   ``f2py`` is not particularly native or pleasant; and a more natural approach
+   is to consider :ref:`f2py-skbuild`
+
+Fibonacci Walkthrough (F77)
+===========================
+
+Returning to the ``fib``  example from :ref:`f2py-getting-started` section.
+
+.. literalinclude:: ./../code/fib1.f
+    :language: fortran
+
+We do not need to explicitly generate the ``python -m numpy.f2py fib1.f``
+output, which is ``fib1module.c``, which is beneficial. With this; we can now
+initialize a ``CMakeLists.txt`` file as follows:
+
+.. literalinclude:: ./../code/CMakeLists.txt
+    :language: cmake
+
+A key element of the ``CMakeLists.txt`` file defined above is that the
+``add_custom_command`` is used to generate the wrapper ``C`` files and then
+added as a dependency of the actual shared library target via a
+``add_custom_target`` directive which prevents the command from running every
+time. Additionally, the method used for obtaining the ``fortranobject.c`` file
+can also be used to grab the ``numpy`` headers on older ``cmake`` versions.
+
+This then works in the same manner as the other modules, although the naming
+conventions are different and the output library is not automatically prefixed
+with the ``cython`` information.
+
+.. code:: bash
+
+    ls .
+    # CMakeLists.txt fib1.f
+    mkdir build && cd build
+    cmake ..
+    make
+    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.]
+
+This is particularly useful where an existing toolchain already exists and
+``scikit-build`` or other additional ``python`` dependencies are discouraged.
+
+.. _official CMake Tutorials: https://cmake.org/cmake/help/latest/guide/tutorial/index.html
diff --git a/doc/source/f2py/distutils.rst b/doc/source/f2py/buildtools/distutils.rst
similarity index 87%
rename from doc/source/f2py/distutils.rst
rename to doc/source/f2py/buildtools/distutils.rst
index 575dacdff21b..9abeee8b84a7 100644
--- a/doc/source/f2py/distutils.rst
+++ b/doc/source/f2py/buildtools/distutils.rst
@@ -1,3 +1,5 @@
+.. _f2py-distutils:
+
 =============================
 Using via `numpy.distutils`
 =============================
@@ -10,23 +12,21 @@ compile Fortran sources, call F2PY to construct extension modules, etc.
 
 .. topic:: Example
 
-  Consider the following `setup file`__ for the ``fib`` examples in the previous
-  section:
+  Consider the following ``setup_file.py`` for the ``fib`` and ``scalar``
+  examples from :ref:`f2py-getting-started` section:
 
-  .. literalinclude:: ./code/setup_example.py
+  .. literalinclude:: ./../code/setup_example.py
     :language: python
 
   Running
 
-  ::
+  .. code-block:: bash
 
     python setup_example.py build
 
   will build two extension modules ``scalar`` and ``fib2`` to the
   build directory.
-
-  __ setup_example.py
-
+   
 Extensions to ``distutils``
 ===========================
 
@@ -57,7 +57,7 @@ Extensions to ``distutils``
 
   Run
 
-  ::
+  .. code-block:: bash
 
     python <setup.py file> config_fc build_src build_ext --help
 
@@ -73,6 +73,6 @@ Extensions to ``distutils``
   See ``numpy_distutils/fcompiler.py`` for an up-to-date list of
   supported compilers for different platforms, or run
 
-  ::
+  .. code-block:: bash
 
-     f2py -c --help-fcompiler
+     python -m numpy.f2py -c --help-fcompiler
diff --git a/doc/source/f2py/buildtools/index.rst b/doc/source/f2py/buildtools/index.rst
new file mode 100644
index 000000000000..959301ec5ab9
--- /dev/null
+++ b/doc/source/f2py/buildtools/index.rst
@@ -0,0 +1,96 @@
+.. _f2py-bldsys:
+
+=======================
+F2PY and Build Systems
+=======================
+
+In this section we will cover the various popular build systems and their usage
+with ``f2py``.
+
+.. note::
+   **As of November 2021**
+
+   The default build system for ``F2PY`` has traditionally been the through the
+   enhanced ``numpy.distutils`` module. This module is based on ``distutils`` which
+   will be removed in ``Python 3.12.0`` in **October 2023**; ``setuptools`` does not
+   have support for Fortran or ``F2PY`` and it is unclear if it will be supported
+   in the future. Alternative methods are thus increasingly more important.
+
+
+Basic Concepts
+===============
+
+Building an extension module which includes Python and Fortran consists of:
+
+- Fortran source(s)
+- One or more generated files from ``f2py``
+
+  + A ``C`` wrapper file is always created
+  + Code with modules require an additional ``.f90`` wrapper
+
+- ``fortranobject.{c,h}``
+
+  + Distributed with ``numpy``
+  + Can be queried via ``python -c "import numpy.f2py; print(numpy.f2py.get_include())"``
+
+- NumPy headers
+
+  + Can be queried via ``python -c "import numpy; print(numpy.get_include())"``
+
+- Python libraries and development headers
+
+Broadly speaking there are three cases which arise when considering the outputs of ``f2py``:
+
+Fortran 77 programs
+   - Input file ``blah.f``
+   - Generates
+
+     + ``blahmodule.c``
+     + ``f2pywrappers.f``
+
+   When no ``COMMON`` blocks are present only a ``C`` wrapper file is generated.
+   Wrappers are also generated to rewrite assumed shape arrays as automatic
+   arrays.
+
+Fortran 90 programs
+   - Input file ``blah.f``
+   - Generates:
+
+     + ``blahmodule.c``
+     + ``blah-f2pywrappers2.f90``
+
+   The secondary wrapper is used to handle code which is subdivided into
+   modules. It rewrites assumed shape arrays as automatic arrays.
+
+Signature files
+   - Input file ``blah.pyf``
+   - Generates:
+
+     + ``blahmodule.c``
+     + ``blah-f2pywrappers2.f90`` (occasionally)
+     + ``f2pywrappers.f`` (occasionally)
+
+   Signature files ``.pyf`` do not signal their language standard via the file
+   extension, they may generate the F90 and F77 specific wrappers depending on
+   their contents; which shifts the burden of checking for generated files onto
+   the build system.
+
+
+In theory keeping the above requirements in hand, any build system can be
+adapted to generate ``f2py`` extension modules. Here we will cover a subset of
+the more popular systems.
+
+.. note::
+   ``make`` has no place in a modern multi-language setup, and so is not
+   discussed further.
+
+Build Systems
+==============
+
+.. toctree::
+   :maxdepth: 2
+
+   distutils
+   meson
+   cmake
+   skbuild
diff --git a/doc/source/f2py/buildtools/meson.rst b/doc/source/f2py/buildtools/meson.rst
new file mode 100644
index 000000000000..d18715bbf27b
--- /dev/null
+++ b/doc/source/f2py/buildtools/meson.rst
@@ -0,0 +1,120 @@
+.. _f2py-meson:
+
+===================
+Using via ``meson``
+===================
+
+The key advantage gained by leveraging ``meson`` over the techniques described
+in :ref:`f2py-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:
+
+.. code-block:: bash
+
+    python -n numpy.f2py fib1.f -m fib2
+
+Now, consider the following ``meson.build`` file for the ``fib`` and ``scalar``
+examples from :ref:`f2py-getting-started` section:
+
+.. literalinclude:: ./../code/meson.build
+    :language: meson
+
+.. note::
+
+    * The ``fortranobject.{c,h}`` files have to be included in the manner shown,
+      by referencing the ``.c`` file and adding to the include directories.
+    * The warning level is lower than that of a build set up with ``meson init``
+
+At this point the build will complete, but the import will fail:
+
+.. code-block:: bash
+
+   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:
+
+.. literalinclude:: ./../code/fib1.f
+   :language: fortran
+
+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:
+
+.. code-block:: bash
+
+   tr "[:upper:]" "[:lower:]" < fib1.f > fib1.f
+   python -n 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:
+
+.. code-block:: bash
+
+   python -n 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 :ref:`f2py-bldsys`.
+
+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.
+
+.. literalinclude:: ./../code/meson_upd.build
+    :language: meson
+
+This can be compiled and run as before.
+
+.. code-block:: bash
+
+    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``
diff --git a/doc/source/f2py/buildtools/skbuild.rst b/doc/source/f2py/buildtools/skbuild.rst
new file mode 100644
index 000000000000..59fe2f82200d
--- /dev/null
+++ b/doc/source/f2py/buildtools/skbuild.rst
@@ -0,0 +1,84 @@
+.. _f2py-skbuild:
+
+============================
+Using via ``scikit-build``
+============================
+
+``scikit-build`` provides two separate concepts geared towards the users of Python extension modules.
+1. A ``setuptools`` replacement
+2. A series of ``cmake`` modules with definitions which help building Python extensions
+
+.. note::
+
+   It is possible to use ``scikit-build``'s ``cmake`` modules to `bypass the
+   cmake setup mechanism`_ completely, and to write targets which call ``f2py
+   -c``. This usage is **not recommended** since the point of these build system
+   documents are to move away from the internal ``numpy.distutils`` methods.
+
+For situations where no ``setuptools`` replacements are required or wanted (i.e.
+if ``wheels`` are not needed), it is recommended to instead use the vanilla
+``cmake`` setup described in :ref:`f2py-cmake`.
+
+Fibonacci Walkthrough (F77)
+===========================
+
+We will consider the ``fib``  example from :ref:`f2py-getting-started` section.
+
+.. literalinclude:: ./../code/fib1.f
+    :language: fortran
+
+``CMake`` modules only
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Consider using the following ``CMakeLists.txt``.
+
+.. literalinclude:: ./../code/CMakeLists_skbuild.txt
+   :language: cmake
+
+Much of the logic is the same as in :ref:`f2py-cmake`, however notably here the
+appropriate module suffix is generated via ``sysconfig.get_config_var("SO")``.
+The resulting extension can be built and loaded in the standard workflow.
+
+.. code:: bash
+
+    ls .
+    # CMakeLists.txt fib1.f
+    mkdir build && cd build
+    cmake ..
+    make
+    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.]
+
+
+``setuptools`` replacement
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The utility of ``scikit-build`` lies in being able to drive the generation of
+more than extension modules, in particular a common usage pattern is the
+generation of Python distributables (for example for PyPI).
+
+The workflow with ``scikit-build`` straightforwardly supports such packaging requirements. Consider augmenting the project with a ``setup.py`` as defined:
+
+.. literalinclude:: ./../code/setup_skbuild.py
+   :language: python
+
+Along with a commiserate ``pyproject.toml``
+
+.. literalinclude:: ./../code/pyproj_skbuild.toml
+   :language: toml
+
+Together these can build the extension using ``cmake`` in tandem with other
+standard ``setuptools`` outputs.
+
+.. code:: bash
+
+    ls .
+    # CMakeLists.txt fib1.f pyproject.toml setup.py
+    python setup.py build_ext --inplace
+    python -c "import numpy as np; import fibby.fibby; a = np.zeros(9); fibby.fibby.fib(a); print (a)"
+    # [ 0.  1.  1.  2.  3.  5.  8. 13. 21.]
+
+Where we have modified the path to the module as ``--inplace`` places the
+extension module in a subfolder.
+
+.. _bypass the cmake setup mechanism: https://scikit-build.readthedocs.io/en/latest/cmake-modules/F2PY.html
diff --git a/doc/source/f2py/code/CMakeLists.txt b/doc/source/f2py/code/CMakeLists.txt
new file mode 100644
index 000000000000..90f8fbac1fca
--- /dev/null
+++ b/doc/source/f2py/code/CMakeLists.txt
@@ -0,0 +1,74 @@
+### setup project ###
+cmake_minimum_required(VERSION 3.17.3)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+project(fibby
+  VERSION 1.0
+  DESCRIPTION "FIB module"
+  LANGUAGES C Fortran
+  )
+
+# Safety net
+if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
+  message(
+    FATAL_ERROR
+      "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n"
+  )
+endif()
+
+# Grab Python
+find_package(Python3 3.9 REQUIRED
+  COMPONENTS Interpreter Development NumPy)
+
+# Grab the variables from a local Python installation
+# F2PY headers
+execute_process(
+  COMMAND "${Python3_EXECUTABLE}"
+  -c "import numpy.f2py; print(numpy.f2py.get_include())"
+  OUTPUT_VARIABLE F2PY_INCLUDE_DIR
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
+# Project scope; consider using target_include_directories instead
+include_directories(
+  BEFORE
+  ${Python3_INCLUDE_DIRS}
+  ${Python3_NumPy_INCLUDE_DIRS}
+  ${F2PY_INCLUDE_DIR}
+  )
+
+message(STATUS ${Python3_INCLUDE_DIRS})
+message(STATUS ${F2PY_INCLUDE_DIR})
+message(STATUS ${Python3_NumPy_INCLUDE_DIRS})
+
+# Generate sources
+add_custom_target(
+  genpyf
+  DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}module.c"
+  )
+add_custom_command(
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}module.c"
+  COMMAND ${Python3_EXECUTABLE}  -m "numpy.f2py"
+                   "${CMAKE_SOURCE_DIR}/fib1.f"
+                   -m "fibby"
+                   --lower # Important
+  DEPENDS fib1.f # Fortran source
+  )
+
+# Set up target
+add_library(${CMAKE_PROJECT_NAME} SHARED
+  "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}module.c" # Generated
+  "${F2PY_INCLUDE_DIR}/fortranobject.c" # From NumPy
+  fib1.f # Fortran source(s)
+  )
+
+# Depend on sources
+add_dependencies(${CMAKE_PROJECT_NAME} genpyf)
+
+set_target_properties(
+     ${CMAKE_PROJECT_NAME}
+    PROPERTIES
+        PREFIX ""
+        OUTPUT_NAME "${CMAKE_PROJECT_NAME}"
+        LINKER_LANGUAGE C
+    )
diff --git a/doc/source/f2py/code/CMakeLists_skbuild.txt b/doc/source/f2py/code/CMakeLists_skbuild.txt
new file mode 100644
index 000000000000..97bc5c744d41
--- /dev/null
+++ b/doc/source/f2py/code/CMakeLists_skbuild.txt
@@ -0,0 +1,89 @@
+### setup project ###
+cmake_minimum_required(VERSION 3.17.3)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+project(fibby
+  VERSION 1.0
+  DESCRIPTION "FIB module"
+  LANGUAGES C Fortran
+  )
+
+# Safety net
+if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
+  message(
+    FATAL_ERROR
+      "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n"
+  )
+endif()
+
+# Grab Python
+find_package(Python3 3.9 REQUIRED
+  COMPONENTS Interpreter Development)
+
+# Ensure scikit-build modules
+if (NOT SKBUILD)
+  # Kanged -->https://github.com/Kitware/torch_liberator/blob/master/CMakeLists.txt
+  # If skbuild is not the driver; include its utilities in CMAKE_MODULE_PATH
+  execute_process(
+  COMMAND "${Python3_EXECUTABLE}"
+  -c "import os, skbuild; print(os.path.dirname(skbuild.__file__))"
+  OUTPUT_VARIABLE SKBLD_DIR
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+  )
+  set(SKBLD_CMAKE_DIR "${SKBLD_DIR}/resources/cmake")
+  list(APPEND CMAKE_MODULE_PATH ${SKBLD_CMAKE_DIR})
+endif()
+
+# scikit-build style includes
+find_package(PythonExtensions REQUIRED) # for ${PYTHON_EXTENSION_MODULE_SUFFIX}
+find_package(NumPy REQUIRED) # for ${NumPy_INCLUDE_DIRS}
+find_package(F2PY REQUIRED) # for ${F2PY_INCLUDE_DIR}
+
+# Prepping the module
+set(f2py_module_name "fibby")
+set(fortran_src_file "${CMAKE_SOURCE_DIR}/fib1.f")
+set(generated_module_file ${f2py_module_name}${PYTHON_EXTENSION_MODULE_SUFFIX})
+
+# Target for enforcing dependencies
+add_custom_target(${f2py_module_name} ALL
+  DEPENDS "${fortran_src_file}"
+  )
+
+# Custom command for generating .c
+add_custom_command(
+  OUTPUT "${f2py_module_name}module.c"
+  COMMAND ${F2PY_EXECUTABLE}
+    -m ${f2py_module_name}
+    ${fortran_src_file}
+    --lower
+  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  DEPENDS ${fortran_src_file}
+  )
+
+add_library(${generated_module_file} MODULE
+            "${f2py_module_name}module.c"
+            "${F2PY_INCLUDE_DIR}/fortranobject.c"
+            "${fortran_src_file}")
+
+target_include_directories(${generated_module_file} PUBLIC
+                           ${F2PY_INCLUDE_DIRS}
+                           ${PYTHON_INCLUDE_DIRS})
+set_target_properties(${generated_module_file} PROPERTIES SUFFIX "")
+set_target_properties(${generated_module_file} PROPERTIES PREFIX "")
+
+# Linker fixes
+if (UNIX)
+  if (APPLE)
+    set_target_properties(${generated_module_file} PROPERTIES
+    LINK_FLAGS  '-Wl,-dylib,-undefined,dynamic_lookup')
+  else()
+    set_target_properties(${generated_module_file} PROPERTIES
+  LINK_FLAGS  '-Wl,--allow-shlib-undefined')
+  endif()
+endif()
+
+if (SKBUILD)
+  install(TARGETS ${generated_module_file} DESTINATION fibby)
+else()
+  install(TARGETS ${generated_module_file} DESTINATION ${CMAKE_SOURCE_DIR}/fibby)
+endif()
diff --git a/doc/source/f2py/code/meson.build b/doc/source/f2py/code/meson.build
new file mode 100644
index 000000000000..b756abf8f59a
--- /dev/null
+++ b/doc/source/f2py/code/meson.build
@@ -0,0 +1,38 @@
+project('f2py_examples', 'c',
+  version : '0.1',
+  default_options : ['warning_level=2'])
+
+add_languages('fortran')
+
+py_mod = import('python')
+py3 = py_mod.find_installation('python3')
+py3_dep = py3.dependency()
+message(py3.path())
+message(py3.get_install_dir())
+
+incdir_numpy = run_command(py3,
+  ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
+  check : true
+).stdout().strip()
+
+incdir_f2py = run_command(py3,
+    ['-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'],
+                            output : ['fibbymodule.c'],
+                            command : [ py3, '-m', 'numpy.f2py', '@INPUT@',
+                            '-m', 'fibby', '--lower' ]
+                            )
+
+inc_np = include_directories(incdir_numpy, incdir_f2py)
+
+py3.extension_module('fibby',
+           'fib1.f',
+           fibby_source,
+           incdir_f2py+'/fortranobject.c',
+           include_directories: inc_np,
+           dependencies : py3_dep,
+           install : true)
diff --git a/doc/source/f2py/code/meson_upd.build b/doc/source/f2py/code/meson_upd.build
new file mode 100644
index 000000000000..97bd8d175c7c
--- /dev/null
+++ b/doc/source/f2py/code/meson_upd.build
@@ -0,0 +1,37 @@
+project('f2py_examples', 'c',
+  version : '0.1',
+  default_options : ['warning_level=2'])
+
+add_languages('fortran')
+
+py_mod = import('python')
+py3 = py_mod.find_installation('python3')
+py3_dep = py3.dependency()
+message(py3.path())
+message(py3.get_install_dir())
+
+incdir_numpy = run_command(py3,
+  ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
+  check : true
+).stdout().strip()
+
+incdir_f2py = run_command(py3,
+    ['-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'],
+                            output : ['fibbymodule.c'],
+                            command : [ py3, '-m', 'numpy.f2py', '@INPUT@',
+                            '-m', 'fibby', '--lower' ])
+
+inc_np = include_directories(incdir_numpy, incdir_f2py)
+
+py3.extension_module('fibby',
+           'fib1.f',
+           fibby_source,
+           incdir_f2py+'/fortranobject.c',
+           include_directories: inc_np,
+           dependencies : py3_dep,
+           install : true)
diff --git a/doc/source/f2py/code/pyproj_skbuild.toml b/doc/source/f2py/code/pyproj_skbuild.toml
new file mode 100644
index 000000000000..6686d1736015
--- /dev/null
+++ b/doc/source/f2py/code/pyproj_skbuild.toml
@@ -0,0 +1,5 @@
+[project]
+requires-python = ">=3.7"
+
+[build-system]
+requires = ["setuptools>=42", "wheel", "scikit-build", "cmake>=3.18", "numpy>=1.21"]
diff --git a/doc/source/f2py/code/setup_skbuild.py b/doc/source/f2py/code/setup_skbuild.py
new file mode 100644
index 000000000000..4dfc6af8b76d
--- /dev/null
+++ b/doc/source/f2py/code/setup_skbuild.py
@@ -0,0 +1,10 @@
+from skbuild import setup
+
+setup(
+    name="fibby",
+    version="0.0.1",
+    description="a minimal example package (fortran version)",
+    license="MIT",
+    packages=['fibby'],
+    cmake_args=['-DSKBUILD=ON']
+)
diff --git a/doc/source/f2py/f2py.getting-started.rst b/doc/source/f2py/f2py.getting-started.rst
index 1709aad61bba..c1a006f6f2eb 100644
--- a/doc/source/f2py/f2py.getting-started.rst
+++ b/doc/source/f2py/f2py.getting-started.rst
@@ -1,3 +1,5 @@
+.. _f2py-getting-started:
+
 ======================================
  Three ways to wrap - getting started
 ======================================
diff --git a/doc/source/f2py/index.rst b/doc/source/f2py/index.rst
index c774a0df6f82..56df31b4e752 100644
--- a/doc/source/f2py/index.rst
+++ b/doc/source/f2py/index.rst
@@ -23,9 +23,9 @@ from Python.
 
    usage
    f2py.getting-started
-   distutils
    python-usage
    signature-file
+   buildtools/index
    advanced
 
 .. _Python: https://www.python.org/
diff --git a/numpy/f2py/__init__.py b/numpy/f2py/__init__.py
index a0fb73619178..f147f1b970a3 100644
--- a/numpy/f2py/__init__.py
+++ b/numpy/f2py/__init__.py
@@ -71,7 +71,7 @@ def compile(source,
 
     Examples
     --------
-    .. literalinclude:: code/results/compile_session.dat
+    .. literalinclude:: ../../source/f2py/code/results/compile_session.dat
         :language: python
 
     """
diff --git a/numpy/f2py/f2py2e.py b/numpy/f2py/f2py2e.py
index 605495574300..4fbb27c7e2e3 100755
--- a/numpy/f2py/f2py2e.py
+++ b/numpy/f2py/f2py2e.py
@@ -416,7 +416,7 @@ def run_main(comline_list):
 
     Examples
     --------
-    .. literalinclude:: code/results/run_main_session.dat
+    .. literalinclude:: ../../source/f2py/code/results/run_main_session.dat
         :language: python
 
     """


Code review comments:
diff_hunk: @@ -0,0 +1,96 @@
+.. _f2py-bldsys:
+
+=======================
+F2PY and Build Systems
+=======================
+
+In this section we will cover the various popular build systems and their usage
+with ``f2py``.
+
+.. note::
+   **As of November 2021**
+
+   The default build system for ``F2PY`` has traditionally been the through the
+   enhanced ``numpy.distutils`` module. This module is based on ``distutils`` which
+   will be removed in ``Python 3.12.0`` in **October 2023**; ``setuptools`` does not
+   have support for Fortran or ``F2PY`` and it is unclear if it will be supported
+   in the future. Alternative methods are thus increasingly more important.
+
+
+Basic Concepts
+===============
+
+Building an extension module which includes Python and Fortran consists of:
+
+- Fortran source(s)
+- One or more generated files from ``f2py``
+
+  + A ``C`` wrapper file is always created
+  + Code with modules require an additional ``.f90`` wrapper
+
+- ``fortranobject.{c,h}``
+
+  + Distributed with ``numpy``
+  + Can be queried via ``python -c "import numpy.f2py; print(numpy.f2py.get_include())"``
+
+- NumPy headers
+
+  + Can be queried via ``python -c "import numpy; print(numpy.get_include())"``
+
+- Python libraries and development headers
+
+Broadly speaking there are three cases which arise when considering the outputs of ``f2py``:
+
+Fortran 77 programs
+   - Input file ``blah.f``
+   - Generates
+
+     + ``blahmodule.c``
+     + ``f2pywrappers.f``
+
+   When no ``COMMON`` blocks are present only a ``C`` wrapper file is generated.
+   Wrappers are also generated to rewrite assumed shape arrays as automatic
+   arrays.
+
+Fortran 90 programs
+   - Input file ``blah.f``
path: doc/source/f2py/buildtools/index.rst
text: @user1:
(did you mean to write `blah.f90`?)

diff_hunk: @@ -0,0 +1,60 @@
+.. _f2py-cmake:
+
+===================
+Using via ``cmake``
+===================
+
+In terms of complexity, ``cmake`` falls between ``make`` and ``meson``. The
+learning curve is steeper since CMake syntax is not pythonic and is closer to
+``make`` with environment variables.
+
+However, the trade-off is enhanced flexibility and support for most architectures
+and compilers. An introduction to the syntax is out of scope for this document,
+but the `official CMake Tutorials`_ are a fantastic resource.
path: doc/source/f2py/buildtools/cmake.rst
text: @user1:
I don't get this line. It seems like there's wide consensus that the the official tutorials and examples of CMake suck, badly. And browsing it again now, I tend to agree. Why not link to, e.g., https://cliutils.gitlab.io/modern-cmake/?

@author:
^_^ Somehow I got by on the official tutorial and references; these are much better though, updating.

diff_hunk: @@ -0,0 +1,120 @@
+.. _f2py-meson:
+
+===================
+Using via ``meson``
+===================
+
+The key advantage gained by leveraging ``meson`` over the techniques described
+in :ref:`f2py-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:
+
+.. code-block:: bash
+
+    python -n numpy.f2py fib1.f -m fib2
+
+Now, consider the following ``meson.build`` file for the ``fib`` and ``scalar``
+examples from :ref:`f2py-getting-started` section:
+
+.. literalinclude:: ./../code/meson.build
+    :language: meson
+
+.. note::
+
+    * The ``fortranobject.{c,h}`` files have to be included in the manner shown,
+      by referencing the ``.c`` file and adding to the include directories.
+    * The warning level is lower than that of a build set up with ``meson init``
path: doc/source/f2py/buildtools/meson.rst
text: @user1:
This is a bit hard to interpret, what do you want to say here exactly? 

@author:
I think I can actually remove the first point; I was thinking of highlighting that these are collected from the `numpy` sources which can cause some confusion (e.g. system installation vs virtual etc).

The second is a bit important since running `meson init` has `warning_level=3` which will fail with the example because of an implicit type used in the `do i=1,n`. Technically the warning level is fine and real codebases shouldn't use implicit typing but a lot of older codes do and I thought it'd be best to mention that lowering the warning level might be required. I'll make this more explicit.

@user1:
`meson init` is not a thing I ever run, it's at most once in the lifetime of a project. So I'd just not mention it, it's basically irrelevant. The regular dev cycle is:
```
meson setup build_dir  # this is the configure stage
ninja -C build_dir  # can be left out, but I like using ninja directly
meson install
```


@author:
Makes sense, I'll remove the second point as well then.

diff_hunk: @@ -0,0 +1,84 @@
+.. _f2py-skbuild:
+
+============================
+Using via ``scikit-build``
+============================
+
+``scikit-build`` provides two separate concepts geared towards the users of Python extension modules.
+1. A ``setuptools`` replacement
+2. A series of ``cmake`` modules with definitions which help building Python extensions
+
+.. note::
+
+   It is possible to use ``scikit-build``'s ``cmake`` modules to `bypass the
+   cmake setup mechanism`_ completely, and to write targets which call ``f2py
+   -c``. This usage is **not recommended** since the point of these build system
+   documents are to move away from the internal ``numpy.distutils`` methods.
+
+For situations where no ``setuptools`` replacements are required or wanted (i.e.
+if ``wheels`` are not needed), it is recommended to instead use the vanilla
+``cmake`` setup described in :ref:`f2py-cmake`.
+
+Fibonacci Walkthrough (F77)
+===========================
+
+We will consider the ``fib``  example from :ref:`f2py-getting-started` section.
+
+.. literalinclude:: ./../code/fib1.f
+    :language: fortran
+
+``CMake`` modules only
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Consider using the following ``CMakeLists.txt``.
+
+.. literalinclude:: ./../code/CMakeLists_skbuild.txt
+   :language: cmake
+
+Much of the logic is the same as in :ref:`f2py-cmake`, however notably here the
+appropriate module suffix is generated via ``sysconfig.get_config_var("SO")``.
+The resulting extension can be built and loaded in the standard workflow.
+
+.. code:: bash
+
+    ls .
+    # CMakeLists.txt fib1.f
+    mkdir build && cd build
+    cmake ..
+    make
+    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.]
+
+
+``setuptools`` replacement
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The utility of ``scikit-build`` lies in being able to drive the generation of
+more than extension modules, in particular a common usage pattern is the
+generation of Python distributables (for example for PyPI).
+
+The workflow with ``scikit-build`` straightforwardly supports such packaging requirements. Consider augmenting the project with a ``setup.py`` as defined:
+
+.. literalinclude:: ./../code/setup_skbuild.py
+   :language: python
+
+Along with a commiserate ``pyproject.toml``
path: doc/source/f2py/buildtools/skbuild.rst
text: @user1:
"commiserate" is something like "have pity on" - maybe you meant commensurate?