Using Python in CMake script

Finding Python is made more robust in CMake >= 3.15, which prioritizes location over version number. Prior to CMake 3.15, even specifying Python3_ROOT could be overridden if the other Python was a higher version.

Using the Python interpreter in CMake should generally be via PYTHON3_EXECUTABLE and not Python3::Interpreter. CMake provides the imported target Python3::Interpreter only when the CMAKE_ROLE is PROJECT. This means that Python3::Interpreter is not available when using CTest, which is often when using the Python3 interpreter is desired. Normally, to use Python interpreter from a CMake script, including in execute_process or add_test, use PYTHON3_EXECUTABLE.

Example

Either specify minimum CMake version 3.15:

cmake_minimum_required(VERSION 3.15)

OR where older CMake versions must be accommodated:

if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15)
  cmake_policy(SET CMP0094 NEW)
endif()

then do like:

find_package(Python3 COMPONENTS Interpreter REQUIRED)

add_test(NAME Foo COMMAND ${PYTHON3_EXECUTABLE} myscript.py -arg1 value)

Cmake HTTP UserAgent

CMake’s UserAgent is like

curl/$curl_version

as seen with CMake script:

file(DOWNLOAD ${url} result.html)

examining result.html at a diagnostic website that returns user agent will have like:

curl/7.69.0

This is worth noting if you find an HTTP server that blocks CMake HTTP traffic such as file(DOWNLOAD …), but not a web browser. This same problem can happen with Python as server operators may tire of scripts scraping their content. So web scrapers simply get more sophisticated and the “war” rages on….

Install Python package from CMake

To handle the case where the user is using system Python, or otherwise has Python site-packages as a non-writable directory, we need the pip “–user” option to install the package under the user home directory. However, if using Python virtualenv (with or without conda) the pip “–user” option is invalid. CMake can use environment variables set by Python to detect when a virtualenv is being used by Python currently.

Using “pip” instead of “setup.py” is also important for locally installed packages, since pip via pyproject.toml will automatically use the latest setuptools. This is quite important as too many user systems have too-old setuptools. The project’s pyproject.toml file should contain at least:

pyproject.toml:

[build-system]
requires = ["setuptools", "wheel"]

CMakeLists.txt

find_package(Python3 COMPONENTS Interpreter REQUIRED)

# detect virtualenv and set Pip args accordingly
if(DEFINED ENV{VIRTUAL_ENV} OR DEFINED ENV{CONDA_PREFIX})
  set(_pip_args)
else()
  set(_pip_args "--user")
endif()

To install a package (named in CMake variable _pypkg_name) from PyPi:

# install PyPi Python package using pip
execute_process(COMMAND ${Python3_EXECUTABLE} -m pip install ${_pypkg_name} ${_pip_args})

To install a local package in development mode (live changes):

# install Python package locally in development mode
execute_process(COMMAND ${Python3_EXECUTABLE} -m pip install -e . ${_pip_args})

References

This is how CMake itself in Modules/FindPython/Support.cmake detects Python virtualenv.

Matplotlib 3-D mesh wiregrid example

Matplotlib no longer needs cumbersome extra imports to make 3-D plots.

Example

3-D mesh wiregrid: minimal working example for the current version of Matplotlib. You can pan, rotate, etc. in the figure window that opens.

from matplotlib.pyplot import figure,show
import numpy as np

def plot3():
    x,y = np.meshgrid(np.linspace(0,2*np.pi), np.linspace(0,2*np.pi))

    z = np.sin(x+0.5*y)
    ax = figure().gca(projection='3d')
    ax.plot_wireframe(x,y,z)
    show()

if __name__ == '__main__':
    plot3()

Alternative

Mayavi for advanced 3-D plotting in Python

enable Windows user to create symbolic links

Windows users may not have permission to create a symbolic link by default. This can result in errors from Python like:

from pathlib import Path

Path('x').touch()
Path('x').symlink_to('y')

or

import os

os.symlink('x', 'y')

OSError: symbolic link privilege not held

Fix

Put Windows into Developer Mode: Settings → Updates & Security → For Developers → Use developer features: Select “Developer mode”

Restart computer, and once again try Python script above to see that Python can create symbolic links.

If it’s still not possible to create symbolic links, you may simply need to use Python ≥ 3.8.

Group Policy

If your version of Windows has it, press Windows + R key (run dialog) and type

gpedit.msc

If you don’t have this, you will need to use Python ≥ 3.8 with Windows in Developer Mode as above.


Navigate to create user permission to create symbolic links:

Computer Configuration → Windows Settings → Security Settings → Local Policies → User Rights Assignment → Create symbolic links

type the user name and click “Check Names” then OK.

Reboot the computer (or logoff, logon) to make the permissions take effect.

References

  • Python os.symlink() on Windows generally requires Python ≥ 3.8

Create symbolic links on Windows

MacOS hardware requirements vs. version

Older MacOS hardware is often of relevance for development or maintenance purposes. Like Windows, MacOS requirements increase over time. If the Apple computer is too old for a currently supported MacOS version, consider installing Linux, which normally works very well on Apple computer hardware. Apple maintains a list of no longer supported Apple hardware.

MacOS hardware requirement vs version

MacOS requirements by version.

Plotted data source is Wikipedia and Apple.

Windows Nearby sharing via slow Bluetooth

When files need to be shared between two computers, whether at office, home or conference, major operating systems have peer-to-peer file sharing built in.

Windows

Windows Nearby sharing uses Bluetooth only, which means file transfer speeds are a tiny fraction (far slower) than even 3G tethered internet. Windows Nearby has the advantage like other operating systems of not requiring internet, but the real-world file transfer throughput is on the order of 1 Mbps, due to the limitations of Bluetooth itself and interference from other 2.4 GHz devices, even when the two laptops are less than 3 meters apart.

Windows Nearby file sharing is generally not practical for files over about 10 megabytes, due to the transfer rate of about 100..200 kilobytes / second in real-world use. That is, a 10 megabyte file takes about 2 minutes to transfer using Windows Nearby.

Apple

Apple AirDrop is much faster than Windows Nearby since AirDrop uses Bluetooth to negotiate a peer-to-peer WiFi connection that doesn’t rely on WiFi AP (router). Depending on the RF congestion and the nature of files transferred (few large files faster than many small files) the data throughput of AirDrop can be in the 100..500 Mbps range, 100 times faster than Windows Nearby sharing. It would be very beneficial if Windows Nearby would use adhoc WiFi like AirDrop.

Android

The deprecated Android Beam used NFC to negotiate a Bluetooth connection, which was very slow like Windows Nearby. Android Nearby is reported to a possible feature in Android 11, ChromeOS and Chrome browsers. We will hope that Android Nearby will use WiFi like AirDrop for useful speed.

Matlab + Octave unit tests quick-start

Matlab unit test framework is well-established and said to have a JUnit-like structure. Matlab’s unit test framework is completely incompatible with GNU Octave’s unit testing framework. A common ground between Matlab and GNU Octave can be found in the easy to use Matlab script-based tests, which can be run as an ordinary script with GNU Octave.

Script-based test

A key characteristic of Matlab script-based tests is they can’t use the Assertable functions. This also means you cannot mark a test as skipped or incomplete with script-based tests. This is not generally a big issue unless your project specifically mandates such usage.

Matlab or Octave can run a script-based test as a plain script. The downsides of running a script-based test as a plain script are:

  1. there is no decorated TestResult
  2. the first failure ends the whole test

It’s best to put the script-based test scripts in the same directory as the code they’re testing to avoid Matlab path issues.

Matlab runtests

When using Matlab on this Octave-compatible script-based test, a richer result comes from using Matlab-only runtests(). Matlab runtests() will search all subdirectories under the current working directory and run all scripts with case-insensitive “test” in the filename. For example, a script testing HDF5 function of a program might be named TestHDF5.m or HDF5test.m. This is similar to PyTest filename-filtering.

Denote each test in the script-based test file with a double percent sign like:

% test_example.m
% setup code goes up top, as each test's variables are isolated from each other
% when using Matlab runtests()

A = magic(2);

%% test_dummy
B = A*2;  % only visible to this test, when using Matlab runtests()
assert(isequal(B, A*2), 'dummy example')

%% test_four
C = A*4; % note that "B" is not visible under runtests()
assert(isequal(C, A*4), 'dummy four')

Run Matlab runtests() from CI command using a runner script. Otherwise, just doing matlab -batch runtests will NOT fail in CI, even with failing test.

Octave

Octave runtests('.') only runs tests in specially formatted comments that Matlab ignores. Thus, we generally recommend writing Matlab script-based tests, and manually running each file from Octave.

Global PEP8 Git pre-commit check hook

Too many fixup Git commits are for forgetting to check project code standards like:

  • PEP8 compliance
  • type annotation checks
  • clang-format

before git push. Eliminate this git commit fixup clutter by making a simple Git pre-commit hook that applies to all repositories. This precommit hook also does a simple check for YaML syntax.

Procedure

This procedure is for Unix-like systems (Linux, MacOS) and native Windows. The script used is written in Python to make it OS agnostic.

Tell Git the directory for Git hooks:

git config --global core.hooksPath ~/.git/hooks

Create executable file ~/.git/hooks/pre-commit from this script.

This global pre-commit check hook does several things:

  1. get a list of Python file names that changed.
  2. check PEP8, mypy type hinting and that Python ≥ 3.7 breakpoint debug statements are not present.
  3. check YaML syntax
  4. Checks for trailing whitespaces in any code language

One can easily extend the concept of lint-checking for other programming languages.

Bypass checks

These checks can be bypassed at any time for a commit by:

git commit -n

override for a particular repo

Override this global pre-commit check, substituting a per-repo .git/hooks/pre-commit by in that repo directory typing:

git config core.hooksPath .git/hooks

For example, a website made of Markdown files may wish to run a local-link check via Linkchecker-Markdown.

References

pre-commit check website markdown internal links

Linkchecker-markdown is a very fast Python module that checks internal and external website links using the raw Markdown before rendering by the static site generator. Avoid broken internal links by using a Git pre-commit hook to check all internal links upon each “git commit”.

setup

In the top directory of the Markdown website repo, do:

git config core.hooksPath .git/hooks

add the file .git/hooks/pre-commit (no .py extension)

global Git pre-commit for checking Python PEP8, etc.